@pmndrs/handle
Version:
framework agnostic expandable handle implementation for threejs
128 lines (127 loc) • 5.55 kB
JavaScript
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);
};
}