@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,137 +1,260 @@
1
1
  import { useFrame, useThree } from '@react-three/fiber';
2
- import { useEffect, useRef } from 'react';
3
- import { Color, UnsignedByteType } from 'three';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Color, Layers, UnsignedByteType } from 'three';
4
4
  import { outline } from 'three/addons/tsl/display/OutlineNode.js';
5
5
  import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
6
6
  import { traa } from 'three/addons/tsl/display/TRAANode.js';
7
- import { add, colorToDirection, diffuseColor, directionToColor, mrt, normalView, oscSine, output, pass, sample, time, uniform, vec4, velocity, } from 'three/tsl';
8
- import { PostProcessing } from 'three/webgpu';
7
+ import { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js';
8
+ import { add, colorToDirection, diffuseColor, directionToColor, float, mix, mrt, normalView, oscSine, output, pass, sample, time, uniform, vec4, velocity, } from 'three/tsl';
9
+ import { RenderPipeline } from 'three/webgpu';
10
+ import { SCENE_LAYER, ZONE_LAYER } from '../../lib/layers';
9
11
  import useViewer from '../../store/use-viewer';
10
12
  // SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion
11
13
  export const SSGI_PARAMS = {
12
14
  enabled: true,
13
- sliceCount: 2,
14
- stepCount: 8,
15
+ sliceCount: 1,
16
+ stepCount: 4,
15
17
  radius: 1,
16
18
  expFactor: 1.5,
17
19
  thickness: 0.5,
18
20
  backfaceLighting: 0.5,
19
21
  aoIntensity: 1.5,
20
- giIntensity: 0.5,
22
+ giIntensity: 0,
21
23
  useLinearThickness: false,
22
24
  useScreenSpaceSampling: true,
23
- useTemporalFiltering: true,
25
+ useTemporalFiltering: false,
24
26
  };
27
+ const MAX_PIPELINE_RETRIES = 3;
28
+ const RETRY_DELAY_MS = 500;
29
+ const DARK_BG = '#1f2433';
30
+ const LIGHT_BG = '#ffffff';
25
31
  const PostProcessingPasses = () => {
26
32
  const { gl: renderer, scene, camera } = useThree();
27
- const postProcessingRef = useRef(null);
33
+ const renderPipelineRef = useRef(null);
34
+ const hasPipelineErrorRef = useRef(false);
35
+ const retryCountRef = useRef(0);
36
+ const [isInitialized, setIsInitialized] = useState(false);
37
+ // Background color uniform — updated every frame via lerp, read by the TSL pipeline.
38
+ // Initialised from the current theme so there's no flash on first render.
39
+ const initBg = useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG;
40
+ const bgUniform = useRef(uniform(new Color(initBg)));
41
+ const bgCurrent = useRef(new Color(initBg));
42
+ const bgTarget = useRef(new Color());
43
+ const zoneLayers = useMemo(() => {
44
+ const l = new Layers();
45
+ l.enable(ZONE_LAYER);
46
+ l.disable(SCENE_LAYER);
47
+ return l;
48
+ }, []);
49
+ // Subscribe to projectId so the pipeline rebuilds on project switch
50
+ const projectId = useViewer((s) => s.projectId);
51
+ // Bump this to force a pipeline rebuild (used by retry logic)
52
+ const [pipelineVersion, setPipelineVersion] = useState(0);
53
+ const requestPipelineRebuild = useCallback(() => {
54
+ setPipelineVersion((v) => v + 1);
55
+ }, []);
56
+ // Renderer initialization
28
57
  useEffect(() => {
29
- if (!renderer || !scene || !camera) {
58
+ let mounted = true;
59
+ const initRenderer = async () => {
60
+ try {
61
+ if (renderer && renderer.init) {
62
+ await renderer.init();
63
+ }
64
+ if (mounted) {
65
+ setIsInitialized(true);
66
+ }
67
+ }
68
+ catch (error) {
69
+ console.error('[viewer] Failed to initialize renderer for post-processing.', error);
70
+ if (mounted) {
71
+ setIsInitialized(false);
72
+ }
73
+ }
74
+ };
75
+ initRenderer();
76
+ return () => {
77
+ mounted = false;
78
+ };
79
+ }, [renderer]);
80
+ // Reset retry count when project changes
81
+ useEffect(() => {
82
+ retryCountRef.current = 0;
83
+ }, []);
84
+ // Build / rebuild the post-processing pipeline
85
+ useEffect(() => {
86
+ if (!(renderer && scene && camera && isInitialized)) {
30
87
  return;
31
88
  }
32
- // Scene pass with MRT for SSGI
33
- const scenePass = pass(scene, camera);
34
- scenePass.setMRT(mrt({
35
- output: output,
36
- diffuseColor: diffuseColor,
37
- normal: directionToColor(normalView),
38
- velocity: velocity,
39
- }));
40
- // Get texture outputs
41
- const scenePassColor = scenePass.getTextureNode('output');
42
- const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
43
- const scenePassDepth = scenePass.getTextureNode('depth');
44
- const scenePassNormal = scenePass.getTextureNode('normal');
45
- const scenePassVelocity = scenePass.getTextureNode('velocity');
46
- // Optimize texture bandwidth
47
- const diffuseTexture = scenePass.getTexture('diffuseColor');
48
- diffuseTexture.type = UnsignedByteType;
49
- const normalTexture = scenePass.getTexture('normal');
50
- normalTexture.type = UnsignedByteType;
51
- // Extract normal from color-encoded texture
52
- const sceneNormal = sample((uv) => {
53
- return colorToDirection(scenePassNormal.sample(uv));
54
- });
55
- // SSGI Pass (cast to PerspectiveCamera for SSGI)
56
- const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
57
- giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
58
- giPass.stepCount.value = SSGI_PARAMS.stepCount;
59
- giPass.radius.value = SSGI_PARAMS.radius;
60
- giPass.expFactor.value = SSGI_PARAMS.expFactor;
61
- giPass.thickness.value = SSGI_PARAMS.thickness;
62
- giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
63
- giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
64
- giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
65
- giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
66
- giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
67
- giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
68
- // Extract GI and AO from SSGI pass
69
- const gi = giPass.rgb;
70
- const ao = giPass.a;
71
- // Composite: scene * AO + diffuse * GI
72
- const compositePass = vec4(add(scenePassColor.rgb.mul(ao), scenePassDiffuse.rgb.mul(gi)), scenePassColor.a);
73
- function generateSelectedOutlinePass() {
74
- const edgeStrength = uniform(3);
75
- const edgeGlow = uniform(0);
76
- const edgeThickness = uniform(1);
77
- const visibleEdgeColor = uniform(new Color(0xffffff));
78
- const hiddenEdgeColor = uniform(new Color(0xf3ff47));
79
- const outlinePass = outline(scene, camera, {
80
- selectedObjects: useViewer.getState().outliner.selectedObjects,
81
- edgeGlow,
82
- edgeThickness,
89
+ hasPipelineErrorRef.current = false;
90
+ // Clear outliner arrays synchronously to prevent stale Object3D refs
91
+ // from the previous project leaking into the new pipeline's outline passes.
92
+ const outliner = useViewer.getState().outliner;
93
+ outliner.selectedObjects.length = 0;
94
+ outliner.hoveredObjects.length = 0;
95
+ try {
96
+ // Scene pass with MRT for SSGI
97
+ const scenePass = pass(scene, camera);
98
+ scenePass.setMRT(mrt({
99
+ output,
100
+ diffuseColor,
101
+ normal: directionToColor(normalView),
102
+ velocity,
103
+ }));
104
+ // Get texture outputs
105
+ const scenePassColor = scenePass.getTextureNode('output');
106
+ const scenePassDiffuse = scenePass.getTextureNode('diffuseColor');
107
+ const scenePassDepth = scenePass.getTextureNode('depth');
108
+ const scenePassNormal = scenePass.getTextureNode('normal');
109
+ const scenePassVelocity = scenePass.getTextureNode('velocity');
110
+ // Optimize texture bandwidth
111
+ const diffuseTexture = scenePass.getTexture('diffuseColor');
112
+ diffuseTexture.type = UnsignedByteType;
113
+ const normalTexture = scenePass.getTexture('normal');
114
+ normalTexture.type = UnsignedByteType;
115
+ // Extract normal from color-encoded texture
116
+ const sceneNormal = sample((uv) => {
117
+ return colorToDirection(scenePassNormal.sample(uv));
83
118
  });
84
- const { visibleEdge, hiddenEdge } = outlinePass;
85
- const outlineColor = visibleEdge
86
- .mul(visibleEdgeColor)
87
- .add(hiddenEdge.mul(hiddenEdgeColor))
88
- .mul(edgeStrength);
89
- return outlineColor;
119
+ const zonePass = pass(scene, camera);
120
+ zonePass.setLayers(zoneLayers);
121
+ // SSGI Pass (cast to PerspectiveCamera for SSGI)
122
+ const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera);
123
+ giPass.sliceCount.value = SSGI_PARAMS.sliceCount;
124
+ giPass.stepCount.value = SSGI_PARAMS.stepCount;
125
+ giPass.radius.value = SSGI_PARAMS.radius;
126
+ giPass.expFactor.value = SSGI_PARAMS.expFactor;
127
+ giPass.thickness.value = SSGI_PARAMS.thickness;
128
+ giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting;
129
+ giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity;
130
+ giPass.giIntensity.value = SSGI_PARAMS.giIntensity;
131
+ giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness;
132
+ giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling;
133
+ giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering;
134
+ const giTexture = giPass.getTextureNode();
135
+ // DenoiseNode only denoises RGB — alpha is passed through unchanged.
136
+ // SSGI packs AO into alpha, so we remap it into RGB before denoising.
137
+ // convertToTexture() inside denoise() will call rtt() on this vec4 node automatically.
138
+ const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1));
139
+ const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera);
140
+ denoisePass.index.value = 0;
141
+ denoisePass.radius.value = 4;
142
+ const gi = giPass.rgb;
143
+ const ao = denoisePass.r;
144
+ // const gi = giPass.rgb;
145
+ // const ao = giPass.a;
146
+ // Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
147
+ // so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
148
+ // WebGPU only applies clearColorValue to MRT attachment 0 (output), so scenePassColor.a
149
+ // is the reliable geometry mask — no normals, no flicker.
150
+ const hasGeometry = scenePassColor.a;
151
+ const contentAlpha = hasGeometry.max(zonePass.a);
152
+ // Composite: scene * AO + diffuse * GI
153
+ const compositePass = vec4(add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))), contentAlpha);
154
+ function generateSelectedOutlinePass() {
155
+ const edgeStrength = uniform(3);
156
+ const edgeGlow = uniform(0);
157
+ const edgeThickness = uniform(1);
158
+ const visibleEdgeColor = uniform(new Color(0xff_ff_ff));
159
+ const hiddenEdgeColor = uniform(new Color(0xf3_ff_47));
160
+ const outlinePass = outline(scene, camera, {
161
+ selectedObjects: useViewer.getState().outliner.selectedObjects,
162
+ edgeGlow,
163
+ edgeThickness,
164
+ });
165
+ const { visibleEdge, hiddenEdge } = outlinePass;
166
+ const outlineColor = visibleEdge
167
+ .mul(visibleEdgeColor)
168
+ .add(hiddenEdge.mul(hiddenEdgeColor))
169
+ .mul(edgeStrength);
170
+ return outlineColor;
171
+ }
172
+ function generateHoverOutlinePass() {
173
+ const edgeStrength = uniform(5);
174
+ const edgeGlow = uniform(0.5);
175
+ const edgeThickness = uniform(1.5);
176
+ const pulsePeriod = uniform(3);
177
+ const visibleEdgeColor = uniform(new Color(0x00_aa_ff));
178
+ const hiddenEdgeColor = uniform(new Color(0xf3_ff_47));
179
+ const outlinePass = outline(scene, camera, {
180
+ selectedObjects: useViewer.getState().outliner.hoveredObjects,
181
+ edgeGlow,
182
+ edgeThickness,
183
+ });
184
+ const { visibleEdge, hiddenEdge } = outlinePass;
185
+ const period = time.div(pulsePeriod).mul(2);
186
+ const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ]
187
+ const outlineColor = visibleEdge
188
+ .mul(visibleEdgeColor)
189
+ .add(hiddenEdge.mul(hiddenEdgeColor))
190
+ .mul(edgeStrength);
191
+ const outlinePulse = pulsePeriod.greaterThan(0).select(outlineColor.mul(osc), outlineColor);
192
+ return outlinePulse;
193
+ }
194
+ const selectedOutlinePass = generateSelectedOutlinePass();
195
+ const hoverOutlinePass = generateHoverOutlinePass();
196
+ // Combine composite with outlines BEFORE applying TRAA
197
+ const compositeWithOutlines = SSGI_PARAMS.enabled
198
+ ? vec4(add(compositePass.rgb, selectedOutlinePass.add(hoverOutlinePass)), compositePass.a)
199
+ : vec4(add(scenePassColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), scenePassColor.a);
200
+ // TRAA (Temporal Reprojection Anti-Aliasing) - applied AFTER combining everything
201
+ const traaOutput = traa(compositeWithOutlines, scenePassDepth, scenePassVelocity, camera);
202
+ // For zone-over-background pixels, scenePassDepth=1.0 (no scene geometry) causes TRAA
203
+ // to output black. Use hasGeometry to blend: geometry pixels use traaRgb, all others
204
+ // (zones over background, pure background) use compositePass.rgb directly.
205
+ const traaRgb = traaOutput.rgb;
206
+ const colorSource = mix(compositePass.rgb, traaRgb, hasGeometry);
207
+ const finalOutput = vec4(mix(bgUniform.current, colorSource, contentAlpha), float(1));
208
+ const renderPipeline = new RenderPipeline(renderer);
209
+ renderPipeline.outputNode = finalOutput;
210
+ renderPipelineRef.current = renderPipeline;
90
211
  }
