@pascal-app/viewer 0.8.0 → 0.9.1
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/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 +38 -22
- package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
- package/dist/components/renderers/parametric-node-renderer.js +34 -0
- package/dist/components/renderers/scene-renderer.d.ts +1 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.d.ts +1 -1
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +12 -24
- package/dist/components/viewer/index.d.ts +37 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +186 -63
- package/dist/components/viewer/lights.d.ts +1 -1
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +170 -43
- package/dist/components/viewer/perf-monitor.d.ts +1 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +99 -11
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +231 -54
- package/dist/components/viewer/registered-systems.d.ts +12 -0
- package/dist/components/viewer/registered-systems.d.ts.map +1 -0
- package/dist/components/viewer/registered-systems.js +47 -0
- package/dist/components/viewer/scene-bvh.d.ts +17 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.js +94 -0
- package/dist/components/viewer/selection-manager.d.ts +1 -1
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +20 -5
- package/dist/components/viewer/viewer-camera.d.ts +1 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts +1 -1
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-node-events.d.ts +5 -73
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +19 -7
- package/dist/index.d.ts +36 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -3
- package/dist/lib/csg-utils.d.ts +16 -0
- package/dist/lib/csg-utils.d.ts.map +1 -0
- package/dist/lib/csg-utils.js +33 -0
- package/dist/lib/drawable-geometry.d.ts +21 -0
- package/dist/lib/drawable-geometry.d.ts.map +1 -0
- package/dist/lib/drawable-geometry.js +22 -0
- package/dist/lib/edge-style.d.ts +3 -0
- package/dist/lib/edge-style.d.ts.map +1 -0
- package/dist/lib/edge-style.js +11 -0
- package/dist/lib/gpu-perf.d.ts +4 -0
- package/dist/lib/gpu-perf.d.ts.map +1 -0
- package/dist/lib/gpu-perf.js +23 -0
- package/dist/lib/ink-edges.d.ts +9 -0
- package/dist/lib/ink-edges.d.ts.map +1 -0
- package/dist/lib/ink-edges.js +43 -0
- package/dist/lib/isolation.d.ts +39 -0
- package/dist/lib/isolation.d.ts.map +1 -0
- package/dist/lib/isolation.js +103 -0
- package/dist/lib/layers.d.ts +18 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/materials.d.ts +25 -15
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +277 -62
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -0
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +289 -0
- package/dist/lib/polygon-union.test.d.ts +2 -0
- package/dist/lib/polygon-union.test.d.ts.map +1 -0
- package/dist/lib/polygon-union.test.js +105 -0
- package/dist/lib/scene-themes.d.ts +29 -0
- package/dist/lib/scene-themes.d.ts.map +1 -0
- package/dist/lib/scene-themes.js +193 -0
- package/dist/store/use-viewer.d.ts +42 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +26 -3
- package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +62 -12
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +373 -22
- package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
- package/dist/systems/elevator/elevator-interaction-system.js +62 -0
- package/dist/systems/fence/fence-system.d.ts +3 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +9 -5
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
- package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
- package/dist/systems/geometry/geometry-system.d.ts +34 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.js +279 -0
- package/dist/systems/interactive/interactive-system.d.ts +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.d.ts +7 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +8 -11
- 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 +18 -5
- package/dist/systems/roof/roof-materials.d.ts +3 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +38 -14
- package/dist/systems/roof/roof-system.d.ts +27 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +396 -59
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +167 -49
- package/dist/systems/slab/slab-system.test.d.ts +2 -0
- package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.test.js +62 -0
- package/dist/systems/stair/stair-materials.d.ts +4 -3
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +30 -15
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +50 -61
- package/dist/systems/surface-hole-geometry.d.ts +3 -0
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
- package/dist/systems/surface-hole-geometry.js +4 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +18 -2
- package/dist/systems/wall/wall-materials.d.ts +4 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +52 -28
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +171 -26
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -7
- package/package.json +3 -4
- package/dist/components/renderers/building/building-renderer.d.ts +0 -5
- package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
- package/dist/components/renderers/building/building-renderer.js +0 -11
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
- package/dist/components/renderers/column/column-renderer.d.ts +0 -5
- package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
- package/dist/components/renderers/column/column-renderer.js +0 -597
- package/dist/components/renderers/door/door-renderer.d.ts +0 -5
- package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
- package/dist/components/renderers/door/door-renderer.js +0 -16
- package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
- package/dist/components/renderers/fence/fence-renderer.js +0 -15
- package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
- package/dist/components/renderers/guide/guide-renderer.js +0 -38
- package/dist/components/renderers/item/item-renderer.d.ts +0 -5
- package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
- package/dist/components/renderers/item/item-renderer.js +0 -172
- package/dist/components/renderers/level/level-renderer.d.ts +0 -5
- package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
- package/dist/components/renderers/level/level-renderer.js +0 -11
- package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
- package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-materials.js +0 -16
- package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof/roof-renderer.js +0 -32
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
- package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
- package/dist/components/renderers/scan/scan-renderer.js +0 -54
- package/dist/components/renderers/site/site-renderer.d.ts +0 -5
- package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
- package/dist/components/renderers/site/site-renderer.js +0 -106
- package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
- package/dist/components/renderers/slab/slab-renderer.js +0 -58
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
- package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
- package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair/stair-renderer.js +0 -603
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
- package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
- package/dist/components/renderers/wall/wall-renderer.js +0 -16
- package/dist/components/renderers/window/window-renderer.d.ts +0 -5
- package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
- package/dist/components/renderers/window/window-renderer.js +0 -21
- package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
- package/dist/components/renderers/zone/zone-renderer.js +0 -181
|
@@ -111,6 +111,7 @@ function createFenceParts(fence) {
|
|
|
111
111
|
const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
|
|
112
112
|
const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
|
|
113
113
|
const isFloating = fence.baseStyle === 'floating';
|
|
114
|
+
const showInfill = fence.showInfill ?? true;
|
|
114
115
|
const baseY = isFloating ? clearance : 0;
|
|
115
116
|
const effectiveBaseHeight = baseHeight;
|
|
116
117
|
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
@@ -119,16 +120,17 @@ function createFenceParts(fence) {
|
|
|
119
120
|
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
|
|
120
121
|
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
|
|
121
122
|
}
|
|
122
|
-
const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
|
|
123
|
+
const count = showInfill ? Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1) : 2;
|
|
123
124
|
const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
|
|
124
125
|
for (let index = 0; index < count; index += 1) {
|
|
125
126
|
const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
|
|
126
127
|
const frame = getFencePointAt(fence, t);
|
|
127
128
|
const isEdgePost = index === 0 || index === count - 1;
|
|
128
|
-
const
|
|
129
|
+
const fullHeightPost = !showInfill || (isFloating && isEdgePost);
|
|
130
|
+
const postHeight = fullHeightPost
|
|
129
131
|
? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
|
|
130
132
|
: verticalHeight;
|
|
131
|
-
const postY =
|
|
133
|
+
const postY = fullHeightPost ? postHeight / 2 : verticalY;
|
|
132
134
|
parts.push({
|
|
133
135
|
position: [frame.point.x, postY, frame.point.y],
|
|
134
136
|
rotationY: -frame.tangentAngle,
|
|
@@ -141,11 +143,13 @@ function createFenceParts(fence) {
|
|
|
141
143
|
}
|
|
142
144
|
return parts;
|
|
143
145
|
}
|
|
144
|
-
function generateFenceGeometry(fence) {
|
|
146
|
+
export function generateFenceGeometry(fence) {
|
|
145
147
|
const parts = createFenceParts(fence);
|
|
146
148
|
const geometries = parts.map(createFencePartGeometry);
|
|
147
149
|
const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
|
|
148
|
-
geometries.forEach((geometry) =>
|
|
150
|
+
geometries.forEach((geometry) => {
|
|
151
|
+
geometry.dispose();
|
|
152
|
+
});
|
|
149
153
|
const mergedUv = merged.getAttribute('uv');
|
|
150
154
|
if (mergedUv) {
|
|
151
155
|
merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic floor-elevation system.
|
|
3
|
+
*
|
|
4
|
+
* Walks `dirtyNodes` and, for any kind that declares
|
|
5
|
+
* `capabilities.floorPlaced`, lifts the registered mesh's Y by whatever
|
|
6
|
+
* slab the footprint overlaps. Items / shelves / etc. that sit directly
|
|
7
|
+
* on a level pick this up automatically — no per-kind elevation logic.
|
|
8
|
+
*
|
|
9
|
+
* Skips nodes whose parent is not a level (items hosted on shelves /
|
|
10
|
+
* tables inherit Y from the parent group), and respects
|
|
11
|
+
* `floorPlaced.applies` so items with `asset.attachTo` (wall / ceiling
|
|
12
|
+
* mounted) are left alone.
|
|
13
|
+
*
|
|
14
|
+
* Runs at priority 1 — before the priority-2 systems (`GeometrySystem`,
|
|
15
|
+
* `ItemSystem`) so the dirty mark survives long enough for those to do
|
|
16
|
+
* their own work. Kinds with no geometry/system have no downstream dirty
|
|
17
|
+
* consumer, so this system clears their dirty mark after applying the lift.
|
|
18
|
+
*/
|
|
19
|
+
export declare const FloorElevationSystem: () => null;
|
|
20
|
+
//# sourceMappingURL=floor-elevation-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AAwCA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,YA6ChC,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getEffectiveNode, getFloorStackedPosition, nodeRegistry, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
function withLiveTransform(node, id) {
|
|
4
|
+
const liveTransform = useLiveTransforms.getState().get(id);
|
|
5
|
+
if (!liveTransform)
|
|
6
|
+
return node;
|
|
7
|
+
const currentRotation = node.rotation;
|
|
8
|
+
const rotation = Array.isArray(currentRotation)
|
|
9
|
+
? [currentRotation[0] ?? 0, liveTransform.rotation, currentRotation[2] ?? 0]
|
|
10
|
+
: typeof currentRotation === 'number'
|
|
11
|
+
? liveTransform.rotation
|
|
12
|
+
: currentRotation;
|
|
13
|
+
return {
|
|
14
|
+
...node,
|
|
15
|
+
position: liveTransform.position,
|
|
16
|
+
...(rotation !== undefined ? { rotation } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generic floor-elevation system.
|
|
21
|
+
*
|
|
22
|
+
* Walks `dirtyNodes` and, for any kind that declares
|
|
23
|
+
* `capabilities.floorPlaced`, lifts the registered mesh's Y by whatever
|
|
24
|
+
* slab the footprint overlaps. Items / shelves / etc. that sit directly
|
|
25
|
+
* on a level pick this up automatically — no per-kind elevation logic.
|
|
26
|
+
*
|
|
27
|
+
* Skips nodes whose parent is not a level (items hosted on shelves /
|
|
28
|
+
* tables inherit Y from the parent group), and respects
|
|
29
|
+
* `floorPlaced.applies` so items with `asset.attachTo` (wall / ceiling
|
|
30
|
+
* mounted) are left alone.
|
|
31
|
+
*
|
|
32
|
+
* Runs at priority 1 — before the priority-2 systems (`GeometrySystem`,
|
|
33
|
+
* `ItemSystem`) so the dirty mark survives long enough for those to do
|
|
34
|
+
* their own work. Kinds with no geometry/system have no downstream dirty
|
|
35
|
+
* consumer, so this system clears their dirty mark after applying the lift.
|
|
36
|
+
*/
|
|
37
|
+
export const FloorElevationSystem = () => {
|
|
38
|
+
const dirtyNodes = useScene((s) => s.dirtyNodes);
|
|
39
|
+
const clearDirty = useScene((s) => s.clearDirty);
|
|
40
|
+
useFrame(() => {
|
|
41
|
+
if (dirtyNodes.size === 0)
|
|
42
|
+
return;
|
|
43
|
+
const nodes = useScene.getState().nodes;
|
|
44
|
+
dirtyNodes.forEach((id) => {
|
|
45
|
+
const node = nodes[id];
|
|
46
|
+
if (!node)
|
|
47
|
+
return;
|
|
48
|
+
const def = nodeRegistry.get(node.type);
|
|
49
|
+
const floorPlaced = def?.capabilities?.floorPlaced;
|
|
50
|
+
if (!floorPlaced)
|
|
51
|
+
return;
|
|
52
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
53
|
+
if (!mesh)
|
|
54
|
+
return;
|
|
55
|
+
const effectiveNode = withLiveTransform(getEffectiveNode(node), id);
|
|
56
|
+
const position = effectiveNode.position;
|
|
57
|
+
if (!position)
|
|
58
|
+
return;
|
|
59
|
+
// This system is the single drag-time authority for floor-stack mesh Y:
|
|
60
|
+
// tools publish base positions to live stores, renderers may
|
|
61
|
+
// reconcile that base Y onto the group, then this presentation system
|
|
62
|
+
// reapplies the resolver-derived visual Y before render. Because the
|
|
63
|
+
// override/store position remains base-height, the slab lift is never
|
|
64
|
+
// committed or applied twice.
|
|
65
|
+
const resolverNodes = effectiveNode === node ? nodes : { ...nodes, [effectiveNode.id]: effectiveNode };
|
|
66
|
+
const visualPosition = getFloorStackedPosition({
|
|
67
|
+
node: effectiveNode,
|
|
68
|
+
nodes: resolverNodes,
|
|
69
|
+
position,
|
|
70
|
+
});
|
|
71
|
+
mesh.position.y = visualPosition[1];
|
|
72
|
+
if (!(def.geometry || def.system)) {
|
|
73
|
+
clearDirty(id);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}, 1);
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic geometry system.
|
|
3
|
+
*
|
|
4
|
+
* For every node in `dirtyNodes` whose definition exposes `def.geometry`,
|
|
5
|
+
* this system:
|
|
6
|
+
* 1. Looks up the registered `Group` from `sceneRegistry` (mounted by the
|
|
7
|
+
* framework's `<ParametricNodeRenderer>`, or a custom renderer that
|
|
8
|
+
* opts into the same mount contract).
|
|
9
|
+
* 2. Builds a `GeometryContext` from the current scene snapshot.
|
|
10
|
+
* 3. Calls `def.geometry(node, ctx)` to get the new `Object3D`.
|
|
11
|
+
* 4. Disposes the registered group's existing children + their geometries
|
|
12
|
+
* and materials.
|
|
13
|
+
* 5. Reparents the returned object's children onto the registered group.
|
|
14
|
+
* 6. Clears the dirty flag.
|
|
15
|
+
*
|
|
16
|
+
* This is the "no per-kind system needed" path documented in
|
|
17
|
+
* `wiki/architecture/node-definitions.md`. A kind that only rebuilds on
|
|
18
|
+
* dirty (shelf, item, fence segment, etc.) ships nothing more than a pure
|
|
19
|
+
* `geometry` function — no `renderer.tsx`, no `system.tsx`.
|
|
20
|
+
*
|
|
21
|
+
* Kinds with `def.system` declared run their own systems *in addition* to
|
|
22
|
+
* this one — animation + cascade + named-mesh material poking stay
|
|
23
|
+
* kind-specific.
|
|
24
|
+
*
|
|
25
|
+
* Frame priority 2 mirrors the per-kind shelf system it replaces. Door
|
|
26
|
+
* animation systems run at priority 2 today too, marking dirty so the
|
|
27
|
+
* geometry rebuild lands at priority 3-4 next frame. Door/window/wall
|
|
28
|
+
* still have their own systems (they need cross-cutting work this system
|
|
29
|
+
* doesn't cover) — they coexist; this system only acts on kinds that
|
|
30
|
+
* declare `def.geometry`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const GeometrySystem: () => null;
|
|
33
|
+
export default GeometrySystem;
|
|
34
|
+
//# sourceMappingURL=geometry-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YAwK1B,CAAA;AAyHD,eAAe,cAAc,CAAA"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { getEffectiveNode, nodeRegistry, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { useFrame } from '@react-three/fiber';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
import { FrontSide } from 'three';
|
|
6
|
+
import { createSurfaceRoleMaterial, } from '../../lib/materials';
|
|
7
|
+
import useViewer from '../../store/use-viewer';
|
|
8
|
+
/**
|
|
9
|
+
* Generic geometry system.
|
|
10
|
+
*
|
|
11
|
+
* For every node in `dirtyNodes` whose definition exposes `def.geometry`,
|
|
12
|
+
* this system:
|
|
13
|
+
* 1. Looks up the registered `Group` from `sceneRegistry` (mounted by the
|
|
14
|
+
* framework's `<ParametricNodeRenderer>`, or a custom renderer that
|
|
15
|
+
* opts into the same mount contract).
|
|
16
|
+
* 2. Builds a `GeometryContext` from the current scene snapshot.
|
|
17
|
+
* 3. Calls `def.geometry(node, ctx)` to get the new `Object3D`.
|
|
18
|
+
* 4. Disposes the registered group's existing children + their geometries
|
|
19
|
+
* and materials.
|
|
20
|
+
* 5. Reparents the returned object's children onto the registered group.
|
|
21
|
+
* 6. Clears the dirty flag.
|
|
22
|
+
*
|
|
23
|
+
* This is the "no per-kind system needed" path documented in
|
|
24
|
+
* `wiki/architecture/node-definitions.md`. A kind that only rebuilds on
|
|
25
|
+
* dirty (shelf, item, fence segment, etc.) ships nothing more than a pure
|
|
26
|
+
* `geometry` function — no `renderer.tsx`, no `system.tsx`.
|
|
27
|
+
*
|
|
28
|
+
* Kinds with `def.system` declared run their own systems *in addition* to
|
|
29
|
+
* this one — animation + cascade + named-mesh material poking stay
|
|
30
|
+
* kind-specific.
|
|
31
|
+
*
|
|
32
|
+
* Frame priority 2 mirrors the per-kind shelf system it replaces. Door
|
|
33
|
+
* animation systems run at priority 2 today too, marking dirty so the
|
|
34
|
+
* geometry rebuild lands at priority 3-4 next frame. Door/window/wall
|
|
35
|
+
* still have their own systems (they need cross-cutting work this system
|
|
36
|
+
* doesn't cover) — they coexist; this system only acts on kinds that
|
|
37
|
+
* declare `def.geometry`.
|
|
38
|
+
*/
|
|
39
|
+
export const GeometrySystem = () => {
|
|
40
|
+
const dirtyNodes = useScene((s) => s.dirtyNodes);
|
|
41
|
+
const clearDirty = useScene((s) => s.clearDirty);
|
|
42
|
+
const shading = useViewer((s) => s.shading);
|
|
43
|
+
const textures = useViewer((s) => s.textures);
|
|
44
|
+
const colorPreset = useViewer((s) => s.colorPreset);
|
|
45
|
+
const sceneTheme = useViewer((s) => s.sceneTheme);
|
|
46
|
+
// Per-node cache of the last-built geometry key (for kinds that declare
|
|
47
|
+
// `def.geometryKey`). Lets us skip a dispose+rebuild when a node is dirty
|
|
48
|
+
// but its geometry inputs are unchanged — e.g. an item reparenting onto a
|
|
49
|
+
// shelf dirties the shelf without altering its boards.
|
|
50
|
+
const builtGeometryKeyRef = useRef(new Map());
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const nodes = useScene.getState().nodes;
|
|
53
|
+
for (const node of Object.values(nodes)) {
|
|
54
|
+
const def = nodeRegistry.get(node.type);
|
|
55
|
+
if (def?.geometry) {
|
|
56
|
+
useScene.getState().markDirty(node.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [shading, textures, colorPreset, sceneTheme]);
|
|
60
|
+
useFrame(() => {
|
|
61
|
+
if (dirtyNodes.size === 0)
|
|
62
|
+
return;
|
|
63
|
+
const nodes = useScene.getState().nodes;
|
|
64
|
+
const batches = new Map();
|
|
65
|
+
const dirtyIds = [];
|
|
66
|
+
dirtyNodes.forEach((id) => {
|
|
67
|
+
const node = nodes[id];
|
|
68
|
+
if (!node)
|
|
69
|
+
return;
|
|
70
|
+
const def = nodeRegistry.get(node.type);
|
|
71
|
+
if (!def?.geometry)
|
|
72
|
+
return;
|
|
73
|
+
dirtyIds.push(id);
|
|
74
|
+
const parentId = (node.parentId ?? null);
|
|
75
|
+
const key = `${node.type}::${parentId ?? ''}`;
|
|
76
|
+
const existing = batches.get(key);
|
|
77
|
+
if (existing)
|
|
78
|
+
existing.ids.push(id);
|
|
79
|
+
else
|
|
80
|
+
batches.set(key, { kind: node.type, parentId, ids: [id] });
|
|
81
|
+
});
|
|
82
|
+
// Phase 2 — for each batch whose kind declares `computeLevelData`,
|
|
83
|
+
// collect every sibling in the level + run the precompute once.
|
|
84
|
+
const levelDataByBatch = new Map();
|
|
85
|
+
for (const [key, batch] of batches) {
|
|
86
|
+
const def = nodeRegistry.get(batch.kind);
|
|
87
|
+
if (!def?.computeLevelData)
|
|
88
|
+
continue;
|
|
89
|
+
const siblings = [];
|
|
90
|
+
if (batch.parentId) {
|
|
91
|
+
const parent = nodes[batch.parentId];
|
|
92
|
+
const childIds = parent?.children;
|
|
93
|
+
if (Array.isArray(childIds)) {
|
|
94
|
+
for (const cid of childIds) {
|
|
95
|
+
const child = nodes[cid];
|
|
96
|
+
if (child?.type === batch.kind)
|
|
97
|
+
siblings.push(child);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
for (const node of Object.values(nodes)) {
|
|
103
|
+
if (node?.type === batch.kind && !node.parentId)
|
|
104
|
+
siblings.push(node);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
levelDataByBatch.set(key, def.computeLevelData(siblings));
|
|
108
|
+
}
|
|
109
|
+
// Phase 3 — per-node rebuild. Each node receives its batch's
|
|
110
|
+
// precomputed `levelData` in ctx.
|
|
111
|
+
for (const id of dirtyIds) {
|
|
112
|
+
const node = nodes[id];
|
|
113
|
+
if (!node)
|
|
114
|
+
continue;
|
|
115
|
+
const def = nodeRegistry.get(node.type);
|
|
116
|
+
const builder = def?.geometry;
|
|
117
|
+
if (!builder)
|
|
118
|
+
continue;
|
|
119
|
+
const group = sceneRegistry.nodes.get(id);
|
|
120
|
+
if (!group)
|
|
121
|
+
continue; // mount hasn't run — keep dirty for next frame
|
|
122
|
+
// Merge any live drag override into the node so resize arrows /
|
|
123
|
+
// 2D affordances see their in-flight patch on every pointer move
|
|
124
|
+
// — zustand only learns about the final value on commit. Mirrors
|
|
125
|
+
// the WallSystem / DoorSystem / WindowSystem hook-up; any kind
|
|
126
|
+
// that drives its mesh through `def.geometry` (fence, shelf, item)
|
|
127
|
+
// now smooths drags through this single line.
|
|
128
|
+
const effectiveNode = getEffectiveNode(node);
|
|
129
|
+
// Skip the rebuild when the geometry inputs are unchanged (kinds that
|
|
130
|
+
// opt in via `def.geometryKey`). Fold in the global rendering inputs so
|
|
131
|
+
// a theme / shading change — which re-dirties every geometry node — is
|
|
132
|
+
// never skipped. This kills the board remount + pointer enter/leave
|
|
133
|
+
// churn when an item reparents onto a shelf.
|
|
134
|
+
if (def.geometryKey) {
|
|
135
|
+
const builtKey = `${shading}|${textures}|${colorPreset}|${sceneTheme}|${def.geometryKey(effectiveNode)}`;
|
|
136
|
+
if (builtGeometryKeyRef.current.get(id) === builtKey) {
|
|
137
|
+
clearDirty(id);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
builtGeometryKeyRef.current.set(id, builtKey);
|
|
141
|
+
}
|
|
142
|
+
const parentId = (node.parentId ?? null);
|
|
143
|
+
const key = `${node.type}::${parentId ?? ''}`;
|
|
144
|
+
const levelData = levelDataByBatch.get(key);
|
|
145
|
+
const ctx = buildGeometryContext(effectiveNode, nodes, levelData);
|
|
146
|
+
// The builder is typed against the kind's specific node — at the
|
|
147
|
+
// generic system level we lose that refinement, so the cast lands
|
|
148
|
+
// here. Builders are responsible for trusting their schema.
|
|
149
|
+
const built = builder(effectiveNode, ctx, shading, textures, colorPreset, sceneTheme);
|
|
150
|
+
if (!textures && def.surfaceRole) {
|
|
151
|
+
applyDefaultSurfaceRole(built, def.surfaceRole, colorPreset, sceneTheme);
|
|
152
|
+
}
|
|
153
|
+
disposeChildren(group);
|
|
154
|
+
for (const child of [...built.children]) {
|
|
155
|
+
// Tag every child the builder produced so a subsequent rebuild
|
|
156
|
+
// can dispose only THIS rebuild's outputs and leave React-
|
|
157
|
+
// mounted siblings (hosted items inside a shelf / slab / etc.)
|
|
158
|
+
// alone. Without this, a parent rebuild triggered by a child
|
|
159
|
+
// event (e.g. an item reparenting onto a shelf calls
|
|
160
|
+
// `dirtyNodes.add(parent)` in `ItemRenderer`'s effect) would
|
|
161
|
+
// wipe ALL of the parent group's children — including the
|
|
162
|
+
// freshly-mounted item — leaving the item in scene state but
|
|
163
|
+
// invisible.
|
|
164
|
+
;
|
|
165
|
+
child.userData = {
|
|
166
|
+
...child.userData,
|
|
167
|
+
__fromGeometry: true,
|
|
168
|
+
};
|
|
169
|
+
group.add(child);
|
|
170
|
+
}
|
|
171
|
+
// NOTE: we intentionally do NOT reset `group.position` / `group.rotation`
|
|
172
|
+
// here. The `ParametricNodeRenderer` binds them via JSX (`position={...}`
|
|
173
|
+
// / `rotation={...}`) driven by `useLiveTransforms` during drag and
|
|
174
|
+
// `node.position` / `node.rotation` after commit. Zeroing them out
|
|
175
|
+
// during a rebuild would clobber the React-applied transform — and
|
|
176
|
+
// because the renderer doesn't necessarily re-render on the rebuild
|
|
177
|
+
// tick, R3F wouldn't re-apply the props, leaving the group stuck at
|
|
178
|
+
// origin. Geometry builders are expected to emit local-space children.
|
|
179
|
+
clearDirty(id);
|
|
180
|
+
}
|
|
181
|
+
}, 2);
|
|
182
|
+
return null;
|
|
183
|
+
};
|
|
184
|
+
function buildGeometryContext(node, nodes, levelData) {
|
|
185
|
+
const resolve = (id) => nodes[id];
|
|
186
|
+
const childIds = node.children;
|
|
187
|
+
const children = Array.isArray(childIds)
|
|
188
|
+
? childIds.map((cid) => nodes[cid]).filter((n) => n !== undefined)
|
|
189
|
+
: [];
|
|
190
|
+
const parentId = node.parentId;
|
|
191
|
+
const parent = parentId ? (nodes[parentId] ?? null) : null;
|
|
192
|
+
// Siblings = same kind, same parent, excluding self. Walks the parent's
|
|
193
|
+
// children array; falls back to scanning the whole scene if the parent
|
|
194
|
+
// doesn't carry a `children` list (rare — most parents do).
|
|
195
|
+
let siblings = [];
|
|
196
|
+
if (parent) {
|
|
197
|
+
const parentChildIds = parent.children;
|
|
198
|
+
if (Array.isArray(parentChildIds)) {
|
|
199
|
+
for (const sid of parentChildIds) {
|
|
200
|
+
if (sid === node.id)
|
|
201
|
+
continue;
|
|
202
|
+
const s = nodes[sid];
|
|
203
|
+
if (s && s.type === node.type)
|
|
204
|
+
siblings.push(s);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
siblings = Object.values(nodes).filter((n) => n !== node && n.type === node.type && n.parentId === parentId);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { resolve, children, siblings, parent, levelData };
|
|
212
|
+
}
|
|
213
|
+
function disposeChildren(group) {
|
|
214
|
+
// Only dispose meshes the geometry builder produced on the previous
|
|
215
|
+
// rebuild (marked via `userData.__fromGeometry`). React-managed
|
|
216
|
+
// children (hosted node renderers) get left in place — they have
|
|
217
|
+
// their own React-driven lifecycle and would lose their meshes /
|
|
218
|
+
// materials if we disposed them here.
|
|
219
|
+
for (const child of [...group.children]) {
|
|
220
|
+
const fromGeometry = child.userData
|
|
221
|
+
?.__fromGeometry;
|
|
222
|
+
if (!fromGeometry)
|
|
223
|
+
continue;
|
|
224
|
+
group.remove(child);
|
|
225
|
+
const mesh = child;
|
|
226
|
+
if (mesh.geometry?.dispose)
|
|
227
|
+
mesh.geometry.dispose();
|
|
228
|
+
if ('material' in mesh) {
|
|
229
|
+
const m = mesh.material;
|
|
230
|
+
if (Array.isArray(m)) {
|
|
231
|
+
for (const mat of m) {
|
|
232
|
+
if (isCachedMaterial(mat))
|
|
233
|
+
continue;
|
|
234
|
+
if (mat && typeof mat.dispose === 'function') {
|
|
235
|
+
;
|
|
236
|
+
mat.dispose();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else if (isCachedMaterial(m)) {
|
|
241
|
+
}
|
|
242
|
+
else if (m && typeof m.dispose === 'function') {
|
|
243
|
+
;
|
|
244
|
+
m.dispose();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function applyDefaultSurfaceRole(root, defaultRole, colorPreset, sceneTheme) {
|
|
250
|
+
root.traverse((child) => {
|
|
251
|
+
const mesh = child;
|
|
252
|
+
if (!('material' in mesh) || !mesh.material)
|
|
253
|
+
return;
|
|
254
|
+
const role = getMeshSurfaceRole(mesh.userData.surfaceRole, defaultRole);
|
|
255
|
+
mesh.userData.surfaceRole = role;
|
|
256
|
+
mesh.material = createSurfaceRoleMaterial(role, colorPreset, getMaterialSide(mesh.material), sceneTheme);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function getMeshSurfaceRole(value, fallback) {
|
|
260
|
+
return typeof value === 'string' && isSurfaceRole(value) ? value : fallback;
|
|
261
|
+
}
|
|
262
|
+
function isSurfaceRole(value) {
|
|
263
|
+
return (value === 'wall' ||
|
|
264
|
+
value === 'floor' ||
|
|
265
|
+
value === 'ceiling' ||
|
|
266
|
+
value === 'roof' ||
|
|
267
|
+
value === 'joinery' ||
|
|
268
|
+
value === 'glazing' ||
|
|
269
|
+
value === 'furnishing');
|
|
270
|
+
}
|
|
271
|
+
function getMaterialSide(material) {
|
|
272
|
+
const source = Array.isArray(material) ? material[0] : material;
|
|
273
|
+
return source?.side ?? FrontSide;
|
|
274
|
+
}
|
|
275
|
+
function isCachedMaterial(value) {
|
|
276
|
+
return Boolean(value?.userData
|
|
277
|
+
?.__pascalCachedMaterial);
|
|
278
|
+
}
|
|
279
|
+
export default GeometrySystem;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const InteractiveSystem: () => import("react
|
|
1
|
+
export declare const InteractiveSystem: () => import("react").JSX.Element;
|
|
2
2
|
//# sourceMappingURL=interactive-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-system.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/interactive-system.tsx"],"names":[],"mappings":"AAwBA,eAAO,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"interactive-system.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/interactive-system.tsx"],"names":[],"mappings":"AAwBA,eAAO,MAAM,iBAAiB,mCAgB7B,CAAA"}
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-frame wall-side offset for items mounted to wall faces. The slab-
|
|
3
|
+
* elevation lift for floor items lives in the generic
|
|
4
|
+
* `<FloorElevationSystem>` and runs at priority 1 — it has already
|
|
5
|
+
* landed `mesh.position.y` by the time this system clears the dirty
|
|
6
|
+
* mark at priority 2.
|
|
7
|
+
*/
|
|
1
8
|
export declare const ItemSystem: () => null;
|
|
2
9
|
//# sourceMappingURL=item-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,YA+BtB,CAAA"}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
// ============================================================================
|
|
4
4
|
// ITEM SYSTEM
|
|
5
5
|
// ============================================================================
|
|
6
|
+
/**
|
|
7
|
+
* Per-frame wall-side offset for items mounted to wall faces. The slab-
|
|
8
|
+
* elevation lift for floor items lives in the generic
|
|
9
|
+
* `<FloorElevationSystem>` and runs at priority 1 — it has already
|
|
10
|
+
* landed `mesh.position.y` by the time this system clears the dirty
|
|
11
|
+
* mark at priority 2.
|
|
12
|
+
*/
|
|
6
13
|
export const ItemSystem = () => {
|
|
7
14
|
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
8
15
|
const clearDirty = useScene((state) => state.clearDirty);
|
|
@@ -27,16 +34,6 @@ export const ItemSystem = () => {
|
|
|
27
34
|
mesh.position.z = (wallThickness / 2) * side;
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
|
-
else if (!item.asset.attachTo) {
|
|
31
|
-
// If parented to another item (surface placement), R3F handles positioning via the hierarchy
|
|
32
|
-
const parentNode = item.parentId ? nodes[item.parentId] : undefined;
|
|
33
|
-
if (parentNode?.type !== 'item') {
|
|
34
|
-
// Floor item: elevate by slab height (using full footprint overlap)
|
|
35
|
-
const levelId = resolveLevelId(item, nodes);
|
|
36
|
-
const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, getScaledDimensions(item), item.rotation);
|
|
37
|
-
mesh.position.y = slabElevation + item.position[1];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
37
|
clearDirty(id);
|
|
41
38
|
});
|
|
42
39
|
}, 2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function ItemLightSystem(): import("react
|
|
1
|
+
export declare function ItemLightSystem(): import("react").JSX.Element;
|
|
2
2
|
//# sourceMappingURL=item-light-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"AAuFA,wBAAgB,eAAe,gCA8N9B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { sceneRegistry, useInteractive, useScene } from '@pascal-app/core';
|
|
2
|
+
import { findLevelAncestorId, sceneRegistry, useInteractive, useScene } from '@pascal-app/core';
|
|
3
3
|
import { useFrame } from '@react-three/fiber';
|
|
4
4
|
import { useRef } from 'react';
|
|
5
5
|
import { MathUtils, Vector3 } from 'three';
|
|
@@ -42,8 +42,7 @@ function scoreRegistration(reg, nodes, selectedLevelId, levelMode, interactiveSt
|
|
|
42
42
|
// Normalised distance component (assumes scenes < 200 units)
|
|
43
43
|
const dist = _camPos.distanceTo(_itemPos) / 200;
|
|
44
44
|
// ── Level factor ──────────────────────────────────────────────────────────
|
|
45
|
-
const
|
|
46
|
-
const itemLevelId = node?.parentId ?? null;
|
|
45
|
+
const itemLevelId = findLevelAncestorId(nodeId, nodes);
|
|
47
46
|
let levelPenalty = 0;
|
|
48
47
|
if (selectedLevelId) {
|
|
49
48
|
if (itemLevelId !== selectedLevelId) {
|
|
@@ -97,7 +96,10 @@ export function ItemLightSystem() {
|
|
|
97
96
|
}
|
|
98
97
|
scored.sort((a, b) => a.score - b.score);
|
|
99
98
|
// Build the desired assignment (top POOL_SIZE keys)
|
|
100
|
-
const desired = scored
|
|
99
|
+
const desired = scored
|
|
100
|
+
.filter((s) => Number.isFinite(s.score))
|
|
101
|
+
.slice(0, POOL_SIZE)
|
|
102
|
+
.map((s) => s.key);
|
|
101
103
|
// Build a map of currently-assigned keys → slot index for hysteresis
|
|
102
104
|
const currentlyAssigned = new Map();
|
|
103
105
|
for (let i = 0; i < POOL_SIZE; i++) {
|
|
@@ -181,9 +183,11 @@ export function ItemLightSystem() {
|
|
|
181
183
|
continue;
|
|
182
184
|
// Fade-out phase: lerp intensity → 0, then complete the transition
|
|
183
185
|
if (slot.isFadingOut) {
|
|
186
|
+
light.visible = true;
|
|
184
187
|
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
|
|
185
188
|
if (light.intensity < 0.01) {
|
|
186
189
|
light.intensity = 0;
|
|
190
|
+
light.visible = false;
|
|
187
191
|
slot.isFadingOut = false;
|
|
188
192
|
slot.key = slot.pendingKey;
|
|
189
193
|
slot.pendingKey = null;
|
|
@@ -200,12 +204,14 @@ export function ItemLightSystem() {
|
|
|
200
204
|
if (!slot.key) {
|
|
201
205
|
// Idle slot — keep dark
|
|
202
206
|
light.intensity = 0;
|
|
207
|
+
light.visible = false;
|
|
203
208
|
continue;
|
|
204
209
|
}
|
|
205
210
|
const reg = registrations.get(slot.key);
|
|
206
211
|
if (!reg) {
|
|
207
212
|
slot.key = null;
|
|
208
213
|
light.intensity = 0;
|
|
214
|
+
light.visible = false;
|
|
209
215
|
continue;
|
|
210
216
|
}
|
|
211
217
|
// Snap world position each frame
|
|
@@ -226,10 +232,17 @@ export function ItemLightSystem() {
|
|
|
226
232
|
const targetIntensity = isOn
|
|
227
233
|
? MathUtils.lerp(reg.effect.intensityRange[0], reg.effect.intensityRange[1], t)
|
|
228
234
|
: reg.effect.intensityRange[0];
|
|
235
|
+
if (targetIntensity > 0) {
|
|
236
|
+
light.visible = true;
|
|
237
|
+
}
|
|
229
238
|
light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
|
|
239
|
+
if (targetIntensity <= 0 && light.intensity < 0.01) {
|
|
240
|
+
light.intensity = 0;
|
|
241
|
+
light.visible = false;
|
|
242
|
+
}
|
|
230
243
|
}
|
|
231
244
|
});
|
|
232
|
-
return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, ref: (el) => {
|
|
245
|
+
return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, visible: false, ref: (el) => {
|
|
233
246
|
lightRefs.current[i] = el;
|
|
234
247
|
} }, i))) }));
|
|
235
248
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RoofNode } from '@pascal-app/core';
|
|
2
|
-
import * as THREE from 'three';
|
|
2
|
+
import type * as THREE from 'three';
|
|
3
|
+
import { type ColorPreset, type RenderShading } from '../../lib/materials';
|
|
3
4
|
export type RoofMaterialArray = [THREE.Material, THREE.Material, THREE.Material, THREE.Material];
|
|
4
|
-
export declare function getRoofMaterialArray(node: RoofNode): RoofMaterialArray | null;
|
|
5
|
+
export declare function getRoofMaterialArray(node: RoofNode, shading?: RenderShading, textures?: boolean, colorPreset?: ColorPreset, sceneTheme?: string): RoofMaterialArray | null;
|
|
5
6
|
//# sourceMappingURL=roof-materials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AACnC,OAAO,EACL,KAAK,WAAW,EAIhB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AA6BhG,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,aAA0B,EACnC,QAAQ,UAAO,EACf,WAAW,GAAE,WAAoB,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,iBAAiB,GAAG,IAAI,CA0D1B"}
|