File size: 5,461 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import * as THREE from "three";
import { regionFrame, framePoint, regionObjectId } from "../../shared/interaction-regions.mjs";
import { floodVertices, floodOutline } from "../../shared/flood-geometry.mjs";
function createFloodLayer(world, { getObjectPose = () => null } = {}) {
  const root = new THREE.Group(), areas = new THREE.Group(), flows = new THREE.Group();
  world.add(root);
  root.add(areas, flows);
  const sections = 24;
  let scene, entries = [];
  function dispose(group) {
    for (const child of [...group.children]) {
      child.geometry?.dispose();
      child.material?.dispose();
      group.remove(child);
    }
  }
  function triangles(points) {
    return THREE.ShapeUtils.triangulateShape(points.map((p) => new THREE.Vector2(...p)), []).flat();
  }
  return {
    dispose() {
      dispose(areas);
      dispose(flows);
      world.remove(root);
      entries = [];
    },
    sync(definition) {
      scene = definition;
      entries = [];
      dispose(areas);
      dispose(flows);
      for (const r of scene.regions || []) {
        const f = regionFrame(scene, r), points = r.points.map((p) => framePoint(f, p).map((n, i) => n + f.normal[i] * 0.012)), color = r.surface === "floor" ? 3198395 : 15562137;
        const line = new THREE.LineLoop(new THREE.BufferGeometry().setFromPoints(points.map((p) => new THREE.Vector3(...p))), new THREE.LineBasicMaterial({ color, toneMapped: false }));
        line.userData.regionId = r.id;
        areas.add(line);
        const geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.Float32BufferAttribute(points.flat(), 3));
        geometry.setIndex(triangles(r.points));
        geometry.computeVertexNormals();
        const fill = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.1, side: THREE.DoubleSide, depthWrite: false, toneMapped: false }));
        fill.userData.regionId = r.id;
        areas.add(fill);
      }
      for (const flood of scene.floods || []) for (const source of flood.sources) {
        const region = scene.regions.find((r) => r.id === source.regionId), outline = floodOutline(region), n = outline.length, indices = [], cap = triangles(outline);
        indices.push(...cap);
        for (let i = 0; i < sections; i++) for (let j = 0; j < n; j++) {
          const a = i * n + j, b = i * n + (j + 1) % n;
          indices.push(a, b, b + n, a, b + n, a + n);
        }
        indices.push(...cap.map((i) => i + sections * n));
        const geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(floodVertices(scene, flood, source, 0, sections), 3));
        geometry.setIndex(indices);
        geometry.computeVertexNormals();
        const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: flood.color, roughness: 0.24, metalness: 0.05, side: THREE.DoubleSide, transparent: true, opacity: 0.94, depthWrite: true }));
        mesh.visible = false;
        mesh.frustumCulled = false;
        flows.add(mesh);
        entries.push({ flood, source, mesh });
      }
    },
    frame(runtime, { showRegions = true, opacity = 1 } = {}) {
      areas.visible = showRegions;
      const regionPoses = /* @__PURE__ */ new Map();
      for (const r of scene.regions || []) if (r.surface === "object-surface" && !regionPoses.has(regionObjectId(r))) {
        const pose = getObjectPose(regionObjectId(r));
        if (pose) regionPoses.set(regionObjectId(r), pose);
      }
      const current = regionPoses.size ? { ...scene, regionPoses } : scene;
      for (const line of areas.children.filter((o) => o.userData.regionId)) {
        const region = scene.regions.find((r) => r.id === line.userData.regionId);
        if (region.surface === "object-surface" && regionPoses.has(regionObjectId(region))) {
          const frame = regionFrame(current, region), positions = line.geometry.getAttribute("position");
          positions.array.set(region.points.flatMap((p) => framePoint(frame, p).map((n, i) => n + frame.normal[i] * 0.012)));
          positions.needsUpdate = true;
          line.geometry.computeBoundingSphere();
        }
        if (region.surface !== "floor") continue;
        const f = scene.floods?.find((f2) => f2.trigger.regionId === region.id), state = runtime.states.find((s) => s.id === f?.id);
        line.material.color.set(state?.fired ? 14047344 : state?.inside ? 15248699 : 3198395);
      }
      for (const { flood, source, mesh } of entries) {
        const state = runtime.states.find((s) => s.id === flood.id);
        mesh.visible = !!(runtime.visible && state?.fired && state.time > 0 && state.time < flood.duration);
        if (!mesh.visible) continue;
        const position = mesh.geometry.getAttribute("position");
        position.array.set(floodVertices(current, flood, source, state.time, sections));
        position.needsUpdate = true;
        mesh.geometry.computeVertexNormals();
        mesh.material.opacity = opacity * 0.94 * Math.min(1, (flood.duration - state.time) / 0.6);
      }
    },
    snapshot() {
      return entries.map(({ flood, source, mesh }) => {
        mesh.geometry.computeBoundingBox();
        return { id: flood.id, regionId: source.regionId, visible: mesh.visible, vertices: mesh.geometry.getAttribute("position").count, bounds: [mesh.geometry.boundingBox.min.toArray(), mesh.geometry.boundingBox.max.toArray()] };
      });
    }
  };
}
export {
  createFloodLayer
};