@pmndrs/uikit
Version:
Build performant 3D user interfaces with Three.js and yoga.
106 lines (105 loc) • 4.33 kB
JavaScript
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;
}
}
}
};
}