@pascal-app/viewer 0.1.13 → 0.2.0
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/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +16 -9
- package/dist/components/renderers/door/door-renderer.d.ts +5 -0
- package/dist/components/renderers/door/door-renderer.d.ts.map +1 -0
- package/dist/components/renderers/door/door-renderer.js +11 -0
- package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -1
- package/dist/components/renderers/guide/guide-renderer.js +4 -2
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +92 -7
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +3 -1
- package/dist/components/renderers/roof/roof-materials.d.ts +4 -0
- package/dist/components/renderers/roof/roof-materials.d.ts.map +1 -0
- package/dist/components/renderers/roof/roof-materials.js +16 -0
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof/roof-renderer.js +5 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +5 -0
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -0
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +13 -0
- package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -1
- package/dist/components/renderers/scan/scan-renderer.js +3 -1
- package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
- package/dist/components/renderers/scene-renderer.js +3 -3
- package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
- package/dist/components/renderers/site/site-renderer.js +4 -19
- package/dist/components/renderers/slab/slab-renderer.js +1 -1
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
- package/dist/components/renderers/wall/wall-renderer.js +1 -1
- package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
- package/dist/components/renderers/window/window-renderer.js +2 -1
- package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
- package/dist/components/renderers/zone/zone-renderer.js +33 -13
- package/dist/components/viewer/ground-occluder.d.ts +2 -0
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -0
- package/dist/components/viewer/ground-occluder.js +55 -0
- package/dist/components/viewer/index.d.ts +1 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +59 -6
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +69 -5
- package/dist/components/viewer/perf-monitor.d.ts +2 -0
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -0
- package/dist/components/viewer/perf-monitor.js +42 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +230 -107
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +47 -17
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +2 -2
- package/dist/hooks/use-gltf-ktx2.d.ts +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +25 -7
- package/dist/hooks/use-node-events.d.ts +9 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +5 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/lib/asset-url.d.ts +1 -1
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +2 -1
- package/dist/lib/layers.d.ts +5 -0
- package/dist/lib/layers.d.ts.map +1 -0
- package/dist/lib/layers.js +4 -0
- package/dist/store/use-item-light-pool.d.ts +18 -0
- package/dist/store/use-item-light-pool.d.ts.map +1 -0
- package/dist/store/use-item-light-pool.js +30 -0
- package/dist/store/use-viewer.d.ts +52 -7
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +79 -17
- package/dist/systems/interactive/interactive-system.d.ts +2 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -0
- package/dist/systems/interactive/interactive-system.js +95 -0
- package/dist/systems/item-light/item-light-system.d.ts +2 -0
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -0
- package/dist/systems/item-light/item-light-system.js +235 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -8
- package/dist/systems/level/level-utils.d.ts +17 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -0
- package/dist/systems/level/level-utils.js +82 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +2 -4
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +29 -3
- package/package.json +6 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,YAsCvB,CAAA"}
|
|
@@ -2,22 +2,33 @@ import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import { lerp } from 'three/src/math/MathUtils.js';
|
|
4
4
|
import useViewer from '../../store/use-viewer';
|
|
5
|
-
|
|
5
|
+
import { getLevelHeight } from './level-utils';
|
|
6
6
|
const EXPLODED_GAP = 5;
|
|
7
7
|
export const LevelSystem = () => {
|
|
8
8
|
useFrame((_, delta) => {
|
|
9
|
+
const nodes = useScene.getState().nodes;
|
|
9
10
|
const levelMode = useViewer.getState().levelMode;
|
|
10
11
|
const selectedLevel = useViewer.getState().selection.levelId;
|
|
12
|
+
const entries = [];
|
|
11
13
|
sceneRegistry.byType.level.forEach((levelId) => {
|
|
12
14
|
const obj = sceneRegistry.nodes.get(levelId);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(LEVEL_HEIGHT + (levelMode === 'exploded' ? EXPLODED_GAP : 0));
|
|
17
|
-
obj.position.y = lerp(obj.position.y, targetY, delta * 3);
|
|
18
|
-
obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel;
|
|
15
|
+
const level = nodes[levelId];
|
|
16
|
+
if (obj && level) {
|
|
17
|
+
entries.push({ levelId, index: level.level ?? 0, obj });
|
|
19
18
|
}
|
|
20
19
|
});
|
|
21
|
-
|
|
20
|
+
entries.sort((a, b) => a.index - b.index);
|
|
21
|
+
// Walk sorted levels, accumulating base Y offsets
|
|
22
|
+
let cumulativeY = 0;
|
|
23
|
+
for (const { levelId, index, obj } of entries) {
|
|
24
|
+
const level = nodes[levelId];
|
|
25
|
+
const baseY = cumulativeY;
|
|
26
|
+
const explodedExtra = levelMode === 'exploded' ? index * EXPLODED_GAP : 0;
|
|
27
|
+
const targetY = baseY + explodedExtra;
|
|
28
|
+
obj.position.y = lerp(obj.position.y, targetY, delta * 12); // Smoothly animate to new Y position
|
|
29
|
+
obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel;
|
|
30
|
+
cumulativeY += getLevelHeight(levelId, nodes);
|
|
31
|
+
}
|
|
32
|
+
}, 5); // Using a lower priority so it runs after transforms from other systems have settled
|
|
22
33
|
return null;
|
|
23
34
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useScene } from '@pascal-app/core';
|
|
2
|
+
export declare const DEFAULT_LEVEL_HEIGHT = 2.5;
|
|
3
|
+
export declare function getLevelHeight(levelId: string, nodes: ReturnType<typeof useScene.getState>['nodes']): number;
|
|
4
|
+
/**
|
|
5
|
+
* Instantly snaps all level Objects3D to their true stacked Y positions
|
|
6
|
+
* (ignores levelMode — always uses stacked, no exploded gap).
|
|
7
|
+
*
|
|
8
|
+
* Returns a restore function that reverts each level's Y to what it was
|
|
9
|
+
* before the snap, so lerp animations in LevelSystem can continue undisturbed.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const restore = snapLevelsToTruePositions()
|
|
13
|
+
* renderer.render(scene, camera)
|
|
14
|
+
* restore()
|
|
15
|
+
*/
|
|
16
|
+
export declare function snapLevelsToTruePositions(): () => void;
|
|
17
|
+
//# sourceMappingURL=level-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"level-utils.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,QAAQ,EAET,MAAM,kBAAkB,CAAA;AAEzB,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAQvC,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GACnD,MAAM,CA8BR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,IAAI,CAyCtD"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
|
+
export const DEFAULT_LEVEL_HEIGHT = 2.5;
|
|
3
|
+
// Cache: levelId → computed height. Invalidated when the nodes reference changes.
|
|
4
|
+
// Zustand produces a new `nodes` object on every mutation, so reference equality
|
|
5
|
+
// is a zero-cost way to detect stale data without any subscription overhead.
|
|
6
|
+
const heightCache = new Map();
|
|
7
|
+
let lastNodesRef = null;
|
|
8
|
+
export function getLevelHeight(levelId, nodes) {
|
|
9
|
+
if (nodes !== lastNodesRef) {
|
|
10
|
+
heightCache.clear();
|
|
11
|
+
lastNodesRef = nodes;
|
|
12
|
+
}
|
|
13
|
+
if (heightCache.has(levelId))
|
|
14
|
+
return heightCache.get(levelId);
|
|
15
|
+
const level = nodes[levelId];
|
|
16
|
+
if (!level)
|
|
17
|
+
return DEFAULT_LEVEL_HEIGHT;
|
|
18
|
+
let maxTop = 0;
|
|
19
|
+
for (const childId of level.children) {
|
|
20
|
+
const child = nodes[childId];
|
|
21
|
+
if (!child)
|
|
22
|
+
continue;
|
|
23
|
+
if (child.type === 'ceiling') {
|
|
24
|
+
const ch = child.height ?? DEFAULT_LEVEL_HEIGHT;
|
|
25
|
+
if (ch > maxTop)
|
|
26
|
+
maxTop = ch;
|
|
27
|
+
}
|
|
28
|
+
else if (child.type === 'wall') {
|
|
29
|
+
let meshY = sceneRegistry.nodes.get(childId)?.position.y ?? 0;
|
|
30
|
+
if (meshY < 0)
|
|
31
|
+
meshY = 0;
|
|
32
|
+
const top = meshY + (child.height ?? DEFAULT_LEVEL_HEIGHT);
|
|
33
|
+
if (top > maxTop)
|
|
34
|
+
maxTop = top;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const height = maxTop > 0 ? maxTop : DEFAULT_LEVEL_HEIGHT;
|
|
38
|
+
heightCache.set(levelId, height);
|
|
39
|
+
return height;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Instantly snaps all level Objects3D to their true stacked Y positions
|
|
43
|
+
* (ignores levelMode — always uses stacked, no exploded gap).
|
|
44
|
+
*
|
|
45
|
+
* Returns a restore function that reverts each level's Y to what it was
|
|
46
|
+
* before the snap, so lerp animations in LevelSystem can continue undisturbed.
|
|
47
|
+
*
|
|
48
|
+
* Usage:
|
|
49
|
+
* const restore = snapLevelsToTruePositions()
|
|
50
|
+
* renderer.render(scene, camera)
|
|
51
|
+
* restore()
|
|
52
|
+
*/
|
|
53
|
+
export function snapLevelsToTruePositions() {
|
|
54
|
+
const nodes = useScene.getState().nodes;
|
|
55
|
+
const entries = [];
|
|
56
|
+
sceneRegistry.byType.level.forEach((levelId) => {
|
|
57
|
+
const obj = sceneRegistry.nodes.get(levelId);
|
|
58
|
+
const level = nodes[levelId];
|
|
59
|
+
if (obj && level) {
|
|
60
|
+
entries.push({ levelId, index: level.level ?? 0, obj });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
entries.sort((a, b) => a.index - b.index);
|
|
64
|
+
// Snapshot current Y and visibility so we can restore them after the render
|
|
65
|
+
const snapshot = new Map(entries.map(({ levelId, obj }) => [levelId, { y: obj.position.y, visible: obj.visible }]));
|
|
66
|
+
// Snap to true stacked positions and make all levels visible
|
|
67
|
+
let cumulativeY = 0;
|
|
68
|
+
for (const { levelId, obj } of entries) {
|
|
69
|
+
obj.position.y = cumulativeY;
|
|
70
|
+
obj.visible = true;
|
|
71
|
+
cumulativeY += getLevelHeight(levelId, nodes);
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
for (const { levelId, obj } of entries) {
|
|
75
|
+
const saved = snapshot.get(levelId);
|
|
76
|
+
if (saved !== undefined) {
|
|
77
|
+
obj.position.y = saved.y;
|
|
78
|
+
obj.visible = saved.visible;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAgDA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAgDA,eAAO,MAAM,UAAU,YAgEtB,CAAA"}
|
|
@@ -85,11 +85,9 @@ export const WallCutout = () => {
|
|
|
85
85
|
hideWall = true;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
else {
|
|
88
|
+
else if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior') {
|
|
89
89
|
// Back side
|
|
90
|
-
|
|
91
|
-
hideWall = true;
|
|
92
|
-
}
|
|
90
|
+
hideWall = true;
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
93
|
;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zone-system.d.ts","sourceRoot":"","sources":["../../../src/systems/zone/zone-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"zone-system.d.ts","sourceRoot":"","sources":["../../../src/systems/zone/zone-system.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,YAuFtB,CAAA"}
|
|
@@ -4,21 +4,47 @@ import { useRef } from 'react';
|
|
|
4
4
|
import { MathUtils } from 'three';
|
|
5
5
|
import useViewer from '../../store/use-viewer';
|
|
6
6
|
const TRANSITION_DURATION = 400; // ms
|
|
7
|
+
const EXIT_DEBOUNCE_MS = 50; // ignore rapid exit→re-enter within this window
|
|
7
8
|
export const ZoneSystem = () => {
|
|
8
9
|
const lastHighlightedZoneRef = useRef(null);
|
|
9
10
|
const lastChangeTimeRef = useRef(0);
|
|
10
11
|
const isTransitioningRef = useRef(false);
|
|
12
|
+
// Debounce exit-to-null: track the raw pending value and when it last changed
|
|
13
|
+
const pendingZoneRef = useRef(null);
|
|
14
|
+
const pendingZoneSinceRef = useRef(0);
|
|
11
15
|
useFrame(({ clock }, delta) => {
|
|
12
16
|
const hoveredId = useViewer.getState().hoveredId;
|
|
13
|
-
let
|
|
17
|
+
let rawZone = null;
|
|
14
18
|
if (hoveredId) {
|
|
15
19
|
const hoveredNode = useScene.getState().nodes[hoveredId];
|
|
16
20
|
if (hoveredNode?.type === 'zone') {
|
|
17
|
-
|
|
21
|
+
rawZone = hoveredId;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
|
-
//
|
|
24
|
+
// Update pending zone when the raw value changes
|
|
25
|
+
if (rawZone !== pendingZoneRef.current) {
|
|
26
|
+
pendingZoneRef.current = rawZone;
|
|
27
|
+
pendingZoneSinceRef.current = clock.elapsedTime * 1000;
|
|
28
|
+
}
|
|
29
|
+
// Apply non-null immediately; debounce null to filter out brief exits
|
|
30
|
+
const age = clock.elapsedTime * 1000 - pendingZoneSinceRef.current;
|
|
31
|
+
const highlightedZone = rawZone !== null ? rawZone : age >= EXIT_DEBOUNCE_MS ? null : lastHighlightedZoneRef.current;
|
|
32
|
+
// Detect stable zone change
|
|
21
33
|
if (highlightedZone !== lastHighlightedZoneRef.current) {
|
|
34
|
+
// Fade out previous zone label-pin
|
|
35
|
+
if (lastHighlightedZoneRef.current) {
|
|
36
|
+
const prevLabel = document.getElementById(`${lastHighlightedZoneRef.current}-label`);
|
|
37
|
+
const pin = prevLabel?.querySelector('.label-pin');
|
|
38
|
+
if (pin)
|
|
39
|
+
pin.style.opacity = '0';
|
|
40
|
+
}
|
|
41
|
+
// Fade in new zone label-pin
|
|
42
|
+
if (highlightedZone) {
|
|
43
|
+
const label = document.getElementById(`${highlightedZone}-label`);
|
|
44
|
+
const pin = label?.querySelector('.label-pin');
|
|
45
|
+
if (pin)
|
|
46
|
+
pin.style.opacity = '1';
|
|
47
|
+
}
|
|
22
48
|
lastHighlightedZoneRef.current = highlightedZone;
|
|
23
49
|
lastChangeTimeRef.current = clock.elapsedTime * 1000;
|
|
24
50
|
isTransitioningRef.current = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,16 +26,17 @@
|
|
|
26
26
|
"@react-three/drei": "^10",
|
|
27
27
|
"@react-three/fiber": "^9",
|
|
28
28
|
"react": "^18 || ^19",
|
|
29
|
-
"three": "^0.
|
|
29
|
+
"three": "^0.183"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"polygon-clipping": "^0.15.7",
|
|
32
33
|
"zustand": "^5"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
+
"@pascal/typescript-config": "*",
|
|
36
37
|
"@types/react": "^19.2.2",
|
|
37
|
-
"typescript": "5.9.
|
|
38
|
-
"@types/three": "^0.
|
|
38
|
+
"typescript": "5.9.3",
|
|
39
|
+
"@types/three": "^0.183.0"
|
|
39
40
|
},
|
|
40
41
|
"keywords": [
|
|
41
42
|
"3d",
|