File size: 4,264 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { UI_THEME as T } from "../../shared/ui-theme.mjs";
import * as THREE from "three";
import { idsInVolume } from "../../shared/selection.mjs";
function createVolumeSelection({ world, getScene, getMeshes, getHit, onCandidates }) {
  let box = null, helper = null, startMarker = null, depth = 4, ids = [];
  const localRay = (ray) => {
    world.updateMatrixWorld(true);
    return ray.clone().applyMatrix4(world.matrixWorld.clone().invert());
  };
  function clear() {
    if (helper) {
      world.remove(helper);
      helper.traverse((o) => {
        o.geometry?.dispose();
        o.material?.dispose();
      });
      helper = null;
    }
    if (startMarker) {
      world.remove(startMarker);
      startMarker.geometry.dispose();
      startMarker.material.dispose();
      startMarker = null;
    }
    box = null;
  }
  function paint() {
    const { origin, u, v, n, a, b } = box;
    helper.position.copy(origin).addScaledVector(u, (a[0] + b[0]) / 2).addScaledVector(v, (a[1] + b[1]) / 2);
    helper.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(u, v, n));
    const visibleMinimum = 0.025 / world.scale.x;
    helper.scale.set(Math.max(visibleMinimum, Math.abs(b[0] - a[0])), Math.max(visibleMinimum, Math.abs(b[1] - a[1])), depth);
    ids = idsInVolume(getScene(), { origin: origin.toArray(), u: u.toArray(), v: v.toArray(), n: n.toArray(), a, b, depth });
    onCandidates(ids);
  }
  function begin(raycaster) {
    clear();
    const ray = localRay(raycaster.ray), hit = getHit ? getHit(raycaster) : raycaster.intersectObjects(getMeshes().filter((m) => m.userData.definition.id !== "ground"), false)[0];
    const objects = getScene().objects, hitDefinition = hit?.object?.userData.definition || objects.find((o) => o.id === hit?.id);
    const distances = objects.filter((o) => o.editable === true || o.id !== "ground" && o.category !== "structure").map((o) => new THREE.Vector3().fromArray(o.position).sub(ray.origin).dot(ray.direction)).filter((d) => d > 0).sort((a, b) => a - b);
    const distance = hitDefinition ? new THREE.Vector3().fromArray(hitDefinition.position).sub(ray.origin).dot(ray.direction) : distances[Math.floor(distances.length / 2)] ?? 3 / world.scale.x;
    const origin = ray.at(Math.max(0.1, distance), new THREE.Vector3());
    const n = ray.direction.clone().negate(), up = Math.abs(n.y) > 0.95 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
    const u = new THREE.Vector3().crossVectors(up, n).normalize(), v = new THREE.Vector3().crossVectors(n, u).normalize();
    box = { origin, u, v, n, a: [0, 0], b: [0, 0], plane: new THREE.Plane().setFromNormalAndCoplanarPoint(n, origin) };
    helper = new THREE.Group();
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const fill = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ toneMapped: false, color: T.selected, transparent: true, opacity: 0.08, depthWrite: false, side: THREE.DoubleSide }));
    const edges = new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ toneMapped: false, color: T.accent, depthTest: false, transparent: true, opacity: 0.9 }));
    edges.renderOrder = 12;
    helper.add(fill, edges);
    world.add(helper);
    startMarker = new THREE.Mesh(new THREE.SphereGeometry(0.015 / world.scale.x, 10, 6), new THREE.MeshBasicMaterial({ toneMapped: false, color: T.accent, depthTest: false }));
    startMarker.position.copy(origin);
    startMarker.renderOrder = 13;
    world.add(startMarker);
    paint();
  }
  function update(raycaster) {
    if (!box) return;
    const point = localRay(raycaster.ray).intersectPlane(box.plane, new THREE.Vector3());
    if (!point) return;
    const delta = point.sub(box.origin);
    box.b = [delta.dot(box.u), delta.dot(box.v)];
    paint();
  }
  function finish() {
    if (!box) return null;
    const valid = Math.abs(box.b[0] - box.a[0]) > 0.08 && Math.abs(box.b[1] - box.a[1]) > 0.08;
    const result = valid ? [...ids] : null;
    clear();
    return result;
  }
  return { begin, update, finish, cancel: clear, isActive: () => !!box, setDepth(value) {
    depth = THREE.MathUtils.clamp(value, 0.5, 30);
    if (box) paint();
    return depth;
  }, getDepth: () => depth };
}
export {
  createVolumeSelection
};