@pascal-app/viewer 1.0.1 → 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/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/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 +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- 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/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/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 +115 -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.map +1 -1
- package/dist/systems/window/window-system.js +3 -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
|
@@ -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;
|
|
@@ -11,8 +11,21 @@ export declare function mergeWallCutoutBrushes(brushes: readonly Brush[]): {
|
|
|
11
11
|
export declare function shouldDeferWallRebuild(wallId: string, nodes: Record<AnyNodeId, AnyNode>, rebuiltThisFrame: number, elapsedMs: number): boolean;
|
|
12
12
|
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
13
13
|
export declare function getPendingWallRebuildCount(): number;
|
|
14
|
-
export
|
|
15
|
-
|
|
14
|
+
export type WallGeometryAdapterContext = {
|
|
15
|
+
isLive: (id: AnyNodeId) => boolean;
|
|
16
|
+
};
|
|
17
|
+
export type WallGeometryAdapter = {
|
|
18
|
+
prepareChildren?: (wall: WallNode, children: readonly AnyNode[], context: WallGeometryAdapterContext) => {
|
|
19
|
+
envelopeChildren: AnyNode[];
|
|
20
|
+
renderChildren: AnyNode[];
|
|
21
|
+
};
|
|
22
|
+
buildGeometry?: (wall: WallNode, envelope: THREE.BufferGeometry, children: readonly AnyNode[]) => THREE.BufferGeometry;
|
|
23
|
+
syncAuxiliaryGeometry?: (wall: WallNode, mesh: THREE.Mesh, geometry: THREE.BufferGeometry) => void;
|
|
24
|
+
};
|
|
25
|
+
export declare const WallSystem: ({ geometryAdapter }?: {
|
|
26
|
+
geometryAdapter?: WallGeometryAdapter;
|
|
27
|
+
}) => null;
|
|
28
|
+
export declare function runWallBuildFrame(geometryAdapter?: WallGeometryAdapter): void;
|
|
16
29
|
type WallTerrainBottomSampler = (x: number, z: number) => number | null;
|
|
17
30
|
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
|
|
18
31
|
//# sourceMappingURL=wall-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAyBd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAsBvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AA2DhE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAmbD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAyBd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAsBvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AA2DhE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,GAAG;IACjE,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,eAAe,EAAE,KAAK,EAAE,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;CACrB,CA4FA;AAmbD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAET;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAID,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,EAAE,0BAA0B,KAChC;QAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;QAAC,cAAc,EAAE,OAAO,EAAE,CAAA;KAAE,CAAA;IAC/D,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,KAAK,CAAC,cAAc,EAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,KACzB,KAAK,CAAC,cAAc,CAAA;IACzB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,cAAc,KAAK,IAAI,CAAA;CACnG,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,sBAAqB;IAAE,eAAe,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAM7F,CAAA;AAED,wBAAgB,iBAAiB,CAAC,eAAe,CAAC,EAAE,mBAAmB,QAatE;AAmcD,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAgEvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,GACzC,KAAK,CAAC,cAAc,CAwPtB"}
|
|
@@ -496,14 +496,14 @@ export function getPendingWallRebuildCount() {
|
|
|
496
496
|
return drainStats.pendingNeighbours;
|
|
497
497
|
}
|
|
498
498
|
let placeholderSweepCountdown = WALL_PLACEHOLDER_SWEEP_INTERVAL;
|
|
499
|
-
export const WallSystem = () => {
|
|
499
|
+
export const WallSystem = ({ geometryAdapter } = {}) => {
|
|
500
500
|
useScene((state) => state.dirtyNodes);
|
|
501
501
|
useLiveNodeOverrides((s) => s.overrides);
|
|
502
502
|
useEffect(() => () => clearLevelMiterCache(), []);
|
|
503
|
-
useFrame(runWallBuildFrame, 4);
|
|
503
|
+
useFrame(() => runWallBuildFrame(geometryAdapter), 4);
|
|
504
504
|
return null;
|
|
505
505
|
};
|
|
506
|
-
export function runWallBuildFrame() {
|
|
506
|
+
export function runWallBuildFrame(geometryAdapter) {
|
|
507
507
|
const initialBuild = isWallInitialBuildActive();
|
|
508
508
|
const token = useScene.getState().hydrationToken;
|
|
509
509
|
if (token !== stalledHydrationToken) {
|
|
@@ -512,13 +512,13 @@ export function runWallBuildFrame() {
|
|
|
512
512
|
}
|
|
513
513
|
drainStats.wallsConsumedThisFrame = 0;
|
|
514
514
|
try {
|
|
515
|
-
consumeWallBuildFrame(initialBuild);
|
|
515
|
+
consumeWallBuildFrame(initialBuild, geometryAdapter);
|
|
516
516
|
}
|
|
517
517
|
finally {
|
|
518
518
|
publishWallDrainStats();
|
|
519
519
|
}
|
|
520
520
|
}
|
|
521
|
-
function consumeWallBuildFrame(initialBuild) {
|
|
521
|
+
function consumeWallBuildFrame(initialBuild, geometryAdapter) {
|
|
522
522
|
const clearDirty = useScene.getState().clearDirty;
|
|
523
523
|
// Self-heal: any registered wall still on its mount-time placeholder
|
|
524
524
|
// geometry with NO dirty mark gets re-marked, so a lost mark (system
|
|
@@ -600,7 +600,7 @@ function consumeWallBuildFrame(initialBuild) {
|
|
|
600
600
|
}
|
|
601
601
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
602
602
|
if (mesh) {
|
|
603
|
-
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
|
|
603
|
+
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData, geometryAdapter), {
|
|
604
604
|
properties: [['node', wallId]],
|
|
605
605
|
});
|
|
606
606
|
clearDirty(wallId);
|
|
@@ -677,7 +677,7 @@ function consumeWallBuildFrame(initialBuild) {
|
|
|
677
677
|
}
|
|
678
678
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
679
679
|
if (mesh) {
|
|
680
|
-
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData), {
|
|
680
|
+
timeSpan('wall-rebuild', () => updateWallGeometry(wallId, miterData, geometryAdapter), {
|
|
681
681
|
properties: [['node', wallId]],
|
|
682
682
|
});
|
|
683
683
|
notifyWallRebuilt(wallId);
|
|
@@ -766,7 +766,7 @@ function getLevelWalls(levelId) {
|
|
|
766
766
|
* (override-merged) so a 2D drag visibly moves the 3D mesh without
|
|
767
767
|
* having touched `useScene` mid-drag.
|
|
768
768
|
*/
|
|
769
|
-
function updateWallGeometry(wallId, miterData) {
|
|
769
|
+
function updateWallGeometry(wallId, miterData, geometryAdapter) {
|
|
770
770
|
const nodes = useScene.getState().nodes;
|
|
771
771
|
const sceneNode = nodes[wallId];
|
|
772
772
|
if (sceneNode?.type !== 'wall')
|
|
@@ -785,38 +785,37 @@ function updateWallGeometry(wallId, miterData) {
|
|
|
785
785
|
? (x, z) => terrainSupportLift(nodes, levelId, x, z)
|
|
786
786
|
: undefined;
|
|
787
787
|
const childrenIds = node.children || [];
|
|
788
|
-
// Merge live overrides into door / window children so cutouts track an
|
|
789
|
-
// in-flight resize drag (door width arrow, window height arrow, etc.)
|
|
790
|
-
// without waiting on the scene store. Non-cutout children pass through
|
|
791
|
-
// unchanged.
|
|
792
788
|
const childrenNodes = childrenIds
|
|
793
789
|
.map((childId) => nodes[childId])
|
|
794
790
|
.filter((n) => n !== undefined)
|
|
795
791
|
.map((child) => {
|
|
796
792
|
if (child.type !== 'door' && child.type !== 'window')
|
|
797
793
|
return child;
|
|
798
|
-
// `getEffectiveNode` folds in resize overrides (width/height arrows).
|
|
799
|
-
// Position moves publish to `useLiveTransforms` instead, so fold that
|
|
800
|
-
// in too — opening cutout brushes are rebuilt directly from the
|
|
801
|
-
// effective node position rather than from the rendered proxy mesh.
|
|
802
794
|
const effective = getEffectiveNode(child);
|
|
803
795
|
const live = useLiveTransforms.getState().get(child.id);
|
|
804
|
-
|
|
805
|
-
return effective;
|
|
806
|
-
return { ...effective, position: live.position };
|
|
796
|
+
return live?.position ? { ...effective, position: live.position } : effective;
|
|
807
797
|
});
|
|
808
|
-
const
|
|
798
|
+
const prepared = geometryAdapter?.prepareChildren?.(node, childrenNodes, {
|
|
799
|
+
isLive: (id) => useLiveNodeOverrides.getState().get(id) !== undefined ||
|
|
800
|
+
useLiveTransforms.getState().get(id) !== undefined,
|
|
801
|
+
}) ?? {
|
|
802
|
+
envelopeChildren: childrenNodes,
|
|
803
|
+
renderChildren: childrenNodes,
|
|
804
|
+
};
|
|
805
|
+
const builtGeo = generateExtrudedWall(node, prepared.envelopeChildren, miterData, slabElevation, slabSupport.baseElevation, slabSupport.baseSegments, planeTop, terrainBottomAt);
|
|
809
806
|
const wallAngle = Math.atan2(node.end[1] - node.start[1], node.end[0] - node.start[0]);
|
|
810
807
|
// World transform the render mesh will apply (position + Y-rotation below).
|
|
811
808
|
// Reproduce it here so the UVs can be projected in WORLD space — see
|
|
812
809
|
// `applyWorldPlanarWallUVs`.
|
|
813
810
|
const wallWorldMatrix = new THREE.Matrix4().compose(new THREE.Vector3(node.start[0], slabElevation, node.start[1]), new THREE.Quaternion().setFromAxisAngle(WALL_UV_Y_AXIS, -wallAngle), WALL_UV_UNIT_SCALE);
|
|
814
|
-
const
|
|
811
|
+
const renderedGeo = geometryAdapter?.buildGeometry?.(node, builtGeo, prepared.renderChildren) ?? builtGeo;
|
|
812
|
+
const newGeo = applyWorldPlanarWallUVs(renderedGeo, wallWorldMatrix);
|
|
815
813
|
mesh.geometry.dispose();
|
|
816
814
|
// A degenerate rebuild (zero-length or fully cut wall) yields as few vertices
|
|
817
815
|
// as the mount-time placeholder; the stamp keeps the sweep from re-marking it.
|
|
818
816
|
newGeo.userData.built = true;
|
|
819
817
|
mesh.geometry = newGeo;
|
|
818
|
+
geometryAdapter?.syncAuxiliaryGeometry?.(node, mesh, newGeo);
|
|
820
819
|
// Update collision mesh
|
|
821
820
|
const collisionMesh = mesh.getObjectByName('collision-mesh');
|
|
822
821
|
if (collisionMesh) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyB9B,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAS3D,eAAO,MAAM,8BAA8B,uoCAAuB,CAAA;AAElE,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyB9B,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAS3D,eAAO,MAAM,8BAA8B,uoCAAuB,CAAA;AAElE,eAAO,MAAM,YAAY,YAqHxB,CAAA;AA28GD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC,IAAI,CAInE"}
|
|
@@ -136,8 +136,9 @@ export const WindowSystem = () => {
|
|
|
136
136
|
// Rebuild the parent wall so its cutout reflects the updated window geometry
|
|
137
137
|
// Avoid triggering expensive wall CSG rebuilds while the window is being interactively moved/duplicated.
|
|
138
138
|
// The editor tools will request a final wall rebuild on commit.
|
|
139
|
-
const
|
|
140
|
-
|
|
139
|
+
const metadata = effectiveNode.metadata;
|
|
140
|
+
const deferParentRebuild = !!metadata?.isTransient || !!metadata?.deferParentRebuild;
|
|
141
|
+
if (!deferParentRebuild && effectiveNode.parentId) {
|
|
141
142
|
useScene.getState().dirtyNodes.add(effectiveNode.parentId);
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare function ImmersiveXRPresentationProvider({ children, enabled, }: {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
export declare function useImmersiveXRPresentation(): boolean;
|
|
7
|
+
//# sourceMappingURL=presentation-context.d.ts.map
|