91
- function generateHoverOutlinePass() {
92
- const edgeStrength = uniform(5);
93
- const edgeGlow = uniform(0.5);
94
- const edgeThickness = uniform(1.5);
95
- const pulsePeriod = uniform(3);
96
- const visibleEdgeColor = uniform(new Color(0x00aaff));
97
- const hiddenEdgeColor = uniform(new Color(0xf3ff47));
98
- const outlinePass = outline(scene, camera, {
99
- selectedObjects: useViewer.getState().outliner.hoveredObjects,
100
- edgeGlow,
101
- edgeThickness,
102
- });
103
- const { visibleEdge, hiddenEdge } = outlinePass;
104
- const period = time.div(pulsePeriod).mul(2);
105
- const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ]
106
- const outlineColor = visibleEdge
107
- .mul(visibleEdgeColor)
108
- .add(hiddenEdge.mul(hiddenEdgeColor))
109
- .mul(edgeStrength);
110
- const outlinePulse = pulsePeriod.greaterThan(0).select(outlineColor.mul(osc), outlineColor);
111
- return outlinePulse;
212
+ catch (error) {
213
+ hasPipelineErrorRef.current = true;
214
+ console.error('[viewer] Failed to set up post-processing pipeline. Rendering without post FX.', error);
215
+ if (renderPipelineRef.current) {
216
+ renderPipelineRef.current.dispose();
217
+ }
218
+ renderPipelineRef.current = null;
112
219
  }
