UNPKG

@threlte/extras

Version:

Utilities, abstractions and plugins for your Threlte apps

57 lines (56 loc) 2.18 kB
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; };