@pascal-app/viewer 1.0.0 → 1.0.3
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/README.md +12 -0
- package/dist/capture/capture-runtime.js +2 -32
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -1
- package/dist/capture/layers/device-motion-layer.js +5 -7
- package/dist/capture/stream-rendering.d.ts.map +1 -1
- package/dist/capture/stream-rendering.js +2 -1
- package/dist/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- package/dist/components/viewer/index.d.ts +12 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +28 -7
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +6 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- package/dist/components/viewer/viewer-camera.d.ts +15 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +43 -5
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/lib/csg-utils.d.ts +1 -1
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +2 -1
- package/dist/lib/renderer-capability.d.ts +2 -1
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +2 -2
- package/dist/lib/scene-visibility.js +3 -1
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +3 -1
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +18 -5
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +122 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +4 -0
- package/dist/systems/level/level-system.js +14 -14
- package/dist/systems/level/level-utils.d.ts +25 -1
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +25 -4
- package/dist/systems/wall/wall-cutout-cache.d.ts +4 -1
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +16 -7
- package/dist/systems/wall/wall-cutout.d.ts +3 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +5 -5
- package/dist/systems/wall/wall-materials.d.ts +11 -2
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +24 -18
- package/dist/systems/wall/wall-system.d.ts +15 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +20 -21
- 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 +3 -2
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -2
- package/dist/xr/presentation-context.d.ts +7 -0
- package/dist/xr/presentation-context.d.ts.map +1 -0
- package/dist/xr/presentation-context.js +10 -0
- package/package.json +4 -4
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getEffectiveNode, getFloorStackedPosition, nodeRegistry, sceneRegistry, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useEffect, useMemo } from 'react';
|
|
4
|
+
import { Euler, Matrix4, Quaternion, Vector3 } from 'three';
|
|
3
5
|
function withLiveTransform(node, liveTransform) {
|
|
4
6
|
if (!liveTransform)
|
|
5
7
|
return node;
|
|
@@ -15,6 +17,16 @@ function withLiveTransform(node, liveTransform) {
|
|
|
15
17
|
...(rotation !== undefined ? { rotation } : {}),
|
|
16
18
|
};
|
|
17
19
|
}
|
|
20
|
+
function restoreMountedExitPose(saved) {
|
|
21
|
+
saved.mesh.matrixAutoUpdate = saved.autoUpdate;
|
|
22
|
+
saved.mesh.scale.copy(saved.scale);
|
|
23
|
+
if (!saved.mesh.parent || saved.mesh.parent === saved.parent) {
|
|
24
|
+
saved.mesh.position.copy(saved.position);
|
|
25
|
+
saved.mesh.quaternion.copy(saved.quaternion);
|
|
26
|
+
}
|
|
27
|
+
saved.mesh.updateMatrix();
|
|
28
|
+
saved.mesh.updateMatrixWorld(true);
|
|
29
|
+
}
|
|
18
30
|
/**
|
|
19
31
|
* Generic floor-elevation system.
|
|
20
32
|
*
|
|
@@ -36,6 +48,56 @@ function withLiveTransform(node, liveTransform) {
|
|
|
36
48
|
export const FloorElevationSystem = () => {
|
|
37
49
|
const dirtyNodes = useScene((s) => s.dirtyNodes);
|
|
38
50
|
const clearDirty = useScene((s) => s.clearDirty);
|
|
51
|
+
const preview = useMemo(() => ({
|
|
52
|
+
local: new Matrix4(),
|
|
53
|
+
target: new Matrix4(),
|
|
54
|
+
position: new Vector3(),
|
|
55
|
+
rotation: new Euler(),
|
|
56
|
+
quaternion: new Quaternion(),
|
|
57
|
+
unit: new Vector3(1, 1, 1),
|
|
58
|
+
saved: new Map(),
|
|
59
|
+
}), []);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const restore = (id) => {
|
|
62
|
+
const saved = preview.saved.get(id);
|
|
63
|
+
if (!saved)
|
|
64
|
+
return;
|
|
65
|
+
restoreMountedExitPose(saved);
|
|
66
|
+
preview.saved.delete(id);
|
|
67
|
+
};
|
|
68
|
+
const sync = () => {
|
|
69
|
+
const { nodes } = useScene.getState();
|
|
70
|
+
const overrides = useLiveNodeOverrides.getState().overrides;
|
|
71
|
+
for (const id of preview.saved.keys()) {
|
|
72
|
+
const node = nodes[id];
|
|
73
|
+
const patch = overrides.get(id);
|
|
74
|
+
if (!node || !patch?.parentId || patch.parentId === node.parentId)
|
|
75
|
+
restore(id);
|
|
76
|
+
}
|
|
77
|
+
for (const [id, patch] of overrides) {
|
|
78
|
+
const node = nodes[id];
|
|
79
|
+
if (!node || !patch.parentId || patch.parentId === node.parentId || preview.saved.has(id))
|
|
80
|
+
continue;
|
|
81
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
82
|
+
if (!mesh?.parent)
|
|
83
|
+
continue;
|
|
84
|
+
preview.saved.set(id, {
|
|
85
|
+
mesh,
|
|
86
|
+
parent: mesh.parent,
|
|
87
|
+
position: mesh.position.clone(),
|
|
88
|
+
quaternion: mesh.quaternion.clone(),
|
|
89
|
+
scale: mesh.scale.clone(),
|
|
90
|
+
autoUpdate: mesh.matrixAutoUpdate,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const unsubscribe = useLiveNodeOverrides.subscribe(sync);
|
|
95
|
+
return () => {
|
|
96
|
+
unsubscribe();
|
|
97
|
+
for (const id of preview.saved.keys())
|
|
98
|
+
restore(id);
|
|
99
|
+
};
|
|
100
|
+
}, [preview]);
|
|
39
101
|
useFrame(() => {
|
|
40
102
|
// Nodes with a live preview (override / transform) are reapplied EVERY
|
|
41
103
|
// frame, not only while dirty: the React commit that rebinds the group's
|
|
@@ -64,6 +126,11 @@ export const FloorElevationSystem = () => {
|
|
|
64
126
|
const position = effectiveNode.position;
|
|
65
127
|
if (!position)
|
|
66
128
|
return;
|
|
129
|
+
if (effectiveNode.parentId !== node.parentId)
|
|
130
|
+
return;
|
|
131
|
+
if (!(def.geometry || def.system) && dirtyNodes.has(id)) {
|
|
132
|
+
clearDirty(id);
|
|
133
|
+
}
|
|
67
134
|
// `applies === false` means the kind opts OUT of floor stacking for this
|
|
68
135
|
// node: its Y belongs to a host frame (a wall/ceiling-mounted item, a
|
|
69
136
|
// cabinet module inside a run, a wall duct terminal). `getFloorPlacedElevation`
|
|
@@ -73,6 +140,10 @@ export const FloorElevationSystem = () => {
|
|
|
73
140
|
// the host frame's own elevation.
|
|
74
141
|
if (floorPlaced.applies && !floorPlaced.applies(effectiveNode))
|
|
75
142
|
return;
|
|
143
|
+
// Hosted meshes inherit elevation from their parent (and possibly a surface group).
|
|
144
|
+
// Their live transform can be world-space, so it cannot replace the mesh's local Y.
|
|
145
|
+
if (!effectiveNode.parentId || nodes[effectiveNode.parentId]?.type !== 'level')
|
|
146
|
+
return;
|
|
76
147
|
// This system is the single drag-time authority for floor-stack mesh Y:
|
|
77
148
|
// tools publish base positions to live stores, renderers may
|
|
78
149
|
// reconcile that base Y onto the group, then this presentation system
|
|
@@ -90,9 +161,6 @@ export const FloorElevationSystem = () => {
|
|
|
90
161
|
maxElevation: liveTransform?.supportElevationCap,
|
|
91
162
|
});
|
|
92
163
|
mesh.position.y = visualPosition[1];
|
|
93
|
-
if (!(def.geometry || def.system) && dirtyNodes.has(id)) {
|
|
94
|
-
clearDirty(id);
|
|
95
|
-
}
|
|
96
164
|
};
|
|
97
165
|
dirtyNodes.forEach((id) => {
|
|
98
166
|
applyLift(id);
|
|
@@ -106,5 +174,56 @@ export const FloorElevationSystem = () => {
|
|
|
106
174
|
applyLift(id);
|
|
107
175
|
});
|
|
108
176
|
}, 1);
|
|
177
|
+
// PostProcessing draws at priority 1 after this system; later callbacks would show one wrong frame per move.
|
|
178
|
+
useFrame(() => {
|
|
179
|
+
const nodes = useScene.getState().nodes;
|
|
180
|
+
for (const [id, patch] of useLiveNodeOverrides.getState().overrides) {
|
|
181
|
+
const node = nodes[id];
|
|
182
|
+
if (!node || !patch.parentId || patch.parentId === node.parentId)
|
|
183
|
+
continue;
|
|
184
|
+
const effective = getEffectiveNode(node);
|
|
185
|
+
if (effective.parentId === node.parentId || !effective.position)
|
|
186
|
+
continue;
|
|
187
|
+
if (!effective.parentId || nodes[effective.parentId]?.type !== 'level')
|
|
188
|
+
continue;
|
|
189
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
190
|
+
const level = sceneRegistry.nodes.get(effective.parentId);
|
|
191
|
+
if (!mesh?.parent || !level)
|
|
192
|
+
continue;
|
|
193
|
+
const previous = preview.saved.get(id);
|
|
194
|
+
if (previous?.mesh !== mesh) {
|
|
195
|
+
if (previous)
|
|
196
|
+
restoreMountedExitPose(previous);
|
|
197
|
+
preview.saved.set(id, {
|
|
198
|
+
mesh,
|
|
199
|
+
parent: mesh.parent,
|
|
200
|
+
position: mesh.position.clone(),
|
|
201
|
+
quaternion: mesh.quaternion.clone(),
|
|
202
|
+
scale: mesh.scale.clone(),
|
|
203
|
+
autoUpdate: mesh.matrixAutoUpdate,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const position = getFloorStackedPosition({
|
|
207
|
+
node: effective,
|
|
208
|
+
nodes,
|
|
209
|
+
position: effective.position,
|
|
210
|
+
});
|
|
211
|
+
const rotation = typeof effective.rotation === 'number'
|
|
212
|
+
? [0, effective.rotation, 0]
|
|
213
|
+
: (effective.rotation ?? [0, 0, 0]);
|
|
214
|
+
level.updateWorldMatrix(true, false);
|
|
215
|
+
mesh.parent.updateWorldMatrix(true, false);
|
|
216
|
+
// The override's parent is logical; the mesh still inherits the mounted host and surface wrapper.
|
|
217
|
+
preview.local
|
|
218
|
+
.copy(mesh.parent.matrixWorld)
|
|
219
|
+
.invert()
|
|
220
|
+
.multiply(level.matrixWorld)
|
|
221
|
+
.multiply(preview.target.compose(preview.position.fromArray(position), preview.quaternion.setFromEuler(preview.rotation.set(rotation[0], rotation[1], rotation[2])), preview.unit));
|
|
222
|
+
// Keep the full matrix: a rotated child beneath nonuniform scale can require shear.
|
|
223
|
+
mesh.matrixAutoUpdate = false;
|
|
224
|
+
mesh.matrix.copy(preview.local);
|
|
225
|
+
mesh.updateMatrixWorld(true);
|
|
226
|
+
}
|
|
227
|
+
}, 1);
|
|
109
228
|
return null;
|
|
110
229
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAUtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YAuM1B,CAAA;
|
|
1
|
+
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAUtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YAuM1B,CAAA;AAiJD,eAAe,cAAc,CAAA;AAE7B,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAU7D;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAKT"}
|
|
@@ -212,7 +212,11 @@ function liveChildOverrideKey(node) {
|
|
|
212
212
|
return '';
|
|
213
213
|
const overrides = useLiveNodeOverrides.getState().overrides;
|
|
214
214
|
const entries = [];
|
|
215
|
+
const childTypes = nodeRegistry.get(node.type)?.geometryChildTypes;
|
|
216
|
+
const nodes = useScene.getState().nodes;
|
|
215
217
|
for (const childId of childIds) {
|
|
218
|
+
if (childTypes && !childTypes.includes(nodes[childId]?.type ?? ''))
|
|
219
|
+
continue;
|
|
216
220
|
const override = overrides.get(childId);
|
|
217
221
|
if (override)
|
|
218
222
|
entries.push([childId, override]);
|
|
@@ -3,7 +3,7 @@ import { useFrame } from '@react-three/fiber';
|
|
|
3
3
|
import { lerp } from 'three/src/math/MathUtils.js';
|
|
4
4
|
import { applyShadowOnly, clearShadowOnly } from '../../lib/shadow-only';
|
|
5
5
|
import useViewer from '../../store/use-viewer';
|
|
6
|
-
import { EXPLODED_GAP } from './level-utils';
|
|
6
|
+
import { EXPLODED_GAP, resolveLevelVisibility } from './level-utils';
|
|
7
7
|
// Levels currently in shadow-caster-only mode (solo hides them from the color
|
|
8
8
|
// passes but keeps their sun shadows). Tracked so we can restore layer masks
|
|
9
9
|
// exactly once on transition; apply re-runs every frame so meshes rebuilt
|
|
@@ -42,23 +42,23 @@ export const LevelSystem = () => {
|
|
|
42
42
|
// (blank viewport). Clamping keeps every step a contraction: identical
|
|
43
43
|
// feel at 60 fps, exact snap instead of overshoot on slow frames.
|
|
44
44
|
obj.position.y = lerp(obj.position.y, targetY, Math.min(1, delta * 12));
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const { visible, shadowOnly } = resolveLevelVisibility({
|
|
46
|
+
levelMode,
|
|
47
|
+
hasSelectedLevel: Boolean(selectedLevel),
|
|
48
|
+
isSelected: level?.id === selectedLevel,
|
|
49
|
+
index,
|
|
50
|
+
selectedIndex,
|
|
51
|
+
nodeVisible: level?.visible !== false,
|
|
52
|
+
});
|
|
53
|
+
if (shadowOnly) {
|
|
51
54
|
applyShadowOnly(obj);
|
|
52
55
|
shadowOnlyLevels.add(obj);
|
|
53
|
-
obj.visible = true;
|
|
54
56
|
}
|
|
55
|
-
else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
shadowOnlyLevels.delete(obj);
|
|
59
|
-
}
|
|
60
|
-
obj.visible = !hidden;
|
|
57
|
+
else if (shadowOnlyLevels.has(obj)) {
|
|
58
|
+
clearShadowOnly(obj);
|
|
59
|
+
shadowOnlyLevels.delete(obj);
|
|
61
60
|
}
|
|
61
|
+
obj.visible = visible;
|
|
62
62
|
}
|
|
63
63
|
}, 5); // Using a lower priority so it runs after transforms from other systems have settled
|
|
64
64
|
return null;
|
|
@@ -7,9 +7,33 @@ export declare const EXPLODED_GAP = 5;
|
|
|
7
7
|
* code framing a level must aim at the destination, not the moving target.
|
|
8
8
|
*/
|
|
9
9
|
export declare function getLevelPresentationY(levelId: string, nodes: Record<string, unknown>, levelMode: 'stacked' | 'exploded' | 'solo' | 'manual'): number;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a level renders this frame, and whether it renders as a
|
|
12
|
+
* shadow-caster only.
|
|
13
|
+
*
|
|
14
|
+
* Two unrelated things hide a level and they do not compose: the author's own
|
|
15
|
+
* `visible` flag (the sidebar eye), which hides the floor outright, and solo
|
|
16
|
+
* mode, which hides every level but the soloed one — keeping the levels ABOVE
|
|
17
|
+
* it in the shadow map so the sun still shadows the soloed floor through them.
|
|
18
|
+
* A level the author hid never enters that shadow-caster branch: its shadows
|
|
19
|
+
* on the floor below would be exactly what hiding it was meant to remove.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveLevelVisibility({ levelMode, hasSelectedLevel, isSelected, index, selectedIndex, nodeVisible, }: {
|
|
22
|
+
levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
|
|
23
|
+
hasSelectedLevel: boolean;
|
|
24
|
+
isSelected: boolean;
|
|
25
|
+
index: number;
|
|
26
|
+
selectedIndex: number | undefined;
|
|
27
|
+
nodeVisible: boolean;
|
|
28
|
+
}): {
|
|
29
|
+
visible: boolean;
|
|
30
|
+
shadowOnly: boolean;
|
|
31
|
+
};
|
|
10
32
|
/**
|
|
11
33
|
* Instantly snaps all level Objects3D to their true stacked Y positions
|
|
12
|
-
* (ignores levelMode — always uses stacked, no exploded gap).
|
|
34
|
+
* (ignores levelMode — always uses stacked, no exploded gap). Presentation
|
|
35
|
+
* hiding is undone with it, but a level the author hid stays hidden: the
|
|
36
|
+
* capture has to match the export, which prunes it.
|
|
13
37
|
*
|
|
14
38
|
* Returns a restore function that reverts each level's Y to what it was
|
|
15
39
|
* before the snap, so lerp animations in LevelSystem can continue undisturbed.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"level-utils.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-utils.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GACpD,MAAM,CAKR;AAED
|
|
1
|
+
{"version":3,"file":"level-utils.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-utils.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GACpD,MAAM,CAKR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,KAAK,EACL,aAAa,EACb,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,gBAAgB,EAAE,OAAO,CAAA;IACzB,UAAU,EAAE,OAAO,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAM5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,IAAI,CA2CtD"}
|
|
@@ -13,9 +13,29 @@ export function getLevelPresentationY(levelId, nodes, levelMode) {
|
|
|
13
13
|
const explodedExtra = levelMode === 'exploded' && level ? level.level * EXPLODED_GAP : 0;
|
|
14
14
|
return baseY + explodedExtra;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether a level renders this frame, and whether it renders as a
|
|
18
|
+
* shadow-caster only.
|
|
19
|
+
*
|
|
20
|
+
* Two unrelated things hide a level and they do not compose: the author's own
|
|
21
|
+
* `visible` flag (the sidebar eye), which hides the floor outright, and solo
|
|
22
|
+
* mode, which hides every level but the soloed one — keeping the levels ABOVE
|
|
23
|
+
* it in the shadow map so the sun still shadows the soloed floor through them.
|
|
24
|
+
* A level the author hid never enters that shadow-caster branch: its shadows
|
|
25
|
+
* on the floor below would be exactly what hiding it was meant to remove.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveLevelVisibility({ levelMode, hasSelectedLevel, isSelected, index, selectedIndex, nodeVisible, }) {
|
|
28
|
+
if (!nodeVisible)
|
|
29
|
+
return { visible: false, shadowOnly: false };
|
|
30
|
+
const hidden = levelMode === 'solo' && hasSelectedLevel && !isSelected;
|
|
31
|
+
const shadowOnly = hidden && selectedIndex !== undefined && index > selectedIndex;
|
|
32
|
+
return { visible: shadowOnly || !hidden, shadowOnly };
|
|
33
|
+
}
|
|
16
34
|
/**
|
|
17
35
|
* Instantly snaps all level Objects3D to their true stacked Y positions
|
|
18
|
-
* (ignores levelMode — always uses stacked, no exploded gap).
|
|
36
|
+
* (ignores levelMode — always uses stacked, no exploded gap). Presentation
|
|
37
|
+
* hiding is undone with it, but a level the author hid stays hidden: the
|
|
38
|
+
* capture has to match the export, which prunes it.
|
|
19
39
|
*
|
|
20
40
|
* Returns a restore function that reverts each level's Y to what it was
|
|
21
41
|
* before the snap, so lerp animations in LevelSystem can continue undisturbed.
|
|
@@ -35,16 +55,17 @@ export function snapLevelsToTruePositions() {
|
|
|
35
55
|
entries.push({
|
|
36
56
|
levelId,
|
|
37
57
|
obj,
|
|
58
|
+
nodeVisible: level.visible !== false,
|
|
38
59
|
});
|
|
39
60
|
}
|
|
40
61
|
});
|
|
41
62
|
const levelElevations = getLevelElevations(nodes);
|
|
42
63
|
// Snapshot current Y and visibility so we can restore them after the render
|
|
43
64
|
const snapshot = new Map(entries.map(({ levelId, obj }) => [levelId, { y: obj.position.y, visible: obj.visible }]));
|
|
44
|
-
// Snap to true stacked positions and
|
|
45
|
-
for (const { levelId, obj } of entries) {
|
|
65
|
+
// Snap to true stacked positions and undo presentation hiding
|
|
66
|
+
for (const { levelId, obj, nodeVisible } of entries) {
|
|
46
67
|
obj.position.y = levelElevations.get(levelId)?.baseY ?? 0;
|
|
47
|
-
obj.visible =
|
|
68
|
+
obj.visible = nodeVisible;
|
|
48
69
|
}
|
|
49
70
|
return () => {
|
|
50
71
|
for (const { levelId, obj } of entries) {
|
|
@@ -2,6 +2,7 @@ import { type WallNode } from '@pascal-app/core';
|
|
|
2
2
|
import { type Camera, type Material, Matrix4, type Mesh, Vector3 } from 'three';
|
|
3
3
|
import useViewer, { type WallMode } from '../../store/use-viewer';
|
|
4
4
|
import { type WallMaterialVariant } from './wall-material-variant';
|
|
5
|
+
import { type WallMaterialsResolver } from './wall-materials';
|
|
5
6
|
export declare function sameMaterialArray(a: Material | Material[], b: Material[]): boolean;
|
|
6
7
|
type Variant = {
|
|
7
8
|
key: WallMaterialVariant;
|
|
@@ -29,6 +30,7 @@ export type WallCutoutViewerStore = {
|
|
|
29
30
|
};
|
|
30
31
|
export declare class WallCutoutCache {
|
|
31
32
|
private readonly viewerStore;
|
|
33
|
+
private readonly materialResolver;
|
|
32
34
|
readonly walls: Map<string, CachedWall>;
|
|
33
35
|
readonly rebuilt: Set<string>;
|
|
34
36
|
private viewer;
|
|
@@ -46,7 +48,8 @@ export declare class WallCutoutCache {
|
|
|
46
48
|
private selected;
|
|
47
49
|
private highlightKey;
|
|
48
50
|
private transformed;
|
|
49
|
-
|
|
51
|
+
private overrides;
|
|
52
|
+
constructor(viewerStore?: WallCutoutViewerStore, materialResolver?: WallMaterialsResolver);
|
|
50
53
|
subscribeLiveTransforms(): () => void;
|
|
51
54
|
update(camera: Camera, time: number): void;
|
|
52
55
|
private refreshAppearance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-cutout-cache.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout-cache.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-cutout-cache.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// New kind-specific modules belong in nodes; viewer must not depend on nodes.
|
|
2
2
|
// This extends the existing viewer-owned wall cutout and material implementation.
|
|
3
|
-
import { getLibraryMaterialsVersion, getWallFaceBandConfig, getWallPlaneTop, resolveLevelId, resolveWallEffectiveHeight, sceneRegistry, spatialGridManager, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { getEffectiveNode, getLibraryMaterialsVersion, getWallFaceBandConfig, getWallPlaneTop, resolveLevelId, resolveWallEffectiveHeight, sceneRegistry, spatialGridManager, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { Matrix4, Vector3 } from 'three';
|
|
5
5
|
import { getMaterialTextureVersion } from '../../lib/materials';
|
|
6
6
|
import useViewer, {} from '../../store/use-viewer';
|
|
@@ -59,6 +59,7 @@ export function wallFacingNegative(dot, previous) {
|
|
|
59
59
|
}
|
|
60
60
|
export class WallCutoutCache {
|
|
61
61
|
viewerStore;
|
|
62
|
+
materialResolver;
|
|
62
63
|
walls = new Map();
|
|
63
64
|
rebuilt = new Set();
|
|
64
65
|
viewer;
|
|
@@ -76,8 +77,10 @@ export class WallCutoutCache {
|
|
|
76
77
|
selected = new Set();
|
|
77
78
|
highlightKey = '';
|
|
78
79
|
transformed = new Set();
|
|
79
|
-
|
|
80
|
+
overrides = useLiveNodeOverrides.getState().overrides;
|
|
81
|
+
constructor(viewerStore = useViewer, materialResolver = getMaterialsForWall) {
|
|
80
82
|
this.viewerStore = viewerStore;
|
|
83
|
+
this.materialResolver = materialResolver;
|
|
81
84
|
}
|
|
82
85
|
subscribeLiveTransforms() {
|
|
83
86
|
return useLiveTransforms.subscribe((state, previous) => {
|
|
@@ -98,6 +101,8 @@ export class WallCutoutCache {
|
|
|
98
101
|
const libraryVersion = getLibraryMaterialsVersion();
|
|
99
102
|
const textureVersion = getMaterialTextureVersion();
|
|
100
103
|
const previous = this.viewer;
|
|
104
|
+
const overrides = useLiveNodeOverrides.getState().overrides;
|
|
105
|
+
const overridesChanged = this.overrides !== overrides;
|
|
101
106
|
const nodesChanged = this.nodes !== scene.nodes;
|
|
102
107
|
const registryChanged = this.registryRevision !== sceneRegistry.revision || this.wallCount !== wallIds.size;
|
|
103
108
|
let highlightChanged = false;
|
|
@@ -128,7 +133,8 @@ export class WallCutoutCache {
|
|
|
128
133
|
: null;
|
|
129
134
|
const releasedPreview = previewId(previous) !== previewId(viewer) ? previewId(previous) : null;
|
|
130
135
|
this.viewer = viewer;
|
|
131
|
-
const invalidated =
|
|
136
|
+
const invalidated = overridesChanged ||
|
|
137
|
+
appearanceChanged ||
|
|
132
138
|
nodesChanged ||
|
|
133
139
|
registryChanged ||
|
|
134
140
|
this.rebuilt.size > 0 ||
|
|
@@ -195,16 +201,18 @@ export class WallCutoutCache {
|
|
|
195
201
|
const rebuilt = this.rebuilt.has(id);
|
|
196
202
|
if (added || changed || rebuilt)
|
|
197
203
|
this.refreshNormal(wall, visited);
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
const overrideChanged = this.overrides.get(id) !== overrides.get(id);
|
|
205
|
+
wall.node = getEffectiveNode(node);
|
|
206
|
+
if (added || appearanceChanged || overrideChanged || (nodesChanged && changed))
|
|
200
207
|
this.refreshAppearance(wall);
|
|
201
208
|
if (added ||
|
|
202
209
|
appearanceChanged ||
|
|
210
|
+
overrideChanged ||
|
|
203
211
|
changed ||
|
|
204
212
|
rebuilt ||
|
|
205
213
|
releasedPreview === id ||
|
|
206
214
|
cameraChanged) {
|
|
207
|
-
this.apply(wall, viewer.wallMode, added || appearanceChanged || (nodesChanged && changed));
|
|
215
|
+
this.apply(wall, viewer.wallMode, added || appearanceChanged || overrideChanged || (nodesChanged && changed));
|
|
208
216
|
}
|
|
209
217
|
}
|
|
210
218
|
}
|
|
@@ -214,6 +222,7 @@ export class WallCutoutCache {
|
|
|
214
222
|
}
|
|
215
223
|
this.rebuilt.clear();
|
|
216
224
|
this.transformed.clear();
|
|
225
|
+
this.overrides = overrides;
|
|
217
226
|
this.nodes = scene.nodes;
|
|
218
227
|
this.materials = scene.materials;
|
|
219
228
|
this.registryRevision = sceneRegistry.revision;
|
|
@@ -238,7 +247,7 @@ export class WallCutoutCache {
|
|
|
238
247
|
const height = resolveWallEffectiveHeight(node, getWallPlaneTop(node, levelId, scene.nodes), support.elevation);
|
|
239
248
|
selectionHighlighted = !getWallFaceBandConfig(node, height).enabled;
|
|
240
249
|
}
|
|
241
|
-
const materials =
|
|
250
|
+
const materials = this.materialResolver(node, viewer.shading, viewer.textures, viewer.colorPreset, viewer.sceneTheme, scene.materials);
|
|
242
251
|
const variant = (hidden) => {
|
|
243
252
|
const key = resolveWallMaterialVariant({
|
|
244
253
|
translucentMode: viewer.wallMode === 'translucent',
|
|
@@ -3,6 +3,7 @@ import type { Camera } from 'three';
|
|
|
3
3
|
import { type Mesh, Vector3 } from 'three/webgpu';
|
|
4
4
|
import { type WallMode } from '../../store/use-viewer';
|
|
5
5
|
import { WallCutoutCache, type WallCutoutViewerStore } from './wall-cutout-cache';
|
|
6
|
+
import { type WallMaterialsResolver } from './wall-materials';
|
|
6
7
|
export declare const WALL_CUTOUT_FRAME_PRIORITY = 0;
|
|
7
8
|
export declare function runWallCutoutFrame(cache: WallCutoutCache, { camera, clock }: {
|
|
8
9
|
camera: Camera;
|
|
@@ -19,7 +20,8 @@ export declare function runWallCutoutFrame(cache: WallCutoutCache, { camera, clo
|
|
|
19
20
|
* from the assigned material variant.
|
|
20
21
|
*/
|
|
21
22
|
export declare function getWallHideState(wallNode: WallNode, wallMesh: Mesh, wallMode: WallMode, cameraDir: Vector3): boolean;
|
|
22
|
-
export declare const WallCutout: ({ viewerStore, }: {
|
|
23
|
+
export declare const WallCutout: ({ viewerStore, materialResolver, }: {
|
|
23
24
|
viewerStore?: WallCutoutViewerStore;
|
|
25
|
+
materialResolver?: WallMaterialsResolver;
|
|
24
26
|
}) => null;
|
|
25
27
|
//# sourceMappingURL=wall-cutout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAGlG,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAkB,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAEL,eAAe,EACf,KAAK,qBAAqB,EAE3B,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoD,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAGlG,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAkB,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAEL,eAAe,EACf,KAAK,qBAAqB,EAE3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAA;AAKzB,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,QAGtE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,OAAO,GACjB,OAAO,CAKT;AAED,eAAO,MAAM,UAAU,GAAI,oCAGxB;IACD,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,gBAAgB,CAAC,EAAE,qBAAqB,CAAA;CACzC,SA8DA,CAAA"}
|
|
@@ -4,7 +4,7 @@ import { useEffect, useMemo } from 'react';
|
|
|
4
4
|
import { Vector3 } from 'three/webgpu';
|
|
5
5
|
import useViewer, {} from '../../store/use-viewer';
|
|
6
6
|
import { sameMaterialArray, WallCutoutCache, wallHiddenFromFacing, } from './wall-cutout-cache';
|
|
7
|
-
import { getMaterialsForWall, getSelectionHighlightMaterials } from './wall-materials';
|
|
7
|
+
import { getMaterialsForWall, getSelectionHighlightMaterials, } from './wall-materials';
|
|
8
8
|
import { subscribeWallRebuilds } from './wall-rebuild-notifications';
|
|
9
9
|
const v = new Vector3();
|
|
10
10
|
export const WALL_CUTOUT_FRAME_PRIORITY = 0;
|
|
@@ -27,8 +27,8 @@ export function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
|
|
|
27
27
|
wallMesh.getWorldDirection(v);
|
|
28
28
|
return wallHiddenFromFacing(wallNode, wallMode, v.dot(cameraDir) < 0);
|
|
29
29
|
}
|
|
30
|
-
export const WallCutout = ({ viewerStore = useViewer, }) => {
|
|
31
|
-
const cache = useMemo(() => new WallCutoutCache(viewerStore), [viewerStore]);
|
|
30
|
+
export const WallCutout = ({ viewerStore = useViewer, materialResolver = getMaterialsForWall, }) => {
|
|
31
|
+
const cache = useMemo(() => new WallCutoutCache(viewerStore, materialResolver), [viewerStore, materialResolver]);
|
|
32
32
|
useEffect(() => subscribeWallRebuilds((id) => cache.rebuilt.add(id)), [cache]);
|
|
33
33
|
useEffect(() => cache.subscribeLiveTransforms(), [cache]);
|
|
34
34
|
// Camera changes reach PostProcessing (1) in this frame. WallSystem (4)
|
|
@@ -44,7 +44,7 @@ export const WallCutout = ({ viewerStore = useViewer, }) => {
|
|
|
44
44
|
const wallNode = useScene.getState().nodes[wallId];
|
|
45
45
|
if (wallNode?.type !== 'wall')
|
|
46
46
|
return;
|
|
47
|
-
const mats =
|
|
47
|
+
const mats = materialResolver(wallNode, viewerStore.getState().shading, viewerStore.getState().textures, viewerStore.getState().colorPreset, viewerStore.getState().sceneTheme, useScene.getState().materials);
|
|
48
48
|
const current = wallMesh.material;
|
|
49
49
|
snapshot.set(wallMesh, current);
|
|
50
50
|
if (current === mats.deleteVisible) {
|
|
@@ -71,6 +71,6 @@ export const WallCutout = ({ viewerStore = useViewer, }) => {
|
|
|
71
71
|
emitter.off('thumbnail:before-capture', restoreForCapture);
|
|
72
72
|
emitter.off('thumbnail:after-capture', reapplyAfterCapture);
|
|
73
73
|
};
|
|
74
|
-
}, [viewerStore]);
|
|
74
|
+
}, [viewerStore, materialResolver]);
|
|
75
75
|
return null;
|
|
76
76
|
};
|
|
@@ -11,7 +11,16 @@ export interface WallMaterials {
|
|
|
11
11
|
deleteInvisible: WallMaterialArray;
|
|
12
12
|
deleteTranslucent: WallMaterialArray;
|
|
13
13
|
materialHash: string;
|
|
14
|
+
ownedVisible?: Material[];
|
|
14
15
|
}
|
|
16
|
+
export type WallMaterialOverride = {
|
|
17
|
+
hash: string;
|
|
18
|
+
create: () => {
|
|
19
|
+
visible: WallMaterialArray;
|
|
20
|
+
owned?: Material[];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type WallMaterialsResolver = (wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials) => WallMaterials;
|
|
15
24
|
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
16
25
|
export declare function getSelectionHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
|
|
17
26
|
/**
|
|
@@ -20,8 +29,8 @@ export declare function getSelectionHighlightMaterials(materials: WallMaterialAr
|
|
|
20
29
|
* film so the wall the click would select reads under the cursor).
|
|
21
30
|
*/
|
|
22
31
|
export declare function getHoverHighlightMaterials(materials: WallMaterialArray): WallMaterialArray;
|
|
23
|
-
export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading, sceneMaterials?: SceneMaterials): string;
|
|
24
|
-
export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterials;
|
|
32
|
+
export declare function getWallMaterialHash(wallNode: WallNode, shading: RenderShading, sceneMaterials?: SceneMaterials, overrideHash?: string): string;
|
|
33
|
+
export declare function getMaterialsForWall(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials, override?: WallMaterialOverride): WallMaterials;
|
|
25
34
|
export declare function getVisibleWallMaterials(wallNode: WallNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string, sceneMaterials?: SceneMaterials): WallMaterialArray;
|
|
26
35
|
export {};
|
|
27
36
|
//# sourceMappingURL=wall-materials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAexE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,EAAE,CAAA;AAE1C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"wall-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAG5C,OAAO,EAEL,KAAK,WAAW,EAMhB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AAE5B,KAAK,cAAc,GAAG,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,GAAG,SAAS,CAAA;AAexE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,EAAE,CAAA;AAE1C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,aAAa,EAAE,iBAAiB,CAAA;IAChC,eAAe,EAAE,iBAAiB,CAAA;IAClC,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAA;CACjE,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,OAAO,EAClB,WAAW,CAAC,EAAE,WAAW,EACzB,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,KAC5B,aAAa,CAAA;AA+SlB,wFAAwF;AACxF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS9F;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,iBAAiB,GAAG,iBAAiB,CAS1F;AA4DD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,EAC/B,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAeR;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,EAC/B,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,aAAa,CAqIf;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,EACnB,cAAc,CAAC,EAAE,cAAc,GAC9B,iBAAiB,CAGnB"}
|
|
@@ -286,9 +286,10 @@ function disposeOwnedMaterials(materials) {
|
|
|
286
286
|
material.dispose();
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
|
-
export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
|
|
289
|
+
export function getWallMaterialHash(wallNode, shading, sceneMaterials, overrideHash) {
|
|
290
290
|
return JSON.stringify({
|
|
291
291
|
shading,
|
|
292
|
+
overrideHash,
|
|
292
293
|
interior: wallFaceMaterialSignature(wallNode, 'interior', sceneMaterials),
|
|
293
294
|
exterior: wallFaceMaterialSignature(wallNode, 'exterior', sceneMaterials),
|
|
294
295
|
lowerInterior: wallSlotMaterialSignature(wallNode, 'lowerInterior', sceneMaterials),
|
|
@@ -301,10 +302,10 @@ export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
|
|
|
301
302
|
topExterior: wallSlotMaterialSignature(wallNode, 'topExterior', sceneMaterials),
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
|
-
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
305
|
+
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials, override) {
|
|
305
306
|
const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
|
|
306
307
|
const materialHash = textures
|
|
307
|
-
? getWallMaterialHash(wallNode, shading, sceneMaterials)
|
|
308
|
+
? getWallMaterialHash(wallNode, shading, sceneMaterials, override?.hash)
|
|
308
309
|
: JSON.stringify({ textures, colorPreset, sceneTheme });
|
|
309
310
|
const existing = wallMaterialCache.get(cacheKey);
|
|
310
311
|
if (existing && existing.materialHash === materialHash) {
|
|
@@ -312,6 +313,7 @@ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = t
|
|
|
312
313
|
}
|
|
313
314
|
if (existing) {
|
|
314
315
|
disposeOwnedMaterials([
|
|
316
|
+
...(existing.ownedVisible ? [existing.ownedVisible] : []),
|
|
315
317
|
existing.invisible,
|
|
316
318
|
existing.translucent,
|
|
317
319
|
existing.deleteVisible,
|
|
@@ -320,25 +322,28 @@ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = t
|
|
|
320
322
|
]);
|
|
321
323
|
}
|
|
322
324
|
const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
|
|
325
|
+
const resolvedOverride = textures ? override?.create() : undefined;
|
|
323
326
|
// Colored mode: each face resolves slot-first (node.slots ref → legacy inline
|
|
324
327
|
// fields → declared slot default, parity with the retired DEFAULT_WALL_MATERIAL).
|
|
325
328
|
// Textures-off collapses every face to the themed wall role (the guaranteed
|
|
326
329
|
// escape hatch). The edge/cap slot (index 0) stays role-based.
|
|
327
|
-
const visible =
|
|
328
|
-
?
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
330
|
+
const visible = resolvedOverride
|
|
331
|
+
? resolvedOverride.visible
|
|
332
|
+
: textures
|
|
333
|
+
? [
|
|
334
|
+
wallRoleMaterial,
|
|
335
|
+
resolveWallFaceMaterial(wallNode, 'interior', shading, sceneMaterials),
|
|
336
|
+
resolveWallFaceMaterial(wallNode, 'exterior', shading, sceneMaterials),
|
|
337
|
+
resolveWallSlotMaterial(wallNode, 'lowerInterior', shading, sceneMaterials),
|
|
338
|
+
resolveWallSlotMaterial(wallNode, 'middleInterior', shading, sceneMaterials),
|
|
339
|
+
resolveWallSlotMaterial(wallNode, 'upperInterior', shading, sceneMaterials),
|
|
340
|
+
resolveWallSlotMaterial(wallNode, 'topInterior', shading, sceneMaterials),
|
|
341
|
+
resolveWallSlotMaterial(wallNode, 'lowerExterior', shading, sceneMaterials),
|
|
342
|
+
resolveWallSlotMaterial(wallNode, 'middleExterior', shading, sceneMaterials),
|
|
343
|
+
resolveWallSlotMaterial(wallNode, 'upperExterior', shading, sceneMaterials),
|
|
344
|
+
resolveWallSlotMaterial(wallNode, 'topExterior', shading, sceneMaterials),
|
|
345
|
+
]
|
|
346
|
+
: Array.from({ length: 11 }, () => wallRoleMaterial);
|
|
342
347
|
const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
|
|
343
348
|
const invisible = [
|
|
344
349
|
createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
@@ -387,6 +392,7 @@ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = t
|
|
|
387
392
|
deleteInvisible,
|
|
388
393
|
deleteTranslucent,
|
|
389
394
|
materialHash,
|
|
395
|
+
ownedVisible: resolvedOverride?.owned,
|
|
390
396
|
};
|
|
391
397
|
wallMaterialCache.set(cacheKey, result);
|
|
392
398
|
return result;
|