@threlte/extras
Version:
Utilities, abstractions and plugins for your Threlte apps
60 lines (59 loc) • 2.31 kB
JavaScript
import { Camera, Vector3 } from 'three';
import { currentWritable, isInstanceOf, useTask, useThrelte } from '@threlte/core';
import { fromStore } from 'svelte/store';
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: cameraStore, size: sizeStore, 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;
});
};
const camera = fromStore(cameraStore);
const size = fromStore(sizeStore);
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 })
});
$effect.pre(() => {
const distance = camera.current.getWorldPosition(position).distanceTo(origin);
updateViewport(size.current, camera.current, distance);
});
return viewport;
};