113
- // Setup post-processing
114
- const postProcessing = new PostProcessing(renderer);
115
- const selectedOutlinePass = generateSelectedOutlinePass();
116
- const hoverOutlinePass = generateHoverOutlinePass();
117
- // Combine composite with outlines BEFORE applying TRAA
118
- const compositeWithOutlines = SSGI_PARAMS.enabled
119
- ? vec4(add(compositePass.rgb, selectedOutlinePass.add(hoverOutlinePass)), compositePass.a)
120
- : vec4(add(scenePassColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), scenePassColor.a);
121
- // TRAA (Temporal Reprojection Anti-Aliasing) - applied AFTER combining everything
122
- const finalOutput = traa(compositeWithOutlines, scenePassDepth, scenePassVelocity, camera);
123
- postProcessing.outputNode = finalOutput;
124
- postProcessingRef.current = postProcessing;
125
220
  return () => {
126
- if (postProcessingRef.current) {
127
- postProcessingRef.current.dispose();
221
+ if (renderPipelineRef.current) {
222
+ renderPipelineRef.current.dispose();
128
223
  }
129
- postProcessingRef.current = null;
224
+ renderPipelineRef.current = null;
130
225
  };
131
- }, [renderer, scene, camera]);
132
- useFrame(() => {
133
- if (postProcessingRef.current) {
134
- postProcessingRef.current.render();
226
+ }, [renderer, scene, camera, isInitialized, zoneLayers]);
227
+ useFrame((_, delta) => {
228
+ // Animate background colour toward the current theme target (same lerp as AnimatedBackground)
229
+ bgTarget.current.set(useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG);
230
+ bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
231
+ bgUniform.current.value.copy(bgCurrent.current);
232
+ if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
233
+ return;
234
+ }
235
+ try {
236
+ // Clear alpha=0 so background pixels in the output MRT attachment (index 0) get a=0,
237
+ // making scenePassColor.a a reliable geometry mask (geometry pixels write a=1 via output node).
238
+ ;
239
+ renderer.setClearAlpha(0);
240
+ renderPipelineRef.current.render();
241
+ }
242
+ catch (error) {
243
+ hasPipelineErrorRef.current = true;
244
+ console.error('[viewer] Post-processing render pass failed.', error);
245
+ if (renderPipelineRef.current) {
246
+ renderPipelineRef.current.dispose();
247
+ }
248
+ renderPipelineRef.current = null;
249
+ if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
250
+ // Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
251
+ retryCountRef.current++;
252
+ console.warn(`[viewer] Scheduling post-processing rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
253
+ setTimeout(requestPipelineRebuild, RETRY_DELAY_MS);
254
+ }
255
+ else {
256
+ console.error('[viewer] Post-processing retries exhausted. Rendering without post FX for this session.');
257
+ }
135
258
  }
136
259
  }, 1);
137
260
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAkOA,eAAO,MAAM,gBAAgB,+CAoE5B,CAAA"}
1
+ {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAwQA,eAAO,MAAM,gBAAgB,+CAsE5B,CAAA"}
@@ -46,12 +46,19 @@ const isNodeOnLevel = (node, levelId) => {
46
46
  // Direct child of level
47
47
  if (node.parentId === levelId)
48
48
  return true;
49
- // Wall-attached items (windows/doors): check if parent wall is on the level
50
- if (node.type === 'item' && node.parentId) {
49
+ // Wall-attached nodes (window/door/item): check if parent wall is on the level
50
+ if ((node.type === 'item' || node.type === 'window' || node.type === 'door') && node.parentId) {
51
51
  const parentNode = nodes[node.parentId];
52
52
  if (parentNode?.type === 'wall' && parentNode.parentId === levelId) {
53
53
  return true;
54
54
  }
55
+ // Ceiling/slab/roof-attached items: check if parent structure is on the level
56
+ if ((parentNode?.type === 'ceiling' ||
57
+ parentNode?.type === 'slab' ||
58
+ parentNode?.type === 'roof') &&
59
+ parentNode.parentId === levelId) {
60
+ return true;
61
+ }
55
62
  }
56
63
  return false;
57
64
  };
@@ -97,7 +104,7 @@ const isNodeInZone = (node, levelId, zoneId) => {
97
104
  }
98
105
  return false;
99
106
  }
100
- if (node.type === 'roof') {
107
+ if (node.type === 'roof' || node.type === 'roof-segment') {
101
108
  // Roofs on the same level are valid when zone is selected
102
109
  return true;
103
110
  }
@@ -105,6 +112,17 @@ const isNodeInZone = (node, levelId, zoneId) => {
105
112
  };
106
113
  const getStrategy = () => {
107
114
  const { buildingId, levelId, zoneId } = useViewer.getState().selection;
115
+ const computeNextIds = (node, selectedIds, event) => {
116
+ const isMeta = event?.metaKey || event?.nativeEvent?.metaKey;
117
+ const isCtrl = event?.ctrlKey || event?.nativeEvent?.ctrlKey;
118
+ if (isMeta || isCtrl) {
119
+ if (selectedIds.includes(node.id)) {
120
+ return selectedIds.filter((id) => id !== node.id);
121
+ }
122
+ return [...selectedIds, node.id];
123
+ }
124
+ return [node.id];
125
+ };
108
126
  // No building selected -> can select buildings
109
127
  if (!buildingId) {
110
128
  return {
@@ -144,20 +162,21 @@ const getStrategy = () => {
144
162
  isValid: (node) => node.type === 'zone' && node.parentId === levelId,
145
163
  };
146
164
  }
147
- // Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs, windows)
165
+ // Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs, windows, doors)
148
166
  return {
149
- types: ['wall', 'item', 'slab', 'ceiling', 'roof', 'window'],
150
- handleClick: (node) => {
151
- const { selectedIds } = useViewer.getState().selection;
152
- // Toggle selection - if already selected, deselect; otherwise select
153
- if (selectedIds.includes(node.id)) {
154
- useViewer
155
- .getState()
156
- .setSelection({ selectedIds: selectedIds.filter((id) => id !== node.id) });
157
- }
158
- else {
159
- useViewer.getState().setSelection({ selectedIds: [node.id] });
167
+ types: ['wall', 'item', 'slab', 'ceiling', 'roof', 'roof-segment', 'window', 'door'],
168
+ handleClick: (node, nativeEvent) => {
169
+ let nodeToSelect = node;
170
+ if (node.type === 'roof-segment' && node.parentId) {
171
+ const parentNode = useScene.getState().nodes[node.parentId];
172
+ if (parentNode && parentNode.type === 'roof') {
173
+ nodeToSelect = parentNode;
174
+ }
160
175
  }
176
+ const { selectedIds } = useViewer.getState().selection;
177
+ useViewer
178
+ .getState()
179
+ .setSelection({ selectedIds: computeNextIds(nodeToSelect, selectedIds, nativeEvent) });
161
180
  },
162
181
  handleDeselect: () => {
163
182
  const { selectedIds } = useViewer.getState().selection;
@@ -170,7 +189,16 @@ const getStrategy = () => {
170
189
  }
171
190
  },
172
191
  isValid: (node) => {
173
- const validTypes = ['wall', 'item', 'slab', 'ceiling', 'roof', 'window'];
192
+ const validTypes = [
193
+ 'wall',
194
+ 'item',
195
+ 'slab',
196
+ 'ceiling',
197
+ 'roof',
198
+ 'roof-segment',
199
+ 'window',
200
+ 'door',
201
+ ];
174
202
  if (!validTypes.includes(node.type))
175
203
  return false;
176
204
  return isNodeInZone(node, levelId, zoneId);
@@ -207,7 +235,7 @@ export const SelectionManager = () => {
207
235
  return;
208
236
  event.stopPropagation();
209
237
  clickHandledRef.current = true;
210
- strategy.handleClick(event.node);
238
+ strategy.handleClick(event.node, event.nativeEvent);
211
239
  // Clear hover immediately after clicking on building/level/zone
212
240
  useViewer.setState({ hoveredId: null });
213
241
  };
@@ -221,7 +249,9 @@ export const SelectionManager = () => {
221
249
  'slab',
222
250
  'ceiling',
223
251
  'roof',
252
+ 'roof-segment',
224
253
  'window',
254
+ 'door',
225
255
  ];
226
256
  for (const type of allTypes) {
227
257
  emitter.on(`${type}:enter`, onEnter);
@@ -1 +1 @@
1
- {"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,+CAOxB,CAAA"}
1
+ {"version":3,"file":"viewer-camera.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/viewer-camera.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,YAAY,+CAQxB,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { OrthographicCamera, PerspectiveCamera } from "@react-three/drei";
3
- import useViewer from "../../store/use-viewer";
2
+ import { OrthographicCamera, PerspectiveCamera } from '@react-three/drei';
3
+ import useViewer from '../../store/use-viewer';
4
4
  export const ViewerCamera = () => {
5
5
  const cameraMode = useViewer((state) => state.cameraMode);
6
6
  return cameraMode === 'perspective' ? (_jsx(PerspectiveCamera, { far: 1000, fov: 50, makeDefault: true, near: 0.1, position: [10, 10, 10] })) : (_jsx(OrthographicCamera, { far: 1000, makeDefault: true, near: -1000, position: [10, 10, 10], zoom: 20 }));
@@ -1,4 +1,4 @@
1
- import { useGLTF } from "@react-three/drei";
1
+ import { useGLTF } from '@react-three/drei';
2
2
  declare const useGLTFKTX2: (path: string) => ReturnType<typeof useGLTF>;
3
3
  export { useGLTFKTX2 };
4
4
  //# sourceMappingURL=use-gltf-ktx2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-gltf-ktx2.d.ts","sourceRoot":"","sources":["../../src/hooks/use-gltf-ktx2.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAQ3C,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,UAAU,CAAC,OAAO,OAAO,CAS5D,CAAA;AACD,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"use-gltf-ktx2.d.ts","sourceRoot":"","sources":["../../src/hooks/use-gltf-ktx2.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAU3C,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,UAAU,CAAC,OAAO,OAAO,CA2B5D,CAAA;AACD,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -1,15 +1,33 @@
1
- import { useGLTF } from "@react-three/drei";
2
- import { useThree } from "@react-three/fiber";
3
- import { KTX2Loader } from "three/examples/jsm/Addons.js";
4
- import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
1
+ import { useGLTF } from '@react-three/drei';
2
+ import { useThree } from '@react-three/fiber';
3
+ import { KTX2Loader } from 'three/examples/jsm/Addons.js';
4
+ import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
5
5
  const ktx2LoaderInstance = new KTX2Loader();
6
6
  ktx2LoaderInstance.setTranscoderPath('https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/basis/');
7
+ const ktx2ConfiguredRenderers = new WeakSet();
8
+ const ktx2WarningLoggedRenderers = new WeakSet();
7
9
  const useGLTFKTX2 = (path) => {
8
10
  const gl = useThree((state) => state.gl);
9
11
  return useGLTF(path, true, true, (loader) => {
10
- ktx2LoaderInstance.detectSupport(gl);
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- loader.setKTX2Loader(ktx2LoaderInstance);
12
+ const renderer = gl;
13
+ if (!ktx2ConfiguredRenderers.has(renderer)) {
14
+ try {
15
+ ktx2LoaderInstance.detectSupport(gl);
16
+ ktx2ConfiguredRenderers.add(renderer);
17
+ }
18
+ catch (error) {
19
+ // Some WebGPU flows can transiently call this before backend init.
20
+ // Avoid crashing the whole scene; scans may render without KTX2 on this pass.
21
+ if (!ktx2WarningLoggedRenderers.has(renderer)) {
22
+ console.warn('[viewer] Skipping KTX2 support detection for now.', error);
23
+ ktx2WarningLoggedRenderers.add(renderer);
24
+ }
25
+ }
26
+ }
27
+ if (ktx2ConfiguredRenderers.has(renderer)) {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ loader.setKTX2Loader(ktx2LoaderInstance);
30
+ }
13
31
  loader.setMeshoptDecoder(MeshoptDecoder);
14
32
  });
15
33
  };
@@ -1,4 +1,4 @@
1
- import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type ItemEvent, type ItemNode, type LevelEvent, type LevelNode, type RoofEvent, type RoofNode, type SiteEvent, type SiteNode, type SlabEvent, type SlabNode, type WallEvent, type WallNode, type WindowEvent, type WindowNode, type ZoneEvent, type ZoneNode } from '@pascal-app/core';
1
+ import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type DoorEvent, type DoorNode, type ItemEvent, type ItemNode, type LevelEvent, type LevelNode, type RoofEvent, type RoofNode, type RoofSegmentEvent, type RoofSegmentNode, type SiteEvent, type SiteNode, type SlabEvent, type SlabNode, type WallEvent, type WallNode, type WindowEvent, type WindowNode, type ZoneEvent, type ZoneNode } from '@pascal-app/core';
2
2
  import type { ThreeEvent } from '@react-three/fiber';
3
3
  type NodeConfig = {
4
4
  site: {
@@ -37,10 +37,18 @@ type NodeConfig = {
37
37
  node: RoofNode;
38
38
  event: RoofEvent;
39
39
  };
40
+ 'roof-segment': {
41
+ node: RoofSegmentNode;
42
+ event: RoofSegmentEvent;
43
+ };
40
44
  window: {
41
45
  node: WindowNode;
42
46
  event: WindowEvent;
43
47
  };
48
+ door: {
49
+ node: DoorNode;
50
+ event: DoorEvent;
51
+ };
44
52
  };
45
53
  type NodeType = keyof NodeConfig;
46
54
  export declare function useNodeEvents<T extends NodeType>(node: NodeConfig[T]['node'], type: T): {
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAGhB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAGpD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,QAAQ,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,CAAA;IACtD,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,OAAO,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAAA;IACnD,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,MAAM,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAA;CACjD,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,UAAU,CAAA;AAEhC,wBAAgB,aAAa,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;uBAiB/D,UAAU,CAAC,YAAY,CAAC;qBAK1B,UAAU,CAAC,YAAY,CAAC;iBAK5B,UAAU,CAAC,YAAY,CAAC;wBAKjB,UAAU,CAAC,YAAY,CAAC;wBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIzB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;EAK9C"}
1
+ {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,QAAQ,EAGb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAGpD,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,QAAQ,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,CAAA;IACtD,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,OAAO,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAAA;IACnD,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;IAC1C,cAAc,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,KAAK,EAAE,gBAAgB,CAAA;KAAE,CAAA;IAClE,MAAM,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,CAAA;IAChD,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED,KAAK,QAAQ,GAAG,MAAM,UAAU,CAAA;AAEhC,wBAAgB,aAAa,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;uBAiB/D,UAAU,CAAC,YAAY,CAAC;qBAK1B,UAAU,CAAC,YAAY,CAAC;iBAQ5B,UAAU,CAAC,YAAY,CAAC;wBAIjB,UAAU,CAAC,YAAY,CAAC;wBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIzB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;uBAIxB,UAAU,CAAC,YAAY,CAAC;EAK9C"}
@@ -28,13 +28,13 @@ export function useNodeEvents(node, type) {
28
28
  if (e.button !== 0)
29
29
  return;
30
30
  emit('pointerup', e);
31
+ // Synthesize a click event on pointer up to be more forgiving than R3F's default onClick
32
+ // which often fails if the mouse moves even 1 pixel.
33
+ emit('click', e);
31
34
  },
32
35
  onClick: (e) => {
33
- if (useViewer.getState().cameraDragging)
34
- return;
35
- if (e.button !== 0)
36
- return;
37
- emit('click', e);
36
+ // Disable default R3F click since we synthesize it on pointerup
37
+ // This prevents double-clicks from firing twice.
38
38
  },
39
39
  onPointerEnter: (e) => {
40
40
  if (useViewer.getState().cameraDragging)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { default as useViewer } from './store/use-viewer';
3
2
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
3
+ export { SCENE_LAYER, ZONE_LAYER } from './lib/layers';
4
+ export { default as useViewer } from './store/use-viewer';
5
+ export { InteractiveSystem } from './systems/interactive/interactive-system';
6
+ export { snapLevelsToTruePositions } from './systems/level/level-utils';
4
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAC5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { default as useViewer } from './store/use-viewer';
3
2
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
3
+ export { SCENE_LAYER, ZONE_LAYER } from './lib/layers';
4
+ export { default as useViewer } from './store/use-viewer';
5
+ export { InteractiveSystem } from './systems/interactive/interactive-system';
6
+ export { snapLevelsToTruePositions } from './systems/level/level-utils';