UNPKG

@pmndrs/uikit

Version:

Build performant 3D user interfaces with Three.js and yoga.

106 lines (105 loc) 4.33 kB
import { Matrix4, Plane, Sphere, Vector2, Vector3 } from 'three'; import { abortableEffect, computeMatrixWorld, } from '../internals.js'; import { clamp } from 'three/src/math/MathUtils.js'; const planeHelper = new Plane(); const vectorHelper = new Vector3(); const sphereHelper = new Sphere(); const matrixHelper = new Matrix4(); export function makePanelSpherecast(rootObjectRef, globalSphereWithLocalScale, globalMatrixSignal, object) { return (sphere, intersects) => { const rootObjectMatrixWorld = rootObjectRef.current?.matrixWorld; if (rootObjectMatrixWorld == null) { return; } sphereHelper.copy(globalSphereWithLocalScale).applyMatrix4(rootObjectMatrixWorld); if (!sphereHelper.intersectsSphere(sphere) || !computeMatrixWorld(object.matrixWorld, object.matrix, rootObjectMatrixWorld, globalMatrixSignal)) { return; } vectorHelper.copy(sphere.center).applyMatrix4(matrixHelper.copy(object.matrixWorld).invert()); vectorHelper.x = clamp(vectorHelper.x, -0.5, 0.5); vectorHelper.y = clamp(vectorHelper.y, -0.5, 0.5); vectorHelper.z = 0; const uv = new Vector2(vectorHelper.x, vectorHelper.y); vectorHelper.applyMatrix4(object.matrixWorld); const distance = sphere.center.distanceTo(vectorHelper); if (distance > sphere.radius) { return; } intersects.push({ distance, object, point: vectorHelper.clone(), uv, normal: new Vector3(0, 0, 1), }); }; } export function makePanelRaycast(raycast, rootObjectRef, globalSphereWithLocalScale, globalMatrixSignal, object) { return (raycaster, intersects) => { const rootObjectMatrixWorld = rootObjectRef.current?.matrixWorld; if (rootObjectMatrixWorld == null) { return; } sphereHelper.copy(globalSphereWithLocalScale).applyMatrix4(rootObjectMatrixWorld); if (!raycaster.ray.intersectsSphere(sphereHelper) || !computeMatrixWorld(object.matrixWorld, object.matrix, rootObjectMatrixWorld, globalMatrixSignal)) { return; } raycast(raycaster, intersects); }; } export function isInteractionPanel(object) { return 'internals' in object; } export function setupBoundingSphere(target, pixelSize, globalMatrixSignal, size, abortSignal) { abortableEffect(() => { const sizeValue = size.value; const globalMatrix = globalMatrixSignal.value; if (sizeValue == null || globalMatrix == null) { return; } target.center.set(0, 0, 0); const [w, h] = sizeValue; const maxDiameter = Math.sqrt(w * w + h * h); target.radius = maxDiameter * 0.5 * pixelSize.value; target.applyMatrix4(globalMatrix); }, abortSignal); } /** * clips the sphere / raycast * also marks the mesh as a interaction panel */ export function makeClippedCast(mesh, fn, rootObject, clippingRect, orderInfoSignal, internals) { Object.assign(mesh, { internals }); return (raycaster, intersects) => { const oldLength = intersects.length; fn.call(mesh, raycaster, intersects); if (oldLength === intersects.length) { return; } const orderInfo = orderInfoSignal.peek(); if (orderInfo == null || rootObject.current == null) { return; } const clippingPlanes = clippingRect?.peek()?.planes; const rootMatrixWorld = rootObject.current.matrixWorld; outer: for (let i = intersects.length - 1; i >= oldLength; i--) { const intersection = intersects[i]; intersection.distance -= orderInfo.majorIndex * 0.01 + orderInfo.elementType * 0.001 + //1-10 orderInfo.minorIndex * 0.00001; //1-100 if (clippingPlanes == null) { continue; } for (let ii = 0; ii < 4; ii++) { planeHelper.copy(clippingPlanes[ii]).applyMatrix4(rootMatrixWorld); if (planeHelper.distanceToPoint(intersection.point) < 0) { intersects.splice(i, 1); continue outer; } } } }; }