@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":"ceiling-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/ceiling/ceiling-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAuChE,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,4CAe9D,CAAA"}
1
+ {"version":3,"file":"ceiling-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/ceiling/ceiling-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,kBAAkB,CAAA;AA+ChE,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,4CAwB9D,CAAA"}
@@ -1,18 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRegistry } from '@pascal-app/core';
3
3
  import { useRef } from 'react';
4
- import { faceDirection, float, mix, positionWorld, smoothstep } from 'three/tsl';
5
- import { DoubleSide, MeshBasicNodeMaterial } from 'three/webgpu';
4
+ import { float, mix, positionWorld, smoothstep } from 'three/tsl';
5
+ import { BackSide, FrontSide, MeshBasicNodeMaterial } from 'three/webgpu';
6
6
  import { useNodeEvents } from '../../../hooks/use-node-events';
7
7
  import { NodeRenderer } from '../node-renderer';
8
8
  // TSL material that renders differently based on face direction:
9
9
  // - Back face (looking up at ceiling from below): solid
10
10
  // - Front face (looking down at ceiling from above): 30% opacity
11
- const ceilingMaterial = new MeshBasicNodeMaterial({
12
- color: 0x999999,
13
- side: DoubleSide,
11
+ const ceilingTopMaterial = new MeshBasicNodeMaterial({
12
+ color: 0xb5_a7_8d,
14
13
  transparent: true,
15
14
  depthWrite: false,
15
+ side: FrontSide,
16
+ // Disabled as we only show ceiling grid when needed
17
+ // alphaTestNode: float(0.4), // Discard pixels with alpha below 0.4 to create grid lines and not affect depth buffer
18
+ });
19
+ const ceilingBottomMaterial = new MeshBasicNodeMaterial({
20
+ color: 0x99_99_99,
21
+ transparent: true,
22
+ side: BackSide,
16
23
  });
17
24
  // Create grid pattern based on local position
18
25
  const gridScale = 5; // Grid cells per meter (1 = 1m grid)
@@ -25,14 +32,14 @@ const lineX = smoothstep(lineWidth, 0, gridX).add(smoothstep(1.0 - lineWidth, 1.
25
32
  const lineY = smoothstep(lineWidth, 0, gridY).add(smoothstep(1.0 - lineWidth, 1.0, gridY));
26
33
  // Combine: if either X or Y is a line, show the line
27
34
  const gridPattern = lineX.max(lineY);
28
- // Grid lines at 0.8 opacity, spaces at 0.1 opacity
29
- const gridOpacity = mix(float(0.1), float(0.8), gridPattern);
35
+ // Grid lines at 0.6 opacity, spaces at 0.2 opacity
36
+ const gridOpacity = mix(float(0.2), float(0.6), gridPattern);
30
37
  // faceDirection is 1.0 for front face, -1.0 for back face
31
38
  // Front face (top, looking down): grid pattern, Back face (bottom, looking up): solid
32
- ceilingMaterial.opacityNode = mix(float(1.0), gridOpacity, faceDirection.greaterThan(0.0));
39
+ ceilingTopMaterial.opacityNode = gridOpacity;
33
40
  export const CeilingRenderer = ({ node }) => {
34
41
  const ref = useRef(null);
35
42
  useRegistry(node.id, 'ceiling', ref);
36
43
  const handlers = useNodeEvents(node, 'ceiling');
37
- return (_jsxs("mesh", { ref: ref, material: ceilingMaterial, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
44
+ return (_jsxs("mesh", { material: ceilingBottomMaterial, ref: ref, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { material: ceilingTopMaterial, name: "ceiling-grid", ...handlers, scale: 0, visible: false, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
38
45
  };
@@ -0,0 +1,5 @@
1
+ import { type DoorNode } from '@pascal-app/core';
2
+ export declare const DoorRenderer: ({ node }: {
3
+ node: DoorNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=door-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"door-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/door/door-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAK7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAsBxD,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ export const DoorRenderer = ({ node }) => {
6
+ const ref = useRef(null);
7
+ useRegistry(node.id, 'door', ref);
8
+ const handlers = useNodeEvents(node, 'door');
9
+ const isTransient = !!node.metadata?.isTransient;
10
+ return (_jsxs("mesh", { castShadow: true, position: node.position, receiveShadow: true, ref: ref, rotation: node.rotation, visible: node.visible, ...(isTransient ? {} : handlers), children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#d1d5db" })] }));
11
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"guide-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/guide/guide-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,kBAAkB,CAAA;AAQ9D,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,4CAe1D,CAAA"}
1
+ {"version":3,"file":"guide-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/guide/guide-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,kBAAkB,CAAA;AAS9D,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,4CAqB1D,CAAA"}
@@ -6,11 +6,13 @@ import { DoubleSide, TextureLoader } from 'three';
6
6
  import { float, texture } from 'three/tsl';
7
7
  import { MeshBasicNodeMaterial } from 'three/webgpu';
8
8
  import { useAssetUrl } from '../../../hooks/use-asset-url';
9
+ import useViewer from '../../../store/use-viewer';
9
10
  export const GuideRenderer = ({ node }) => {
11
+ const showGuides = useViewer((s) => s.showGuides);
10
12
  const ref = useRef(null);
11
13
  useRegistry(node.id, 'guide', ref);
12
14
  const resolvedUrl = useAssetUrl(node.url);
13
- return (_jsx("group", { ref: ref, position: node.position, rotation: [0, node.rotation[1], 0], children: resolvedUrl && (_jsx(Suspense, { children: _jsx(GuidePlane, { url: resolvedUrl, scale: node.scale, opacity: node.opacity }) })) }));
15
+ return (_jsx("group", { position: node.position, ref: ref, rotation: [0, node.rotation[1], 0], visible: showGuides, children: resolvedUrl && (_jsx(Suspense, { children: _jsx(GuidePlane, { opacity: node.opacity, scale: node.scale, url: resolvedUrl }) })) }));
14
16
  };
15
17
  const GuidePlane = ({ url, scale, opacity }) => {
16
18
  const tex = useLoader(TextureLoader, url);
@@ -32,5 +34,5 @@ const GuidePlane = ({ url, scale, opacity }) => {
32
34
  });
33
35
  return { width: planeWidth, height: planeHeight, material: mat };
34
36
  }, [tex, scale, opacity]);
35
- return (_jsx("mesh", { rotation: [-Math.PI / 2, 0, 0], material: material, raycast: () => { }, frustumCulled: false, children: _jsx("planeGeometry", { args: [width, height], boundingBox: null, boundingSphere: null }) }));
37
+ return (_jsx("mesh", { frustumCulled: false, material: material, raycast: () => { }, rotation: [-Math.PI / 2, 0, 0], children: _jsx("planeGeometry", { args: [width, height], boundingBox: null, boundingSphere: null }) }));
36
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAyB,MAAM,kBAAkB,CAAA;AAoCvF,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAexD,CAAA"}
1
+ {"version":3,"file":"item-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/item/item-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAKd,MAAM,kBAAkB,CAAA;AAwCzB,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAexD,CAAA"}
@@ -1,16 +1,20 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRegistry, useScene } from '@pascal-app/core';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInteractive, useRegistry, useScene, } from '@pascal-app/core';
3
+ import { useAnimations } from '@react-three/drei';
3
4
  import { Clone } from '@react-three/drei/core/Clone';
4
5
  import { useGLTF } from '@react-three/drei/core/Gltf';
6
+ import { useFrame } from '@react-three/fiber';
5
7
  import { Suspense, useEffect, useMemo, useRef } from 'react';
8
+ import { MathUtils } from 'three';
6
9
  import { positionLocal, smoothstep, time } from 'three/tsl';
7
10
  import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
8
11
  import { useNodeEvents } from '../../../hooks/use-node-events';
9
12
  import { resolveCdnUrl } from '../../../lib/asset-url';
13
+ import { useItemLightPool } from '../../../store/use-item-light-pool';
10
14
  import { NodeRenderer } from '../node-renderer';
11
15
  // Shared materials to avoid creating new instances for every mesh
12
16
  const defaultMaterial = new MeshStandardNodeMaterial({
13
- color: 0xffffff,
17
+ color: 0xff_ff_ff,
14
18
  roughness: 1,
15
19
  metalness: 0,
16
20
  });
@@ -33,7 +37,7 @@ const getMaterialForOriginal = (original) => {
33
37
  export const ItemRenderer = ({ node }) => {
34
38
  const ref = useRef(null);
35
39
  useRegistry(node.id, node.type, ref);
36
- return (_jsxs("group", { position: node.position, rotation: node.rotation, ref: ref, visible: node.visible, children: [_jsx(Suspense, { fallback: _jsx(PreviewModel, { node: node }), children: _jsx(ModelRenderer, { node: node }) }), node.children?.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
40
+ return (_jsxs("group", { position: node.position, ref: ref, rotation: node.rotation, visible: node.visible, children: [_jsx(Suspense, { fallback: _jsx(PreviewModel, { node: node }), children: _jsx(ModelRenderer, { node: node }) }), node.children?.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
37
41
  };
38
42
  const previewMaterial = new MeshStandardNodeMaterial({
39
43
  color: '#cccccc',
@@ -45,10 +49,15 @@ const previewOpacity = smoothstep(0.42, 0.55, positionLocal.y.add(time.mul(-0.2)
45
49
  previewMaterial.opacityNode = previewOpacity;
46
50
  previewMaterial.transparent = true;
47
51
  const PreviewModel = ({ node }) => {
48
- return (_jsx("mesh", { "position-y": node.asset.dimensions[1] / 2, material: previewMaterial, children: _jsx("boxGeometry", { args: [node.asset.dimensions[0], node.asset.dimensions[1], node.asset.dimensions[2]] }) }));
52
+ return (_jsx("mesh", { material: previewMaterial, "position-y": node.asset.dimensions[1] / 2, children: _jsx("boxGeometry", { args: [node.asset.dimensions[0], node.asset.dimensions[1], node.asset.dimensions[2]] }) }));
49
53
  };
54
+ const multiplyScales = (a, b) => [a[0] * b[0], a[1] * b[1], a[2] * b[2]];
50
55
  const ModelRenderer = ({ node }) => {
51
- const { scene, nodes } = useGLTF(resolveCdnUrl(node.asset.src) || '');
56
+ const { scene, nodes, animations } = useGLTF(resolveCdnUrl(node.asset.src) || '');
57
+ const ref = useRef(null);
58
+ const { actions } = useAnimations(animations, ref);
59
+ // Freeze the interactive definition at mount — asset schemas don't change at runtime
60
+ const interactiveRef = useRef(node.asset.interactive);
52
61
  if (nodes.cutout) {
53
62
  nodes.cutout.visible = false;
54
63
  }
@@ -58,6 +67,13 @@ const ModelRenderer = ({ node }) => {
58
67
  return;
59
68
  useScene.getState().dirtyNodes.add(node.parentId);
60
69
  }, [node.parentId]);
70
+ useEffect(() => {
71
+ const interactive = interactiveRef.current;
72
+ if (!interactive)
73
+ return;
74
+ useInteractive.getState().initItem(node.id, interactive);
75
+ return () => useInteractive.getState().removeItem(node.id);
76
+ }, [node.id]);
61
77
  useMemo(() => {
62
78
  scene.traverse((child) => {
63
79
  if (child.isMesh) {
@@ -81,5 +97,74 @@ const ModelRenderer = ({ node }) => {
81
97
  }
82
98
  });
83
99
  }, [scene]);
84
- return (_jsx(Clone, { object: scene, scale: node.asset.scale, position: node.asset.offset, rotation: node.asset.rotation, ...handlers }));
100
+ const interactive = interactiveRef.current;
101
+ const animEffect = interactive?.effects.find((e) => e.kind === 'animation') ?? null;
102
+ const lightEffects = interactive?.effects.filter((e) => e.kind === 'light') ?? [];
103
+ return (_jsxs(_Fragment, { children: [_jsx(Clone, { object: scene, position: node.asset.offset, ref: ref, rotation: node.asset.rotation, scale: multiplyScales(node.asset.scale || [1, 1, 1], node.scale || [1, 1, 1]), ...handlers }), animations.length > 0 && (_jsx(ItemAnimation, { actions: actions, animations: animations, animEffect: animEffect, interactive: interactive ?? null, nodeId: node.id })), lightEffects.map((effect, i) => (_jsx(ItemLightRegistrar, { effect: effect, index: i, interactive: interactive, nodeId: node.id }, i)))] }));
104
+ };
105
+ const ItemAnimation = ({ nodeId, animEffect, interactive, actions, animations, }) => {
106
+ const activeClipRef = useRef(null);
107
+ const fadingOutRef = useRef(null);
108
+ // Reactive: derive target clip name — only re-renders when the clip name itself changes
109
+ const targetClip = useInteractive((s) => {
110
+ const values = s.items[nodeId]?.controlValues;
111
+ if (!animEffect)
112
+ return animations[0]?.name ?? null;
113
+ const toggleIndex = interactive.controls.findIndex((c) => c.kind === 'toggle');
114
+ const isOn = toggleIndex >= 0 ? Boolean(values?.[toggleIndex]) : false;
115
+ return isOn
116
+ ? (animEffect.clips.on ?? null)
117
+ : (animEffect.clips.off ?? animEffect.clips.loop ?? null);
118
+ });
119
+ // When target clip changes: kick off the transition
120
+ useEffect(() => {
121
+ // Cancel any ongoing fade-out immediately
122
+ if (fadingOutRef.current) {
123
+ fadingOutRef.current.timeScale = 0;
124
+ fadingOutRef.current = null;
125
+ }
126
+ // Move current clip to fade-out
127
+ if (activeClipRef.current && activeClipRef.current !== targetClip) {
128
+ const old = actions[activeClipRef.current];
129
+ if (old?.isRunning())
130
+ fadingOutRef.current = old;
131
+ }
132
+ // Start new clip at timeScale 0.01 (as 0 would cause isRunning to be false and thus not play at all), then fade in to 1
133
+ activeClipRef.current = targetClip;
134
+ if (targetClip) {
135
+ const next = actions[targetClip];
136
+ if (next) {
137
+ next.timeScale = 0.01;
138
+ next.play();
139
+ }
140
+ }
141
+ }, [targetClip, actions]);
142
+ // useFrame: only lerping — no logic
143
+ useFrame((_, delta) => {
144
+ if (fadingOutRef.current) {
145
+ const action = fadingOutRef.current;
146
+ action.timeScale = MathUtils.lerp(action.timeScale, 0, Math.min(delta * 5, 1));
147
+ if (action.timeScale < 0.01) {
148
+ action.timeScale = 0;
149
+ fadingOutRef.current = null;
150
+ }
151
+ }
152
+ if (activeClipRef.current) {
153
+ const action = actions[activeClipRef.current];
154
+ if (action?.isRunning() && action.timeScale < 1) {
155
+ action.timeScale = MathUtils.lerp(action.timeScale, 1, Math.min(delta * 5, 1));
156
+ if (1 - action.timeScale < 0.01)
157
+ action.timeScale = 1;
158
+ }
159
+ }
160
+ });
161
+ return null;
162
+ };
163
+ const ItemLightRegistrar = ({ nodeId, effect, interactive, index, }) => {
164
+ useEffect(() => {
165
+ const key = `${nodeId}:${index}`;
166
+ useItemLightPool.getState().register(key, nodeId, effect, interactive);
167
+ return () => useItemLightPool.getState().unregister(key);
168
+ }, [nodeId, index, effect, interactive]);
169
+ return null;
85
170
  };
@@ -1 +1 @@
1
- {"version":3,"file":"node-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/node-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,kBAAkB,CAAA;AAczD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,mDAqBjE,CAAA"}
1
+ {"version":3,"file":"node-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/node-renderer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,kBAAkB,CAAA;AAgBzD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,mDAuBjE,CAAA"}
@@ -3,10 +3,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { useScene } from '@pascal-app/core';
4
4
  import { BuildingRenderer } from './building/building-renderer';
5
5
  import { CeilingRenderer } from './ceiling/ceiling-renderer';
6
+ import { DoorRenderer } from './door/door-renderer';
6
7
  import { GuideRenderer } from './guide/guide-renderer';
7
8
  import { ItemRenderer } from './item/item-renderer';
8
9
  import { LevelRenderer } from './level/level-renderer';
9
10
  import { RoofRenderer } from './roof/roof-renderer';
11
+ import { RoofSegmentRenderer } from './roof-segment/roof-segment-renderer';
10
12
  import { ScanRenderer } from './scan/scan-renderer';
11
13
  import { SiteRenderer } from './site/site-renderer';
12
14
  import { SlabRenderer } from './slab/slab-renderer';
@@ -17,5 +19,5 @@ export const NodeRenderer = ({ nodeId }) => {
17
19
  const node = useScene((state) => state.nodes[nodeId]);
18
20
  if (!node)
19
21
  return null;
20
- return (_jsxs(_Fragment, { children: [node.type === 'site' && _jsx(SiteRenderer, { node: node }), node.type === 'building' && _jsx(BuildingRenderer, { node: node }), node.type === 'ceiling' && _jsx(CeilingRenderer, { node: node }), node.type === 'level' && _jsx(LevelRenderer, { node: node }), node.type === 'item' && _jsx(ItemRenderer, { node: node }), node.type === 'slab' && _jsx(SlabRenderer, { node: node }), node.type === 'wall' && _jsx(WallRenderer, { node: node }), node.type === 'window' && _jsx(WindowRenderer, { node: node }), node.type === 'zone' && _jsx(ZoneRenderer, { node: node }), node.type === 'roof' && _jsx(RoofRenderer, { node: node }), node.type === 'scan' && _jsx(ScanRenderer, { node: node }), node.type === 'guide' && _jsx(GuideRenderer, { node: node })] }));
22
+ return (_jsxs(_Fragment, { children: [node.type === 'site' && _jsx(SiteRenderer, { node: node }), node.type === 'building' && _jsx(BuildingRenderer, { node: node }), node.type === 'ceiling' && _jsx(CeilingRenderer, { node: node }), node.type === 'level' && _jsx(LevelRenderer, { node: node }), node.type === 'item' && _jsx(ItemRenderer, { node: node }), node.type === 'slab' && _jsx(SlabRenderer, { node: node }), node.type === 'wall' && _jsx(WallRenderer, { node: node }), node.type === 'door' && _jsx(DoorRenderer, { node: node }), node.type === 'window' && _jsx(WindowRenderer, { node: node }), node.type === 'zone' && _jsx(ZoneRenderer, { node: node }), node.type === 'roof' && _jsx(RoofRenderer, { node: node }), node.type === 'roof-segment' && _jsx(RoofSegmentRenderer, { node: node }), node.type === 'scan' && _jsx(ScanRenderer, { node: node }), node.type === 'guide' && _jsx(GuideRenderer, { node: node })] }));
21
23
  };
@@ -0,0 +1,4 @@
1
+ import * as THREE from 'three';
2
+ export declare const roofMaterials: THREE.Material[];
3
+ export declare const roofDebugMaterials: THREE.Material[];
4
+ //# sourceMappingURL=roof-materials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,QAAQ,EAKzC,CAAA;AAGD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,QAAQ,EAK9C,CAAA"}
@@ -0,0 +1,16 @@
1
+ import * as THREE from 'three';
2
+ // Production materials — match the rest of the scene (white walls, light-gray slabs).
3
+ // Indices: 0 = Wall/Trim, 1 = Deck, 2 = Interior, 3 = Shingle
4
+ export const roofMaterials = [
5
+ new THREE.MeshStandardMaterial({ color: 'white', roughness: 1, side: THREE.DoubleSide }), // 0: Wall/Trim
6
+ new THREE.MeshStandardMaterial({ color: '#e5e5e5', roughness: 1, side: THREE.FrontSide }), // 1: Deck
7
+ new THREE.MeshStandardMaterial({ color: 'white', roughness: 1, side: THREE.DoubleSide }), // 2: Interior
8
+ new THREE.MeshStandardMaterial({ color: '#e5e5e5', roughness: 0.9, side: THREE.FrontSide }), // 3: Shingle
9
+ ];
10
+ // Debug materials — vivid, distinct colours to identify each surface group.
11
+ export const roofDebugMaterials = [
12
+ new THREE.MeshStandardMaterial({ color: '#eaeaea', roughness: 0.8, side: THREE.DoubleSide }), // 0: Wall
13
+ new THREE.MeshStandardMaterial({ color: '#000000', roughness: 0.9, side: THREE.FrontSide }), // 1: Deck
14
+ new THREE.MeshStandardMaterial({ color: '#dddddd', roughness: 0.9, side: THREE.DoubleSide }), // 2: Interior
15
+ new THREE.MeshStandardMaterial({ color: '#4ade80', roughness: 0.9, side: THREE.FrontSide }), // 3: Shingle
16
+ ];
@@ -1 +1 @@
1
- {"version":3,"file":"roof-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/roof/roof-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAK7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAsBxD,CAAA"}
1
+ {"version":3,"file":"roof-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/roof/roof-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAQ7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CA+BxD,CAAA"}
@@ -2,9 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRegistry } from '@pascal-app/core';
3
3
  import { useRef } from 'react';
4
4
  import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ import useViewer from '../../../store/use-viewer';
6
+ import { NodeRenderer } from '../node-renderer';
7
+ import { roofDebugMaterials, roofMaterials } from './roof-materials';
5
8
  export const RoofRenderer = ({ node }) => {
6
9
  const ref = useRef(null);
7
10
  useRegistry(node.id, 'roof', ref);
8
11
  const handlers = useNodeEvents(node, 'roof');
9
- return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, position: node.position, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "white" })] }));
12
+ const debugColors = useViewer((s) => s.debugColors);
13
+ return (_jsxs("group", { position: node.position, ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: [_jsx("mesh", { castShadow: true, material: debugColors ? roofDebugMaterials : roofMaterials, name: "merged-roof", receiveShadow: true, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), _jsx("group", { name: "segments-wrapper", visible: false, children: (node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) })] }));
10
14
  };
@@ -0,0 +1,5 @@
1
+ import { type RoofSegmentNode } from '@pascal-app/core';
2
+ export declare const RoofSegmentRenderer: ({ node }: {
3
+ node: RoofSegmentNode;
4
+ }) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=roof-segment-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roof-segment-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/roof-segment/roof-segment-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAe,MAAM,kBAAkB,CAAA;AAOpE,eAAO,MAAM,mBAAmB,GAAI,UAAU;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,4CAqBtE,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRegistry } from '@pascal-app/core';
3
+ import { useRef } from 'react';
4
+ import { useNodeEvents } from '../../../hooks/use-node-events';
5
+ import useViewer from '../../../store/use-viewer';
6
+ import { roofDebugMaterials, roofMaterials } from '../roof/roof-materials';
7
+ export const RoofSegmentRenderer = ({ node }) => {
8
+ const ref = useRef(null);
9
+ useRegistry(node.id, 'roof-segment', ref);
10
+ const handlers = useNodeEvents(node, 'roof-segment');
11
+ const debugColors = useViewer((s) => s.debugColors);
12
+ return (_jsx("mesh", { material: debugColors ? roofDebugMaterials : roofMaterials, position: node.position, ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }));
13
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"scan-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/scan/scan-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAM7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAoBxD,CAAA"}
1
+ {"version":3,"file":"scan-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/scan/scan-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAO7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAsBxD,CAAA"}
@@ -3,11 +3,13 @@ import { useRegistry } from '@pascal-app/core';
3
3
  import { Suspense, useMemo, useRef } from 'react';
4
4
  import { useAssetUrl } from '../../../hooks/use-asset-url';
5
5
  import { useGLTFKTX2 } from '../../../hooks/use-gltf-ktx2';
6
+ import useViewer from '../../../store/use-viewer';
6
7
  export const ScanRenderer = ({ node }) => {
8
+ const showScans = useViewer((s) => s.showScans);
7
9
  const ref = useRef(null);
8
10
  useRegistry(node.id, 'scan', ref);
9
11
  const resolvedUrl = useAssetUrl(node.url);
10
- return (_jsx("group", { ref: ref, position: node.position, rotation: node.rotation, scale: [node.scale, node.scale, node.scale], children: resolvedUrl && (_jsx(Suspense, { children: _jsx(ScanModel, { url: resolvedUrl, opacity: node.opacity }) })) }));
12
+ return (_jsx("group", { position: node.position, ref: ref, rotation: node.rotation, scale: [node.scale, node.scale, node.scale], visible: showScans, children: resolvedUrl && (_jsx(Suspense, { children: _jsx(ScanModel, { opacity: node.opacity, url: resolvedUrl }) })) }));
11
13
  };
12
14
  const ScanModel = ({ url, opacity }) => {
13
15
  const gltf = useGLTFKTX2(url);
@@ -1 +1 @@
1
- {"version":3,"file":"scene-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/scene-renderer.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,+CAUzB,CAAC"}
1
+ {"version":3,"file":"scene-renderer.d.ts","sourceRoot":"","sources":["../../../src/components/renderers/scene-renderer.tsx"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,+CAUzB,CAAA"}
@@ -1,7 +1,7 @@
1
- "use client";
1
+ 'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useScene } from "@pascal-app/core";
4
- import { NodeRenderer } from "./node-renderer";
3
+ import { useScene } from '@pascal-app/core';
4
+ import { NodeRenderer } from './node-renderer';
5
5
  export const SceneRenderer = () => {
6
6
  const rootNodes = useScene((state) => state.rootNodeIds);
7
7
  return (_jsx("group", { name: "scene-renderer", children: rootNodes.map((nodeId) => (_jsx(NodeRenderer, { nodeId: nodeId }, nodeId))) }));
@@ -1 +1 @@
1
- {"version":3,"file":"site-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/site/site-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAiC7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAwFxD,CAAA"}
1
+ {"version":3,"file":"site-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/site/site-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AA+B7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDA2DxD,CAAA"}
@@ -1,12 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRegistry } from '@pascal-app/core';
3
- import { Html } from '@react-three/drei';
4
3
  import { useMemo, useRef } from 'react';
5
4
  import { BufferGeometry, Float32BufferAttribute, Shape } from 'three';
6
5
  import { useNodeEvents } from '../../../hooks/use-node-events';
7
6
  import { NodeRenderer } from '../node-renderer';
8
7
  const Y_OFFSET = 0.01;
9
- const LINE_HEIGHT = 0.5;
10
8
  /**
11
9
  * Creates simple line geometry for site boundary
12
10
  * Single horizontal line at ground level
@@ -18,10 +16,10 @@ const createBoundaryLineGeometry = (points) => {
18
16
  const positions = [];
19
17
  // Create a simple line loop at ground level
20
18
  for (const [x, z] of points) {
21
- positions.push(x, Y_OFFSET, z);
19
+ positions.push(x ?? 0, Y_OFFSET, z ?? 0);
22
20
  }
23
21
  // Close the loop
24
- positions.push(points[0][0], Y_OFFSET, points[0][1]);
22
+ positions.push(points[0]?.[0] ?? 0, Y_OFFSET, points[0]?.[1] ?? 0);
25
23
  geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
26
24
  return geometry;
27
25
  };
@@ -50,22 +48,9 @@ export const SiteRenderer = ({ node }) => {
50
48
  return null;
51
49
  return createBoundaryLineGeometry(node.polygon.points);
52
50
  }, [node?.polygon?.points]);
53
- // Edge distances for labels
54
- const edges = useMemo(() => {
55
- const polygon = node?.polygon?.points ?? [];
56
- if (polygon.length < 2)
57
- return [];
58
- return polygon.map(([x1, z1], i) => {
59
- const [x2, z2] = polygon[(i + 1) % polygon.length];
60
- const midX = (x1 + x2) / 2;
61
- const midZ = (z1 + z2) / 2;
62
- const dist = Math.sqrt((x2 - x1) ** 2 + (z2 - z1) ** 2);
63
- return { midX, midZ, dist };
64
- });
65
- }, [node?.polygon?.points]);
66
51
  const handlers = useNodeEvents(node, 'site');
67
- if (!node || !floorShape || !lineGeometry) {
52
+ if (!(node && floorShape && lineGeometry)) {
68
53
  return null;
69
54
  }
70
- return (_jsxs("group", { ref: ref, ...handlers, children: [node.children.map((child) => (_jsx(NodeRenderer, { nodeId: typeof child === 'string' ? child : child.id }, typeof child === 'string' ? child : child.id))), _jsxs("mesh", { position: [0, Y_OFFSET - 0.005, 0], rotation: [-Math.PI / 2, 0, 0], receiveShadow: true, children: [_jsx("shapeGeometry", { args: [floorShape] }), _jsx("shadowMaterial", { transparent: true, opacity: 0.75 })] }), _jsx("line", { geometry: lineGeometry, frustumCulled: false, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, transparent: true, opacity: 0.6 }) }), edges.map((edge, i) => (_jsx(Html, { center: true, position: [edge.midX, 0.5, edge.midZ], style: { pointerEvents: 'none', userSelect: 'none' }, zIndexRange: [10, 0], occlude: true, children: _jsxs("div", { className: "whitespace-nowrap rounded bg-black/75 px-1.5 py-0.5 font-mono text-white text-xs backdrop-blur-sm", children: [edge.dist.toFixed(2), "m"] }) }, `edge-${i}`)))] }));
55
+ return (_jsxs("group", { ref: ref, ...handlers, children: [node.children.map((child) => (_jsx(NodeRenderer, { nodeId: typeof child === 'string' ? child : child.id }, typeof child === 'string' ? child : child.id))), _jsxs("mesh", { position: [0, Y_OFFSET - 0.005, 0], receiveShadow: true, rotation: [-Math.PI / 2, 0, 0], children: [_jsx("shapeGeometry", { args: [floorShape] }), _jsx("shadowMaterial", { opacity: 0.75, transparent: true })] }), _jsx("line", { frustumCulled: false, geometry: lineGeometry, renderOrder: 9, children: _jsx("lineBasicMaterial", { color: "#f59e0b", linewidth: 2, opacity: 0.6, transparent: true }) })] }));
71
56
  };
@@ -6,5 +6,5 @@ export const SlabRenderer = ({ node }) => {
6
6
  const ref = useRef(null);
7
7
  useRegistry(node.id, 'slab', ref);
8
8
  const handlers = useNodeEvents(node, 'slab');
9
- return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, ...handlers, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#e5e5e5" })] }));
9
+ return (_jsxs("mesh", { castShadow: true, receiveShadow: true, ref: ref, ...handlers, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#e5e5e5" })] }));
10
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAM7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAoBxD,CAAA"}
1
+ {"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAM7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAqBxD,CAAA"}
@@ -7,5 +7,5 @@ export const WallRenderer = ({ node }) => {
7
7
  const ref = useRef(null);
8
8
  useRegistry(node.id, 'wall', ref);
9
9
  const handlers = useNodeEvents(node, 'wall');
10
- return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", ...handlers, visible: false, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
10
+ return (_jsxs("mesh", { castShadow: true, receiveShadow: true, ref: ref, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", visible: false, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
11
11
  };
@@ -1 +1 @@
1
- {"version":3,"file":"window-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/window/window-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAK/D,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,4CAqB5D,CAAA"}
1
+ {"version":3,"file":"window-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/window/window-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAK/D,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,4CAsB5D,CAAA"}
@@ -6,5 +6,6 @@ export const WindowRenderer = ({ node }) => {
6
6
  const ref = useRef(null);
7
7
  useRegistry(node.id, 'window', ref);
8
8
  const handlers = useNodeEvents(node, 'window');
9
- return (_jsxs("mesh", { ref: ref, castShadow: true, receiveShadow: true, visible: node.visible, position: node.position, rotation: node.rotation, ...handlers, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#d1d5db" })] }));
9
+ const isTransient = !!node.metadata?.isTransient;
10
+ return (_jsxs("mesh", { castShadow: true, position: node.position, receiveShadow: true, ref: ref, rotation: node.rotation, visible: node.visible, ...(isTransient ? {} : handlers), children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("meshStandardMaterial", { color: "#d1d5db" })] }));
10
11
  };
@@ -1 +1 @@
1
- {"version":3,"file":"zone-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/zone/zone-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAwG7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAoGxD,CAAA"}
1
+ {"version":3,"file":"zone-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/zone/zone-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAyG7D,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,mDAqJxD,CAAA"}
@@ -6,6 +6,7 @@ import { BufferGeometry, Color, DoubleSide, Float32BufferAttribute, Shape } from
6
6
  import { color, float, uniform, uv } from 'three/tsl';
7
7
  import { MeshBasicNodeMaterial } from 'three/webgpu';
8
8
  import { useNodeEvents } from '../../../hooks/use-node-events';
9
+ import { ZONE_LAYER } from '../../../lib/layers';
9
10
  const Y_OFFSET = 0.01;
10
11
  const WALL_HEIGHT = 2.3;
11
12
  /**
@@ -24,11 +25,11 @@ const createWallGradientMaterial = (zoneColor) => {
24
25
  colorNode: baseColor,
25
26
  opacityNode: finalOpacity,
26
27
  side: DoubleSide,
27
- depthWrite: false,
28
+ depthWrite: true,
28
29
  depthTest: false,
29
30
  userData: {
30
31
  uOpacity: opacity,
31
- }
32
+ },
32
33
  });
33
34
  };
34
35
  /**
@@ -44,7 +45,7 @@ const createFloorMaterial = (zoneColor) => {
44
45
  side: DoubleSide,
45
46
  depthWrite: false,
46
47
  depthTest: false,
47
- userData: { uOpacity: opacity }
48
+ userData: { uOpacity: opacity },
48
49
  });
49
50
  };
50
51
  /**
@@ -144,18 +145,37 @@ export const ZoneRenderer = ({ node }) => {
144
145
  return createWallGradientMaterial(node.color);
145
146
  }, [node?.color]);
146
147
  const handlers = useNodeEvents(node, 'zone');
147
- if (!node || !floorShape || !wallGeometry || !floorMaterial || !wallMaterial) {
148
+ if (!(node && floorShape && wallGeometry && floorMaterial && wallMaterial)) {
148
149
  return null;
149
150
  }
150
- return (_jsxs("group", { ref: ref, ...handlers, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: {
151
- pointerEvents: 'none'
152
- }, zIndexRange: [10, 0], children: _jsx("div", { style: {
153
- transform: 'translate3d(-50%, -50%, 0)',
154
- width: 'max-content',
155
- color: 'white',
156
- textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
151
+ return (_jsxs("group", { ref: ref, ...handlers, userData: { labelPosition: [centroid[0], 1, centroid[1]] }, children: [_jsx(Html, { name: "label", position: [centroid[0], 1, centroid[1]], style: { pointerEvents: 'none' }, zIndexRange: [10, 0], children: _jsxs("div", { id: `${node.id}-label`, style: {
157
152
  display: 'flex',
158
- gap: '8px',
153
+ flexDirection: 'column',
159
154
  alignItems: 'center',
160
- }, children: node.name }) }), _jsx("mesh", { position: [0, Y_OFFSET, 0], rotation: [-Math.PI / 2, 0, 0], material: floorMaterial, name: "floor", children: _jsx("shapeGeometry", { args: [floorShape] }) }), _jsx("mesh", { geometry: wallGeometry, material: wallMaterial, name: "walls" })] }));
155
+ transform: 'translate3d(-50%, -50%, 0)',
156
+ opacity: 0,
157
+ transition: 'opacity 0.3s ease-in-out',
158
+ }, children: [_jsx("div", { style: {
159
+ width: 'max-content',
160
+ color: 'white',
161
+ textShadow: `-1px -1px 0 ${node.color}, 1px -1px 0 ${node.color}, -1px 1px 0 ${node.color}, 1px 1px 0 ${node.color}`,
162
+ textAlign: 'center',
163
+ }, children: _jsx("span", { children: node.name }) }), _jsxs("div", { className: "label-pin", style: {
164
+ display: 'flex',
165
+ flexDirection: 'column',
166
+ alignItems: 'center',
167
+ marginTop: '2px',
168
+ opacity: 0,
169
+ transition: 'opacity 0.5s ease-in-out',
170
+ }, children: [_jsx("div", { style: {
171
+ width: '2px',
172
+ height: '40px',
173
+ backgroundColor: node.color,
174
+ } }), _jsx("div", { style: {
175
+ width: '10px',
176
+ height: '10px',
177
+ borderRadius: '50%',
178
+ backgroundColor: node.color,
179
+ border: '1px solid white',
180
+ } })] })] }) }), _jsx("mesh", { layers: ZONE_LAYER, material: floorMaterial, name: "floor", position: [0, Y_OFFSET, 0], rotation: [-Math.PI / 2, 0, 0], children: _jsx("shapeGeometry", { args: [floorShape] }) }), _jsx("mesh", { geometry: wallGeometry, layers: ZONE_LAYER, material: wallMaterial, name: "walls" })] }));
161
181
  };
@@ -0,0 +1,2 @@
1
+ export declare const GroundOccluder: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ground-occluder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+CAqE1B,CAAA"}