@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.
Files changed (86) hide show
  1. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  2. package/dist/components/renderers/ceiling/ceiling-renderer.js +16 -9
  3. package/dist/components/renderers/door/door-renderer.d.ts +5 -0
  4. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -0
  5. package/dist/components/renderers/door/door-renderer.js +11 -0
  6. package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -1
  7. package/dist/components/renderers/guide/guide-renderer.js +4 -2
  8. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  9. package/dist/components/renderers/item/item-renderer.js +92 -7
  10. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  11. package/dist/components/renderers/node-renderer.js +3 -1
  12. package/dist/components/renderers/roof/roof-materials.d.ts +4 -0
  13. package/dist/components/renderers/roof/roof-materials.d.ts.map +1 -0
  14. package/dist/components/renderers/roof/roof-materials.js +16 -0
  15. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  16. package/dist/components/renderers/roof/roof-renderer.js +5 -1
  17. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +5 -0
  18. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -0
  19. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +13 -0
  20. package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -1
  21. package/dist/components/renderers/scan/scan-renderer.js +3 -1
  22. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  23. package/dist/components/renderers/scene-renderer.js +3 -3
  24. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  25. package/dist/components/renderers/site/site-renderer.js +4 -19
  26. package/dist/components/renderers/slab/slab-renderer.js +1 -1
  27. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  28. package/dist/components/renderers/wall/wall-renderer.js +1 -1
  29. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  30. package/dist/components/renderers/window/window-renderer.js +2 -1
  31. package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
  32. package/dist/components/renderers/zone/zone-renderer.js +33 -13
  33. package/dist/components/viewer/ground-occluder.d.ts +2 -0
  34. package/dist/components/viewer/ground-occluder.d.ts.map +1 -0
  35. package/dist/components/viewer/ground-occluder.js +55 -0
  36. package/dist/components/viewer/index.d.ts +1 -0
  37. package/dist/components/viewer/index.d.ts.map +1 -1
  38. package/dist/components/viewer/index.js +59 -6
  39. package/dist/components/viewer/lights.d.ts.map +1 -1
  40. package/dist/components/viewer/lights.js +69 -5
  41. package/dist/components/viewer/perf-monitor.d.ts +2 -0
  42. package/dist/components/viewer/perf-monitor.d.ts.map +1 -0
  43. package/dist/components/viewer/perf-monitor.js +42 -0
  44. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  45. package/dist/components/viewer/post-processing.js +230 -107
  46. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  47. package/dist/components/viewer/selection-manager.js +47 -17
  48. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  49. package/dist/components/viewer/viewer-camera.js +2 -2
  50. package/dist/hooks/use-gltf-ktx2.d.ts +1 -1
  51. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  52. package/dist/hooks/use-gltf-ktx2.js +25 -7
  53. package/dist/hooks/use-node-events.d.ts +9 -1
  54. package/dist/hooks/use-node-events.d.ts.map +1 -1
  55. package/dist/hooks/use-node-events.js +5 -5
  56. package/dist/index.d.ts +4 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +4 -1
  59. package/dist/lib/asset-url.d.ts +1 -1
  60. package/dist/lib/asset-url.d.ts.map +1 -1
  61. package/dist/lib/asset-url.js +2 -1
  62. package/dist/lib/layers.d.ts +5 -0
  63. package/dist/lib/layers.d.ts.map +1 -0
  64. package/dist/lib/layers.js +4 -0
  65. package/dist/store/use-item-light-pool.d.ts +18 -0
  66. package/dist/store/use-item-light-pool.d.ts.map +1 -0
  67. package/dist/store/use-item-light-pool.js +30 -0
  68. package/dist/store/use-viewer.d.ts +52 -7
  69. package/dist/store/use-viewer.d.ts.map +1 -1
  70. package/dist/store/use-viewer.js +79 -17
  71. package/dist/systems/interactive/interactive-system.d.ts +2 -0
  72. package/dist/systems/interactive/interactive-system.d.ts.map +1 -0
  73. package/dist/systems/interactive/interactive-system.js +95 -0
  74. package/dist/systems/item-light/item-light-system.d.ts +2 -0
  75. package/dist/systems/item-light/item-light-system.d.ts.map +1 -0
  76. package/dist/systems/item-light/item-light-system.js +235 -0
  77. package/dist/systems/level/level-system.d.ts.map +1 -1
  78. package/dist/systems/level/level-system.js +19 -8
  79. package/dist/systems/level/level-utils.d.ts +17 -0
  80. package/dist/systems/level/level-utils.d.ts.map +1 -0
  81. package/dist/systems/level/level-utils.js +82 -0
  82. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  83. package/dist/systems/wall/wall-cutout.js +2 -4
  84. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  85. package/dist/systems/zone/zone-system.js +29 -3
  86. 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,YAkBvB,CAAA"}
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
- const LEVEL_HEIGHT = 2.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
- if (obj) {
14
- const level = useScene.getState().nodes[levelId];
15
- const targetY = (level.level || 0) *
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,YAkEtB,CAAA"}
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
- if (wallNode.backSide === 'exterior' && wallNode.frontSide !== 'exterior') {
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":"AASA,eAAO,MAAM,UAAU,YA4DtB,CAAA"}
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 highlightedZone = null;
17
+ let rawZone = null;
14
18
  if (hoveredId) {
15
19
  const hoveredNode = useScene.getState().nodes[hoveredId];
16
20
  if (hoveredNode?.type === 'zone') {
17
- highlightedZone = hoveredId;
21
+ rawZone = hoveredId;
18
22
  }
19
23
  }
20
- // Detect zone change
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.1.13",
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.182"
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
- "@repo/typescript-config": "*",
36
+ "@pascal/typescript-config": "*",
36
37
  "@types/react": "^19.2.2",
37
- "typescript": "5.9.2",
38
- "@types/three": "^0.182.0"
38
+ "typescript": "5.9.3",
39
+ "@types/three": "^0.183.0"
39
40
  },
40
41
  "keywords": [
41
42
  "3d",