EmboDi / src /client /rendering /flood-layer.mjs
xlt99's picture
Upload 180 files
970fc3e verified
Raw History Blame Contribute Delete
5.46 kB
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
};