@pascal-app/viewer 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture/capture-runtime.js +1 -1
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +12 -4
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.d.ts +1 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +22 -3
- package/dist/components/viewer/glb-interactive.d.ts +25 -1
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +253 -86
- package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
- package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
- package/dist/components/viewer/glb-mechanisms.js +19 -0
- package/dist/components/viewer/glb-reference-nodes.js +1 -1
- package/dist/components/viewer/glb-replace-instances.js +1 -1
- package/dist/components/viewer/glb-scene.d.ts +45 -27
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +421 -508
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +13 -2
- package/dist/components/viewer/index.d.ts +24 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +111 -37
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +113 -22
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +6 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -1
- package/dist/components/viewer/perf-panel.js +1 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +104 -36
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +17 -3
- package/dist/components/viewer/render-error.d.ts +18 -0
- package/dist/components/viewer/render-error.d.ts.map +1 -0
- package/dist/components/viewer/render-error.js +29 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +1 -1
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +31 -11
- package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
- package/dist/components/viewer/scene-ground-replacement.js +15 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +25 -7
- package/dist/components/viewer/underground-ambiance.d.ts +9 -0
- package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
- package/dist/components/viewer/underground-ambiance.js +105 -0
- package/dist/components/viewer/viewer-camera.d.ts +4 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -2
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/index.d.ts +36 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -19
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +7 -1
- package/dist/lib/backdrop.d.ts +17 -0
- package/dist/lib/backdrop.d.ts.map +1 -1
- package/dist/lib/backdrop.js +18 -1
- package/dist/lib/construction-dust.d.ts +57 -0
- package/dist/lib/construction-dust.d.ts.map +1 -0
- package/dist/lib/construction-dust.js +187 -0
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/display-state.d.ts +25 -0
- package/dist/lib/display-state.d.ts.map +1 -0
- package/dist/lib/display-state.js +1 -0
- package/dist/lib/foundation-materials.d.ts +26 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -0
- package/dist/lib/foundation-materials.js +65 -0
- package/dist/lib/gltf-unlit.d.ts +10 -0
- package/dist/lib/gltf-unlit.d.ts.map +1 -0
- package/dist/lib/gltf-unlit.js +23 -0
- package/dist/lib/ground-window.d.ts +33 -0
- package/dist/lib/ground-window.d.ts.map +1 -0
- package/dist/lib/ground-window.js +114 -0
- package/dist/lib/hero-pose.d.ts +13 -3
- package/dist/lib/hero-pose.d.ts.map +1 -1
- package/dist/lib/hero-pose.js +72 -5
- package/dist/lib/ink-edges.d.ts +6 -3
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +17 -7
- package/dist/lib/key-light-override.d.ts +11 -0
- package/dist/lib/key-light-override.d.ts.map +1 -0
- package/dist/lib/key-light-override.js +14 -0
- package/dist/lib/ktx2-loader.d.ts +7 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +35 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layers.d.ts +11 -7
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +16 -6
- package/dist/lib/live-frame-hold.d.ts +14 -0
- package/dist/lib/live-frame-hold.d.ts.map +1 -0
- package/dist/lib/live-frame-hold.js +28 -0
- package/dist/lib/materials.d.ts +19 -7
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +65 -9
- package/dist/lib/merged-outline-node.d.ts +5 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +80 -2
- package/dist/lib/node-top-surface-height.d.ts +6 -0
- package/dist/lib/node-top-surface-height.d.ts.map +1 -0
- package/dist/lib/node-top-surface-height.js +74 -0
- package/dist/lib/perf-probe-state.d.ts +39 -0
- package/dist/lib/perf-probe-state.d.ts.map +1 -0
- package/dist/lib/perf-probe-state.js +17 -0
- package/dist/lib/pointer-events.d.ts +26 -2
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +99 -12
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/procedural-emission.d.ts +7 -0
- package/dist/lib/procedural-emission.d.ts.map +1 -0
- package/dist/lib/procedural-emission.js +54 -0
- package/dist/lib/reveal-candidates.d.ts +80 -0
- package/dist/lib/reveal-candidates.d.ts.map +1 -0
- package/dist/lib/reveal-candidates.js +524 -0
- package/dist/lib/reveal-ghost.d.ts +9 -0
- package/dist/lib/reveal-ghost.d.ts.map +1 -0
- package/dist/lib/reveal-ghost.js +62 -0
- package/dist/lib/reveal-pending.d.ts +12 -0
- package/dist/lib/reveal-pending.d.ts.map +1 -0
- package/dist/lib/reveal-pending.js +12 -0
- package/dist/lib/reveal-pose.d.ts +70 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -0
- package/dist/lib/reveal-pose.js +248 -0
- package/dist/lib/reveal-schedule.d.ts +208 -0
- package/dist/lib/reveal-schedule.d.ts.map +1 -0
- package/dist/lib/reveal-schedule.js +410 -0
- package/dist/lib/reveal-texture-scale.d.ts +5 -0
- package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
- package/dist/lib/reveal-texture-scale.js +113 -0
- package/dist/lib/reveal-windows.d.ts +23 -0
- package/dist/lib/reveal-windows.d.ts.map +1 -0
- package/dist/lib/reveal-windows.js +31 -0
- package/dist/lib/reveal-wipe.d.ts +71 -0
- package/dist/lib/reveal-wipe.d.ts.map +1 -0
- package/dist/lib/reveal-wipe.js +105 -0
- package/dist/lib/scene-pass-mrt.d.ts +12 -0
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
- package/dist/lib/scene-pass-mrt.js +23 -0
- package/dist/lib/scene-support-height.d.ts +4 -0
- package/dist/lib/scene-support-height.d.ts.map +1 -0
- package/dist/lib/scene-support-height.js +34 -0
- package/dist/lib/scene-themes.d.ts +30 -8
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +36 -0
- package/dist/lib/scene-visibility.d.ts +17 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +53 -3
- package/dist/lib/scripted-opening.d.ts +11 -0
- package/dist/lib/scripted-opening.d.ts.map +1 -0
- package/dist/lib/scripted-opening.js +20 -0
- package/dist/lib/site-surfaces.d.ts +32 -0
- package/dist/lib/site-surfaces.d.ts.map +1 -0
- package/dist/lib/site-surfaces.js +70 -0
- package/dist/lib/snapshot-pipeline.d.ts +34 -3
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +247 -117
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
- package/dist/lib/terrain-perimeter-fill.js +22 -14
- package/dist/lib/tsl-compat.d.ts +2 -2
- package/dist/lib/tsl-compat.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.d.ts +168 -0
- package/dist/lib/underground-ambiance.d.ts.map +1 -0
- package/dist/lib/underground-ambiance.js +334 -0
- package/dist/lib/zone-geometry.d.ts +11 -0
- package/dist/lib/zone-geometry.d.ts.map +1 -0
- package/dist/lib/zone-geometry.js +46 -0
- package/dist/store/use-item-light-pool.d.ts +14 -10
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +52 -26
- package/dist/store/use-viewer.d.ts +194 -26
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +164 -21
- package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +88 -6
- package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
- package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-dust.js +92 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
- package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
- package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
- package/dist/systems/construction-reveal/dust-material.js +17 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
- package/dist/systems/construction-reveal/ghost-dress.js +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +55 -17
- package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +18 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +33 -5
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +71 -11
- package/dist/systems/interactive/control-widget.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +52 -17
- package/dist/systems/interactive/procedural-controls.d.ts +13 -0
- package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
- package/dist/systems/interactive/procedural-controls.js +17 -0
- package/dist/systems/interactive/scripted-clips.d.ts +19 -0
- package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
- package/dist/systems/interactive/scripted-clips.js +134 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +9 -3
- package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
- package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
- package/dist/systems/item/object-cut-invalidation.js +68 -0
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +48 -53
- package/dist/systems/level/level-hold.d.ts +4 -0
- package/dist/systems/level/level-hold.d.ts.map +1 -0
- package/dist/systems/level/level-hold.js +14 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -6
- package/dist/systems/level/level-utils.d.ts +19 -6
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +68 -9
- package/dist/systems/level/shadow-only-levels.d.ts +9 -0
- package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
- package/dist/systems/level/shadow-only-levels.js +7 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
- package/dist/systems/roof/roof-materials.d.ts +16 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +64 -31
- package/dist/systems/roof/roof-system.d.ts +21 -4
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +374 -49
- package/dist/systems/slab/slab-system.d.ts +1 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +49 -19
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +133 -354
- package/dist/systems/wall/wall-cutout-cache.d.ts +39 -3
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +141 -19
- package/dist/systems/wall/wall-cutout.d.ts +6 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +42 -28
- package/dist/systems/wall/wall-finish-data.d.ts +22 -0
- package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
- package/dist/systems/wall/wall-finish-data.js +17 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.d.ts +9 -4
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +135 -153
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.d.ts +33 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +621 -159
- package/dist/systems/window/corner-frame.d.ts +10 -0
- package/dist/systems/window/corner-frame.d.ts.map +1 -0
- package/dist/systems/window/corner-frame.js +17 -0
- package/dist/systems/window/window-system.d.ts +1 -1
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -27
- package/package.json +10 -10
- package/dist/systems/fence/fence-system.d.ts +0 -22
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -357
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { isDerivedNode, nodeRegistry, } from '@pascal-app/core';
|
|
2
|
+
/** A roof piece lifts this share of its drop before it falls: a hair, 20 cm of 4 m. */
|
|
3
|
+
export const PART_LEAD = 0.05;
|
|
4
|
+
/** `simple` keeps the stagger with only the two slice-one motions. */
|
|
5
|
+
export function styleAt(style, at) {
|
|
6
|
+
// Framing lays a roof's covering over its frame in place; Full drops the whole roof.
|
|
7
|
+
if (at === 'framing')
|
|
8
|
+
return style === 'assemble' ? 'cover' : style;
|
|
9
|
+
if (at !== 'simple' || style === 'rise')
|
|
10
|
+
return style;
|
|
11
|
+
return 'scale';
|
|
12
|
+
}
|
|
13
|
+
/** Kinds that hold the others: staging one would unmount everything under it. */
|
|
14
|
+
export const CONTAINERS = new Set(['site', 'building', 'level']);
|
|
15
|
+
/**
|
|
16
|
+
* The reveal a new node of a kind that declares none comes in with, so nothing a build adds pops in
|
|
17
|
+
* at once: by where it stands (on an assembled node such as a roof, it falls onto it) and what it is. Null for the containers,
|
|
18
|
+
* the annotations and the kinds that draw nothing.
|
|
19
|
+
*/
|
|
20
|
+
function defaultReveal(node, nodes) {
|
|
21
|
+
const definition = nodeRegistry.get(node.type);
|
|
22
|
+
if (!definition || CONTAINERS.has(node.type))
|
|
23
|
+
return null;
|
|
24
|
+
if (definition.category === 'analysis' || !(definition.renderer || definition.geometry))
|
|
25
|
+
return null;
|
|
26
|
+
let parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
27
|
+
for (let guard = 0; parent && guard < 32; guard += 1) {
|
|
28
|
+
if (nodeRegistry.get(parent.type)?.capabilities?.reveal?.style === 'assemble')
|
|
29
|
+
return { phase: 'roof', style: 'drop', height: 2 };
|
|
30
|
+
if (parent.type === 'level')
|
|
31
|
+
break;
|
|
32
|
+
parent = parent.parentId ? nodes[parent.parentId] : undefined;
|
|
33
|
+
}
|
|
34
|
+
switch (definition.category) {
|
|
35
|
+
case 'structure':
|
|
36
|
+
return { phase: 'structure', style: 'scale' };
|
|
37
|
+
case 'utility':
|
|
38
|
+
return { phase: 'openings', style: 'scale' };
|
|
39
|
+
case 'site':
|
|
40
|
+
return { phase: 'foundation', style: 'scale' };
|
|
41
|
+
default:
|
|
42
|
+
return { phase: 'furnishing', style: 'drop', height: 0.3 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Whether `node` comes in with a new host the commit stages (it is drawn inside it), not on its own. */
|
|
46
|
+
function ridesWithHost(node, before, current) {
|
|
47
|
+
const parent = node.parentId ? current.nodes[node.parentId] : undefined;
|
|
48
|
+
return Boolean(parent && !CONTAINERS.has(parent.type) && !before.nodes[parent.id]);
|
|
49
|
+
}
|
|
50
|
+
/** What a node declares and where it stands: its candidate, or null for a kind that declares no reveal. */
|
|
51
|
+
function candidateOf(node, nodes, at, source, seq, fallback = false) {
|
|
52
|
+
const reveal = nodeRegistry.get(node.type)?.capabilities?.reveal ??
|
|
53
|
+
(fallback ? defaultReveal(node, nodes) : null);
|
|
54
|
+
if (!reveal)
|
|
55
|
+
return null;
|
|
56
|
+
let depth = 0;
|
|
57
|
+
let levelId = null;
|
|
58
|
+
let levelIndex = Number.MIN_SAFE_INTEGER;
|
|
59
|
+
let parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
60
|
+
while (parent && depth < 32) {
|
|
61
|
+
depth += 1;
|
|
62
|
+
if (!levelId && parent.type === 'level') {
|
|
63
|
+
levelId = parent.id;
|
|
64
|
+
levelIndex = parent.level;
|
|
65
|
+
}
|
|
66
|
+
parent = parent.parentId ? nodes[parent.parentId] : undefined;
|
|
67
|
+
}
|
|
68
|
+
const style = styleAt(reveal.style, at);
|
|
69
|
+
const height = 'height' in reveal && style === reveal.style ? reveal.height : 0;
|
|
70
|
+
return {
|
|
71
|
+
id: node.id,
|
|
72
|
+
phase: reveal.phase,
|
|
73
|
+
levelId,
|
|
74
|
+
levelIndex,
|
|
75
|
+
source,
|
|
76
|
+
style,
|
|
77
|
+
height,
|
|
78
|
+
depth,
|
|
79
|
+
seq,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* What `commit` stages at `at`: each node it creates whose kind declares a
|
|
84
|
+
* reveal, and the new parts (children declaring none) of each one that
|
|
85
|
+
* assembles. A new node of a kind that declares none comes in with a default
|
|
86
|
+
* reveal when what holds it was already there (under a new host, it comes in
|
|
87
|
+
* with the host). `seq` counts on from `firstSeq` in arrival order. Each
|
|
88
|
+
* candidate carries its room and its place in it (`layoutRevealCandidates`).
|
|
89
|
+
*/
|
|
90
|
+
export function commitRevealCandidates({ before, current, author }, at, firstSeq = 0) {
|
|
91
|
+
const candidates = [];
|
|
92
|
+
let seq = firstSeq;
|
|
93
|
+
for (const id in current.nodes) {
|
|
94
|
+
if (before.nodes[id])
|
|
95
|
+
continue;
|
|
96
|
+
const node = current.nodes[id];
|
|
97
|
+
const candidate = node &&
|
|
98
|
+
candidateOf(node, current.nodes, at, author ?? null, seq, !ridesWithHost(node, before, current));
|
|
99
|
+
if (!(node && candidate))
|
|
100
|
+
continue;
|
|
101
|
+
seq += 1;
|
|
102
|
+
candidates.push(candidate);
|
|
103
|
+
if (candidate.style !== 'assemble')
|
|
104
|
+
continue;
|
|
105
|
+
// Its new children that declare no reveal of their own are its parts: each drops in.
|
|
106
|
+
for (const partId of node.children ?? []) {
|
|
107
|
+
if (typeof partId !== 'string' || before.nodes[partId])
|
|
108
|
+
continue;
|
|
109
|
+
const part = current.nodes[partId];
|
|
110
|
+
if (!part || nodeRegistry.get(part.type)?.capabilities?.reveal)
|
|
111
|
+
continue;
|
|
112
|
+
candidates.push({
|
|
113
|
+
id: partId,
|
|
114
|
+
phase: candidate.phase,
|
|
115
|
+
levelId: candidate.levelId,
|
|
116
|
+
levelIndex: candidate.levelIndex,
|
|
117
|
+
source: candidate.source,
|
|
118
|
+
style: 'drop',
|
|
119
|
+
height: candidate.height,
|
|
120
|
+
depth: candidate.depth + 1,
|
|
121
|
+
seq: seq++,
|
|
122
|
+
host: id,
|
|
123
|
+
lead: PART_LEAD,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
layoutRevealCandidates(candidates, current.nodes);
|
|
128
|
+
return candidates;
|
|
129
|
+
}
|
|
130
|
+
const isPt = (value) => Array.isArray(value) &&
|
|
131
|
+
value.length === 2 &&
|
|
132
|
+
typeof value[0] === 'number' &&
|
|
133
|
+
typeof value[1] === 'number';
|
|
134
|
+
function polygonOf(node) {
|
|
135
|
+
const polygon = node.polygon;
|
|
136
|
+
return Array.isArray(polygon) && polygon.length >= 3 && polygon.every(isPt) ? polygon : null;
|
|
137
|
+
}
|
|
138
|
+
/** The room a node outlines, as its kind declares it (`capabilities.revealLayout.room`). */
|
|
139
|
+
function roomOf(node) {
|
|
140
|
+
const polygon = nodeRegistry.get(node.type)?.capabilities?.revealLayout?.room?.(node);
|
|
141
|
+
return polygon && polygon.length >= 3 && polygon.every(isPt) ? polygon : null;
|
|
142
|
+
}
|
|
143
|
+
/** The line a node bounds rooms along, as its kind declares it (`capabilities.revealLayout.line`). */
|
|
144
|
+
function lineOf(node) {
|
|
145
|
+
const line = nodeRegistry.get(node.type)?.capabilities?.revealLayout?.line?.(node);
|
|
146
|
+
return line && isPt(line[0]) && isPt(line[1]) ? line : null;
|
|
147
|
+
}
|
|
148
|
+
const middleOf = ([start, end]) => [
|
|
149
|
+
(start[0] + end[0]) / 2,
|
|
150
|
+
(start[1] + end[1]) / 2,
|
|
151
|
+
];
|
|
152
|
+
function centroidOf(polygon) {
|
|
153
|
+
let x = 0;
|
|
154
|
+
let z = 0;
|
|
155
|
+
for (const [px, pz] of polygon) {
|
|
156
|
+
x += px;
|
|
157
|
+
z += pz;
|
|
158
|
+
}
|
|
159
|
+
return [x / polygon.length, z / polygon.length];
|
|
160
|
+
}
|
|
161
|
+
function areaOf(polygon) {
|
|
162
|
+
let twice = 0;
|
|
163
|
+
for (let index = 0; index < polygon.length; index += 1) {
|
|
164
|
+
const [x0, z0] = polygon[index];
|
|
165
|
+
const [x1, z1] = polygon[(index + 1) % polygon.length];
|
|
166
|
+
twice += x0 * z1 - x1 * z0;
|
|
167
|
+
}
|
|
168
|
+
return Math.abs(twice) / 2;
|
|
169
|
+
}
|
|
170
|
+
function inside([x, z], polygon) {
|
|
171
|
+
let within = false;
|
|
172
|
+
for (let index = 0, last = polygon.length - 1; index < polygon.length; last = index++) {
|
|
173
|
+
const [xi, zi] = polygon[index];
|
|
174
|
+
const [xj, zj] = polygon[last];
|
|
175
|
+
if (zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi)
|
|
176
|
+
within = !within;
|
|
177
|
+
}
|
|
178
|
+
return within;
|
|
179
|
+
}
|
|
180
|
+
/** Where a node stands in its level's plan: a line's middle, a polygon's centre, a placed node's spot. */
|
|
181
|
+
function planPointOf(node) {
|
|
182
|
+
const line = lineOf(node);
|
|
183
|
+
if (line)
|
|
184
|
+
return middleOf(line);
|
|
185
|
+
const { start, end, position } = node;
|
|
186
|
+
if (isPt(start) && isPt(end))
|
|
187
|
+
return middleOf([start, end]);
|
|
188
|
+
const polygon = polygonOf(node);
|
|
189
|
+
if (polygon)
|
|
190
|
+
return centroidOf(polygon);
|
|
191
|
+
if (Array.isArray(position) && typeof position[0] === 'number' && typeof position[2] === 'number')
|
|
192
|
+
return [position[0], position[2]];
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
/** The node right under the level that `id` stands in (itself, or the host it hangs on). */
|
|
196
|
+
function standingOf(id, nodes) {
|
|
197
|
+
let node = nodes[id];
|
|
198
|
+
for (let guard = 0; node && guard < 32; guard += 1) {
|
|
199
|
+
const parent = node.parentId ? nodes[node.parentId] : undefined;
|
|
200
|
+
if (!parent || parent.type === 'level')
|
|
201
|
+
return node;
|
|
202
|
+
node = parent;
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/** Walls a run holds before the next run of the same outline starts beside it. */
|
|
207
|
+
const WALLS_PER_RUN = 6;
|
|
208
|
+
/** Wall ends closer than this (m) meet. */
|
|
209
|
+
const MEETS_M = 0.05;
|
|
210
|
+
/**
|
|
211
|
+
* Gives each candidate its room for the lazy schedule: the room it stands in (a node on a line, the
|
|
212
|
+
* room on either side of its middle), else, for a node on a line, a run of the lines it meets end to
|
|
213
|
+
* end (`capabilities.revealLayout`); its room's
|
|
214
|
+
* sweep across the floor; and its place in the room (around the room's centre, or along the run).
|
|
215
|
+
* What stands in no room keeps none: it belongs to the floor.
|
|
216
|
+
*/
|
|
217
|
+
export function layoutRevealCandidates(candidates, nodes) {
|
|
218
|
+
const zonesByLevel = new Map();
|
|
219
|
+
const zonesOf = (levelId) => {
|
|
220
|
+
let zones = zonesByLevel.get(levelId);
|
|
221
|
+
if (zones)
|
|
222
|
+
return zones;
|
|
223
|
+
zones = [];
|
|
224
|
+
const level = nodes[levelId];
|
|
225
|
+
for (const childId of level?.children ?? []) {
|
|
226
|
+
const child = typeof childId === 'string' ? nodes[childId] : undefined;
|
|
227
|
+
const polygon = child ? roomOf(child) : null;
|
|
228
|
+
if (child && polygon)
|
|
229
|
+
zones.push({ id: child.id, polygon, centre: centroidOf(polygon) });
|
|
230
|
+
}
|
|
231
|
+
zones.sort((left, right) => areaOf(left.polygon) - areaOf(right.polygon));
|
|
232
|
+
zonesByLevel.set(levelId, zones);
|
|
233
|
+
return zones;
|
|
234
|
+
};
|
|
235
|
+
const turnAround = (centre, point) => {
|
|
236
|
+
const angle = Math.atan2(point[1] - centre[1], point[0] - centre[0]);
|
|
237
|
+
return angle < 0 ? angle + 2 * Math.PI : angle;
|
|
238
|
+
};
|
|
239
|
+
// Lines in no room, by level: they make runs; what they host follows them.
|
|
240
|
+
const loose = new Map();
|
|
241
|
+
const onLooseWalls = [];
|
|
242
|
+
for (const candidate of candidates) {
|
|
243
|
+
if (!candidate.levelId)
|
|
244
|
+
continue;
|
|
245
|
+
const standing = standingOf(candidate.host ?? candidate.id, nodes);
|
|
246
|
+
const point = standing && planPointOf(standing);
|
|
247
|
+
if (!(standing && point))
|
|
248
|
+
continue;
|
|
249
|
+
const zones = zonesOf(candidate.levelId);
|
|
250
|
+
const line = lineOf(standing);
|
|
251
|
+
let probes = [point];
|
|
252
|
+
if (line) {
|
|
253
|
+
const [start, end] = line;
|
|
254
|
+
const length = Math.hypot(end[0] - start[0], end[1] - start[1]) || 1;
|
|
255
|
+
const nx = -(end[1] - start[1]) / length;
|
|
256
|
+
const nz = (end[0] - start[0]) / length;
|
|
257
|
+
probes = [
|
|
258
|
+
[point[0] + nx * 0.3, point[1] + nz * 0.3],
|
|
259
|
+
[point[0] - nx * 0.3, point[1] - nz * 0.3],
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
const zone = zones.find((candidateZone) => probes.some((probe) => inside(probe, candidateZone.polygon)));
|
|
263
|
+
if (zone) {
|
|
264
|
+
candidate.cluster = zone.id;
|
|
265
|
+
candidate.sweep = zone.centre[0] + zone.centre[1];
|
|
266
|
+
candidate.order = turnAround(zone.centre, point);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (line && standing.id === candidate.id) {
|
|
270
|
+
const list = loose.get(candidate.levelId) ?? [];
|
|
271
|
+
list.push({ candidate, line });
|
|
272
|
+
loose.set(candidate.levelId, list);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (line)
|
|
276
|
+
onLooseWalls.push({ candidate, wallId: standing.id, point });
|
|
277
|
+
candidate.order = point[0] + point[1];
|
|
278
|
+
}
|
|
279
|
+
for (const [levelId, walls] of loose)
|
|
280
|
+
layoutRuns(levelId, walls);
|
|
281
|
+
if (onLooseWalls.length === 0)
|
|
282
|
+
return;
|
|
283
|
+
const byId = new Map(candidates.map((candidate) => [candidate.id, candidate]));
|
|
284
|
+
for (const { candidate, wallId } of onLooseWalls) {
|
|
285
|
+
const wall = byId.get(wallId);
|
|
286
|
+
if (!wall?.cluster)
|
|
287
|
+
continue;
|
|
288
|
+
candidate.cluster = wall.cluster;
|
|
289
|
+
candidate.sweep = wall.sweep;
|
|
290
|
+
candidate.order = wall.order;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Lines in no room: the lines that meet end to end make an outline, cut in runs along it. */
|
|
294
|
+
function layoutRuns(levelId, walls) {
|
|
295
|
+
const key = ([x, z]) => `${Math.round(x / MEETS_M)}:${Math.round(z / MEETS_M)}`;
|
|
296
|
+
const byEnd = new Map();
|
|
297
|
+
const ends = walls.map(({ line: [start, end] }) => [key(start), key(end)]);
|
|
298
|
+
ends.forEach((pair, index) => {
|
|
299
|
+
for (const end of pair)
|
|
300
|
+
byEnd.set(end, [...(byEnd.get(end) ?? []), index]);
|
|
301
|
+
});
|
|
302
|
+
const sweepOf = (index) => {
|
|
303
|
+
const [x, z] = middleOf(walls[index].line);
|
|
304
|
+
return x + z;
|
|
305
|
+
};
|
|
306
|
+
const meeting = (index) => ends[index].flatMap((end) => byEnd.get(end) ?? []).filter((next) => next !== index);
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
const starts = walls
|
|
309
|
+
.map((_, index) => index)
|
|
310
|
+
.sort((left, right) => sweepOf(left) - sweepOf(right));
|
|
311
|
+
let outline = 0;
|
|
312
|
+
for (const first of starts) {
|
|
313
|
+
if (seen.has(first))
|
|
314
|
+
continue;
|
|
315
|
+
// An open chain is walked from one of its free ends, a closed outline from its first wall in
|
|
316
|
+
// the sweep: wall to the wall it meets, so each run is one stretch of the outline.
|
|
317
|
+
const component = new Set([first]);
|
|
318
|
+
for (const index of component)
|
|
319
|
+
for (const next of meeting(index))
|
|
320
|
+
component.add(next);
|
|
321
|
+
const begin = [...component]
|
|
322
|
+
.filter((index) => ends[index].some((end) => byEnd.get(end)?.length === 1))
|
|
323
|
+
.sort((left, right) => sweepOf(left) - sweepOf(right))[0] ?? first;
|
|
324
|
+
const order = [];
|
|
325
|
+
const stack = [begin];
|
|
326
|
+
while (stack.length > 0) {
|
|
327
|
+
const index = stack.pop();
|
|
328
|
+
if (seen.has(index))
|
|
329
|
+
continue;
|
|
330
|
+
seen.add(index);
|
|
331
|
+
order.push(index);
|
|
332
|
+
stack.push(...meeting(index).reverse());
|
|
333
|
+
}
|
|
334
|
+
order.forEach((index, rank) => {
|
|
335
|
+
const run = Math.floor(rank / WALLS_PER_RUN);
|
|
336
|
+
const candidate = walls[index].candidate;
|
|
337
|
+
candidate.cluster = `${levelId}:run:${outline}:${run}`;
|
|
338
|
+
candidate.sweep = sweepOf(order[run * WALLS_PER_RUN]);
|
|
339
|
+
candidate.order = rank;
|
|
340
|
+
});
|
|
341
|
+
outline += 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** Moved less than this (metres, radians) and a piece is where it was. */
|
|
345
|
+
const PLACED_EPSILON = 1e-4;
|
|
346
|
+
function placedDifferently(was, now) {
|
|
347
|
+
if (typeof was === 'number' && typeof now === 'number')
|
|
348
|
+
return Math.abs(was - now) > PLACED_EPSILON;
|
|
349
|
+
if (Array.isArray(was) && Array.isArray(now)) {
|
|
350
|
+
return (was.length !== now.length || was.some((value, index) => placedDifferently(value, now[index])));
|
|
351
|
+
}
|
|
352
|
+
return was !== now;
|
|
353
|
+
}
|
|
354
|
+
/** Moved less than this (metres, radians) and a piece does not glide: it is where it was. */
|
|
355
|
+
const GLIDES_FROM = 0.02;
|
|
356
|
+
function placementOf(node) {
|
|
357
|
+
const { position, rotation } = node;
|
|
358
|
+
if (!(Array.isArray(position) && position.length === 3))
|
|
359
|
+
return null;
|
|
360
|
+
if (!position.every((value) => typeof value === 'number'))
|
|
361
|
+
return null;
|
|
362
|
+
const yaw = Array.isArray(rotation) ? rotation[1] : rotation;
|
|
363
|
+
return {
|
|
364
|
+
position: position,
|
|
365
|
+
yaw: typeof yaw === 'number' ? yaw : 0,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/** The place a piece glides from: standing in the same level both times, and moved enough to see. */
|
|
369
|
+
function glidesFrom(was, node, nodes) {
|
|
370
|
+
if (was.parentId !== node.parentId || nodes[node.parentId]?.type !== 'level')
|
|
371
|
+
return null;
|
|
372
|
+
const from = placementOf(was);
|
|
373
|
+
const to = placementOf(node);
|
|
374
|
+
if (!(from && to))
|
|
375
|
+
return null;
|
|
376
|
+
const travelled = Math.hypot(from.position[0] - to.position[0], from.position[1] - to.position[1], from.position[2] - to.position[2]);
|
|
377
|
+
return travelled > GLIDES_FROM || Math.abs(from.yaw - to.yaw) > GLIDES_FROM ? from : null;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* What `commit` moves: each piece of furniture it finds already there that is placed differently
|
|
381
|
+
* (another position, turn or parent). Furniture only, for now: walls and openings move with the
|
|
382
|
+
* rooms and walls that hold them.
|
|
383
|
+
*/
|
|
384
|
+
export function commitChangeCandidates({ before, current, author }, at, firstSeq = 0) {
|
|
385
|
+
const candidates = [];
|
|
386
|
+
let seq = firstSeq;
|
|
387
|
+
for (const id in current.nodes) {
|
|
388
|
+
const was = before.nodes[id];
|
|
389
|
+
const node = current.nodes[id];
|
|
390
|
+
if (!(was && node) || was === node)
|
|
391
|
+
continue;
|
|
392
|
+
if (nodeRegistry.get(node.type)?.capabilities?.reveal?.phase !== 'furnishing')
|
|
393
|
+
continue;
|
|
394
|
+
const placed = (value, key) => value[key];
|
|
395
|
+
const moved = node.parentId !== was.parentId ||
|
|
396
|
+
['position', 'rotation'].some((key) => placedDifferently(placed(was, key), placed(node, key)));
|
|
397
|
+
if (!moved)
|
|
398
|
+
continue;
|
|
399
|
+
const candidate = candidateOf(node, current.nodes, at, author ?? null, seq);
|
|
400
|
+
if (!candidate)
|
|
401
|
+
continue;
|
|
402
|
+
seq += 1;
|
|
403
|
+
candidates.push({ ...candidate, from: glidesFrom(was, node, current.nodes) });
|
|
404
|
+
}
|
|
405
|
+
return candidates;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* What taking `ids` back reveals: each of them, and everything under them (a wall's openings, a
|
|
409
|
+
* room's furniture), as the build would have staged it: by what its kind declares, or the default
|
|
410
|
+
* reveal a kind that declares none came in with. What stands in a host that goes (a segment of a
|
|
411
|
+
* stair, a part of a pool) goes with it; an assembled node (a roof) goes whole, not part by part.
|
|
412
|
+
*/
|
|
413
|
+
export function nodeRevealCandidates(ids, nodes, at) {
|
|
414
|
+
const asked = new Set(ids);
|
|
415
|
+
const seen = new Set();
|
|
416
|
+
const candidates = [];
|
|
417
|
+
const visit = (id, guard = 0) => {
|
|
418
|
+
const node = nodes[id];
|
|
419
|
+
if (!node || seen.has(id) || guard > 64)
|
|
420
|
+
return;
|
|
421
|
+
seen.add(id);
|
|
422
|
+
const host = node.parentId ? nodes[node.parentId] : undefined;
|
|
423
|
+
const goesWithHost = Boolean(host && !CONTAINERS.has(host.type) && (asked.has(host.id) || seen.has(host.id)));
|
|
424
|
+
const candidate = candidateOf(node, nodes, at, null, candidates.length, !goesWithHost);
|
|
425
|
+
if (candidate)
|
|
426
|
+
candidates.push(candidate);
|
|
427
|
+
// An assembled or covered node's parts are not staged on their own.
|
|
428
|
+
if (candidate?.style === 'assemble' || candidate?.style === 'cover')
|
|
429
|
+
return;
|
|
430
|
+
for (const childId of node.children ?? []) {
|
|
431
|
+
if (typeof childId === 'string')
|
|
432
|
+
visit(childId, guard + 1);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
for (const id of asked)
|
|
436
|
+
visit(id);
|
|
437
|
+
return candidates;
|
|
438
|
+
}
|
|
439
|
+
/** How an edited node plays again: a quick re-rise or re-scale, never a fall from a height. */
|
|
440
|
+
const EDIT_STYLE = {
|
|
441
|
+
rise: 'rise',
|
|
442
|
+
scale: 'scale',
|
|
443
|
+
settle: 'settle',
|
|
444
|
+
drop: 'scale',
|
|
445
|
+
cut: 'scale',
|
|
446
|
+
assemble: 'scale',
|
|
447
|
+
cover: 'scale',
|
|
448
|
+
};
|
|
449
|
+
const sameValue = (was, now) => Object.is(was, now) || JSON.stringify(was) === JSON.stringify(now);
|
|
450
|
+
/**
|
|
451
|
+
* What `commit` edits: each node already there whose kind lists `capabilities.revealOnEdit` and one of
|
|
452
|
+
* those fields (the ones that make its picture) changed. A change to any other field stays instant, and
|
|
453
|
+
* so does what the structure reconciler derived from an edit (`derivedWrites`: the plates and ceilings it
|
|
454
|
+
* owns, the fields it wrote on a node): a wall moved does not make its room's floor rise again.
|
|
455
|
+
*/
|
|
456
|
+
export function commitEditCandidates({ before, current, author, derivedWrites }, at, firstSeq = 0) {
|
|
457
|
+
const candidates = [];
|
|
458
|
+
let seq = firstSeq;
|
|
459
|
+
for (const id in current.nodes) {
|
|
460
|
+
const was = before.nodes[id];
|
|
461
|
+
const node = current.nodes[id];
|
|
462
|
+
const derived = derivedWrites?.get(id);
|
|
463
|
+
if (!(was && node) || was === node || derived === 'all' || isDerivedNode(node))
|
|
464
|
+
continue;
|
|
465
|
+
const fields = nodeRegistry.get(node.type)?.capabilities?.revealOnEdit;
|
|
466
|
+
const edited = fields?.some((field) => !derived?.has(field) &&
|
|
467
|
+
!sameValue(was[field], node[field]));
|
|
468
|
+
if (!edited)
|
|
469
|
+
continue;
|
|
470
|
+
const candidate = candidateOf(node, current.nodes, at, author ?? null, seq, true);
|
|
471
|
+
if (!candidate)
|
|
472
|
+
continue;
|
|
473
|
+
seq += 1;
|
|
474
|
+
const style = EDIT_STYLE[candidate.style];
|
|
475
|
+
candidates.push({
|
|
476
|
+
...candidate,
|
|
477
|
+
style,
|
|
478
|
+
height: style === candidate.style ? candidate.height : 0,
|
|
479
|
+
from: null,
|
|
480
|
+
edited: true,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return candidates;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* What `commit` deletes that the picture can play out: each node that was there and is gone whose kind
|
|
487
|
+
* stages a reveal, as it was staged when built (a node standing in a host that goes with it goes with
|
|
488
|
+
* its host). Derived construction the reconciler retires is not a deletion the person or the agent made.
|
|
489
|
+
*/
|
|
490
|
+
export function commitDeleteCandidates({ before, current, author, derivedWrites }, at, firstSeq = 0) {
|
|
491
|
+
const candidates = [];
|
|
492
|
+
let seq = firstSeq;
|
|
493
|
+
// A plate or a ceiling the reconciler owns goes with its room: it plays when the commit takes a
|
|
494
|
+
// wall or a room off its level, and not when the reconciler merely derives it anew.
|
|
495
|
+
const goingLevels = new Set();
|
|
496
|
+
for (const id in before.nodes) {
|
|
497
|
+
const node = before.nodes[id];
|
|
498
|
+
if (current.nodes[id] || !node?.parentId)
|
|
499
|
+
continue;
|
|
500
|
+
if (nodeRegistry.get(node.type)?.capabilities?.revealLayout)
|
|
501
|
+
goingLevels.add(node.parentId);
|
|
502
|
+
}
|
|
503
|
+
for (const id in before.nodes) {
|
|
504
|
+
if (current.nodes[id])
|
|
505
|
+
continue;
|
|
506
|
+
const node = before.nodes[id];
|
|
507
|
+
if (!node)
|
|
508
|
+
continue;
|
|
509
|
+
if (isDerivedNode(node)) {
|
|
510
|
+
if (!(node.parentId && goingLevels.has(node.parentId)))
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
else if (derivedWrites?.get(id) === 'all')
|
|
514
|
+
continue;
|
|
515
|
+
const host = node.parentId ? before.nodes[node.parentId] : undefined;
|
|
516
|
+
const goesWithHost = Boolean(host && !CONTAINERS.has(host.type) && !current.nodes[host.id]);
|
|
517
|
+
const candidate = candidateOf(node, before.nodes, at, author ?? null, seq, !goesWithHost);
|
|
518
|
+
if (!candidate)
|
|
519
|
+
continue;
|
|
520
|
+
seq += 1;
|
|
521
|
+
candidates.push(candidate);
|
|
522
|
+
}
|
|
523
|
+
return candidates;
|
|
524
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Fades everything drawn under `root` to `opacity`. Idempotent: a frame later it only moves the
|
|
4
|
+
* opacity, and a renderer that put its own materials back is dressed again. At 1 it is whole.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setGhost(root: Object3D, opacity: number): void;
|
|
7
|
+
/** Gives every mesh under `root` its own materials and its shadow back. */
|
|
8
|
+
export declare function clearGhost(root: Object3D): void;
|
|
9
|
+
//# sourceMappingURL=reveal-ghost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal-ghost.d.ts","sourceRoot":"","sources":["../../src/lib/reveal-ghost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,QAAQ,EAAE,MAAM,OAAO,CAAA;AA+BrD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA8B9D;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAU/C"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const worn = new WeakMap();
|
|
2
|
+
/** Under this a ghost casts no shadow and writes no depth: the rooms below are lit and seen. */
|
|
3
|
+
const FAINT = 0.5;
|
|
4
|
+
const cloneAll = (material) => Array.isArray(material) ? material.map((each) => each.clone()) : material.clone();
|
|
5
|
+
const eachOf = (material) => Array.isArray(material) ? material : [material];
|
|
6
|
+
function isMesh(object) {
|
|
7
|
+
return object.isMesh === true && Boolean(object.material);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fades everything drawn under `root` to `opacity`. Idempotent: a frame later it only moves the
|
|
11
|
+
* opacity, and a renderer that put its own materials back is dressed again. At 1 it is whole.
|
|
12
|
+
*/
|
|
13
|
+
export function setGhost(root, opacity) {
|
|
14
|
+
if (opacity >= 0.999) {
|
|
15
|
+
clearGhost(root);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
root.traverse((child) => {
|
|
19
|
+
if (!isMesh(child))
|
|
20
|
+
return;
|
|
21
|
+
let entry = worn.get(child);
|
|
22
|
+
if (entry && child.material !== entry.ghost) {
|
|
23
|
+
// Its renderer set the real materials again: they are the originals now.
|
|
24
|
+
for (const material of eachOf(entry.ghost))
|
|
25
|
+
material.dispose();
|
|
26
|
+
entry = { original: child.material, ghost: cloneAll(child.material), cast: entry.cast };
|
|
27
|
+
worn.set(child, entry);
|
|
28
|
+
child.material = entry.ghost;
|
|
29
|
+
}
|
|
30
|
+
else if (!entry) {
|
|
31
|
+
entry = { original: child.material, ghost: cloneAll(child.material), cast: child.castShadow };
|
|
32
|
+
worn.set(child, entry);
|
|
33
|
+
child.material = entry.ghost;
|
|
34
|
+
}
|
|
35
|
+
for (const material of eachOf(entry.ghost)) {
|
|
36
|
+
// A material that turns transparent compiles anew once: the flag and the write order change.
|
|
37
|
+
if (!material.transparent) {
|
|
38
|
+
material.transparent = true;
|
|
39
|
+
material.needsUpdate = true;
|
|
40
|
+
}
|
|
41
|
+
material.opacity = opacity;
|
|
42
|
+
material.depthWrite = opacity > FAINT;
|
|
43
|
+
}
|
|
44
|
+
child.castShadow = opacity > FAINT && entry.cast;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Gives every mesh under `root` its own materials and its shadow back. */
|
|
48
|
+
export function clearGhost(root) {
|
|
49
|
+
root.traverse((child) => {
|
|
50
|
+
if (!isMesh(child))
|
|
51
|
+
return;
|
|
52
|
+
const entry = worn.get(child);
|
|
53
|
+
if (!entry)
|
|
54
|
+
return;
|
|
55
|
+
if (child.material === entry.ghost)
|
|
56
|
+
child.material = entry.original;
|
|
57
|
+
for (const material of eachOf(entry.ghost))
|
|
58
|
+
material.dispose();
|
|
59
|
+
child.castShadow = entry.cast;
|
|
60
|
+
worn.delete(child);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RevealCandidate } from './reveal-schedule';
|
|
2
|
+
/**
|
|
3
|
+
* The nodes a construction reveal holds back until their turn, by id. Kept apart from the reveal
|
|
4
|
+
* component so the systems that build hosts read it without pulling in React.
|
|
5
|
+
*/
|
|
6
|
+
export declare const revealPending: Map<string, RevealCandidate>;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the node waits for its turn. A host builds without what waits in it: a wall cuts no
|
|
9
|
+
* hole for a window before the window's turn (Victor run 12).
|
|
10
|
+
*/
|
|
11
|
+
export declare function isRevealWaiting(id: string): boolean;
|
|
12
|
+
//# sourceMappingURL=reveal-pending.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reveal-pending.d.ts","sourceRoot":"","sources":["../../src/lib/reveal-pending.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD;;;GAGG;AACH,eAAO,MAAM,aAAa,8BAAqC,CAAA;AAE/D;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nodes a construction reveal holds back until their turn, by id. Kept apart from the reveal
|
|
3
|
+
* component so the systems that build hosts read it without pulling in React.
|
|
4
|
+
*/
|
|
5
|
+
export const revealPending = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Whether the node waits for its turn. A host builds without what waits in it: a wall cuts no
|
|
8
|
+
* hole for a window before the window's turn (Victor run 12).
|
|
9
|
+
*/
|
|
10
|
+
export function isRevealWaiting(id) {
|
|
11
|
+
return revealPending.has(id);
|
|
12
|
+
}
|