UNPKG

@pmndrs/handle

Version:

framework agnostic expandable handle implementation for threejs

128 lines (127 loc) 5.55 kB
import { ArrayCamera, Euler, Object3D, Quaternion, Vector3 } from 'three'; import { damp } from 'three/src/math/MathUtils.js'; import { createStore } from 'zustand/vanilla'; const zAxis = new Vector3(0, 0, 1); const yAxis = new Vector3(0, 1, 0); export function defaultScreenCameraApply(update, store) { store.setState(update); } const v1Helper = new Vector3(); const v2Helper = new Vector3(); const eHelper = new Euler(); const qHelper = new Quaternion(); function computeOriginToCameraOffset(target, cameraDistanceToOrigin, cameraRotation, yToUp) { target.copy(zAxis).applyEuler(cameraRotation).applyQuaternion(yToUp).multiplyScalar(cameraDistanceToOrigin); } function buildCameraPositionUpdate(update, x, y, z, origin, upToY) { v1Helper.set(x, y, z); v2Helper.set(...origin); v1Helper.sub(v2Helper); v1Helper.applyQuaternion(upToY); const distance = v1Helper.length(); v1Helper.divideScalar(distance); qHelper.setFromUnitVectors(zAxis, v1Helper); eHelper.setFromQuaternion(qHelper, 'YXZ'); update.distance = distance; update.pitch = eHelper.x; update.yaw = eHelper.y; } const vectorHelper = new Vector3(); const yToUpHelper = new Quaternion(); const upToYHelper = new Quaternion(); export function computeScreenCameraStoreTransformation(pitch, yaw, cameraDistanceToOrigin, origin, position, rotation, up = Object3D.DEFAULT_UP) { yToUpHelper.setFromUnitVectors(yAxis, up); eHelper.set(pitch, yaw, 0, 'YXZ'); if (position != null) { computeOriginToCameraOffset(position, cameraDistanceToOrigin, eHelper, yToUpHelper); const [x, y, z] = origin; position.x += x; position.y += y; position.z += z; } if (rotation != null) { rotation.setFromEuler(eHelper).premultiply(yToUpHelper); } } export function createScreenCameraStore({ distance = 5, origin = [0, 0, 0], pitch: rotationX = 0, yaw: rotationY = 0 } = {}, up = Object3D.DEFAULT_UP) { return createStore((set, get) => ({ distance, origin, pitch: rotationX, yaw: rotationY, activeHandle: undefined, getCameraTransformation(position, rotation) { const { pitch, distance, yaw, origin } = get(); computeScreenCameraStoreTransformation(pitch, yaw, distance, origin, position, rotation, up); }, setCameraPosition(x, y, z, keepOffsetToOrigin = false) { const update = {}; upToYHelper.setFromUnitVectors(up, yAxis); buildCameraPositionUpdate(update, x, y, z, get().origin, upToYHelper); if (keepOffsetToOrigin === true) { const state = get(); eHelper.set(state.pitch, state.yaw, 0, 'YXZ'); yToUpHelper.setFromUnitVectors(yAxis, up); computeOriginToCameraOffset(vectorHelper, state.distance, eHelper, yToUpHelper); vectorHelper.x -= x; vectorHelper.y -= y; vectorHelper.z -= z; update.origin = vectorHelper.toArray(); } set(update); }, setOriginPosition(x, y, z, keepOffsetToCamera = false) { const origin = [x, y, z]; const update = { origin, }; if (keepOffsetToCamera === false) { const { pitch, distance, origin: oldOrigin, yaw } = get(); computeScreenCameraStoreTransformation(pitch, yaw, distance, oldOrigin, vectorHelper, undefined, up); upToYHelper.setFromUnitVectors(up, yAxis); buildCameraPositionUpdate(update, vectorHelper.x, vectorHelper.y, vectorHelper.z, origin, upToYHelper); } set(update); }, })); } export function applyScreenCameraState(store, getTarget) { const fn = (state) => { const target = getTarget(); if (target == null) { return; } state.getCameraTransformation(target.position, target.quaternion); }; fn(store.getState()); return store.subscribe(fn); } export function applyDampedScreenCameraState(store, getTarget, getDamping, up = Object3D.DEFAULT_UP) { let { distance, yaw, origin: [originX, originY, originZ], pitch, } = store.getState(); return (deltaTime) => { const target = getTarget(); //if the target is a array camera (which is used for XR stuff, we dont apply) if (target == null || target instanceof ArrayCamera) { return; } let damping = getDamping(); if (damping === false) { return; } if (damping === true) { damping = 0.01; } const { distance: targetDistance, yaw: targetYaw, origin: [targetOriginX, targetOriginY, targetOriginZ], pitch: targetPitch, } = store.getState(); distance = damp(distance, targetDistance, damping, deltaTime); let angleDistance; while (Math.abs((angleDistance = targetYaw - yaw)) > Math.PI) { yaw += (angleDistance > 0 ? 2 : -2) * Math.PI; } yaw = damp(yaw, targetYaw, damping, deltaTime); pitch = damp(pitch, targetPitch, damping, deltaTime); originX = damp(originX, targetOriginX, damping, deltaTime); originY = damp(originY, targetOriginY, damping, deltaTime); originZ = damp(originZ, targetOriginZ, damping, deltaTime); computeScreenCameraStoreTransformation(pitch, yaw, distance, [originX, originY, originZ], target.position, target.quaternion, up); }; }