File size: 3,146 Bytes
970fc3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
};