EmboDi / src /client /ray-feedback.mjs
xlt99's picture
Upload 180 files
970fc3e verified
Raw History Blame Contribute Delete
3.15 kB
import { UI_THEME as T } from "../shared/ui-theme.mjs";
import * as THREE from "three";
function createRayFeedback(parent, controller = null) {
const group = new THREE.Group();
group.name = "interaction-ray-contact";
group.visible = false;
parent.add(group);
const material = (color) => new THREE.MeshBasicMaterial({ toneMapped: false, color, side: THREE.DoubleSide, depthTest: false, depthWrite: false, transparent: true, opacity: 0.95 });
const rim = new THREE.Mesh(new THREE.RingGeometry(0.64, 1.14, 32), material(T.panel));
const ring = new THREE.Mesh(new THREE.RingGeometry(0.76, 1, 32), material(T.selected));
const dot = new THREE.Mesh(new THREE.CircleGeometry(0.24, 16), material(T.selected));
for (const [i, mesh] of [rim, ring, dot].entries()) {
mesh.renderOrder = 80 + i;
group.add(mesh);
}
const laser = controller ? new THREE.Line(new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(0, 0, -1)]), new THREE.LineBasicMaterial({ toneMapped: false, color: T.selected, transparent: true, opacity: 0.65, depthWrite: false })) : null;
if (laser) {
laser.visible = false;
controller.add(laser);
}
const normal = new THREE.Vector3(), normalMatrix = new THREE.Matrix3(), z = new THREE.Vector3(0, 0, 1);
let contact = null;
function hide() {
group.visible = false;
if (laser) laser.visible = false;
contact = null;
}
function update(raycaster, { enabled = false, hit = null, color = T.selected, viewerPosition = raycaster.ray.origin } = {}) {
hide();
if (!enabled) return;
if (laser) {
laser.visible = true;
laser.scale.z = hit?.distance ?? 3;
laser.material.color.set(color);
}
if (hit) contact = { id: hit.id ?? hit.object?.userData.definition?.id ?? null, approximate: !!hit.approximate };
if (!hit?.point || hit.approximate) return;
normal.copy(hit.face?.normal || z);
if (hit.face && hit.object) normal.applyMatrix3(normalMatrix.getNormalMatrix(hit.object.matrixWorld)).normalize();
else normal.copy(raycaster.ray.direction).negate();
if (normal.dot(raycaster.ray.direction) > 0) normal.negate();
const distance = hit.point.distanceTo(viewerPosition), radius = THREE.MathUtils.clamp(distance * 7e-3, 8e-3, 0.045);
group.position.copy(hit.point).addScaledVector(normal, 15e-4);
group.quaternion.setFromUnitVectors(z, normal);
group.scale.setScalar(radius);
group.visible = true;
ring.material.color.set(color);
dot.material.color.set(color);
contact = { ...contact, point: hit.point.toArray(), normal: normal.toArray(), distance: hit.distance, radius };
}
return { update, hide, group, snapshot: () => ({ visible: group.visible, rayVisible: laser?.visible ?? false, rayLength: laser?.visible ? laser.scale.z : 0, ...contact }), dispose() {
hide();
group.removeFromParent();
for (const mesh of [rim, ring, dot]) {
mesh.geometry.dispose();
mesh.material.dispose();
}
if (laser) {
laser.removeFromParent();
laser.geometry.dispose();
laser.material.dispose();
}
} };
}
export {
createRayFeedback
};