@threlte/extras
Version:
Utilities, abstractions and plugins for your Threlte apps
57 lines (56 loc) • 2.18 kB
JavaScript
import { Camera, Vector3 } from 'three';
import { currentWritable, isInstanceOf, useTask, useThrelte, watch } from '@threlte/core';
const origin = new Vector3();
const position = new Vector3();
const lastPosition = new Vector3();
export const useViewport = (target) => {
const viewport = currentWritable({
width: 0,
height: 0,
factor: 0,
distance: 0
});
const { camera, size, renderStage, scheduler } = useThrelte();
const updateViewport = ($size, $camera, distance) => {
viewport.update(($viewport) => {
const { width, height } = $size;
if (Array.isArray(target)) {
origin.fromArray(target);
}
else if (target !== undefined) {
origin.copy(target);
}
$viewport.distance = distance;
if (isInstanceOf($camera, 'OrthographicCamera')) {
$viewport.width = width / $camera.zoom;
$viewport.height = height / $camera.zoom;
$viewport.factor = 1;
}
else if (isInstanceOf($camera, 'PerspectiveCamera')) {
const fov = ($camera.fov * Math.PI) / 180; // convert vertical fov to radians
const h = 2 * Math.tan(fov / 2) * distance; // visible height
const w = h * (width / height);
$viewport.width = w;
$viewport.height = h;
$viewport.factor = width / w;
}
return $viewport;
});
};
useTask(() => {
camera.current.getWorldPosition(position);
if (!position.equals(lastPosition)) {
const distance = position.distanceTo(origin);
updateViewport(size.current, camera.current, distance);
lastPosition.copy(position);
}
}, {
autoInvalidate: false,
stage: scheduler.createStage(Symbol('viewport-stage'), { before: renderStage })
});
watch([camera, size], ([$camera, $size]) => {
const distance = $camera.getWorldPosition(position).distanceTo(origin);
updateViewport($size, $camera, distance);
});
return viewport;
};