@pascal-app/viewer 0.1.13 → 0.3.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 (92) hide show
  1. package/dist/components/error-boundary.d.ts +18 -0
  2. package/dist/components/error-boundary.d.ts.map +1 -0
  3. package/dist/components/error-boundary.js +11 -0
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +16 -9
  6. package/dist/components/renderers/door/door-renderer.d.ts +5 -0
  7. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/door/door-renderer.js +11 -0
  9. package/dist/components/renderers/guide/guide-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/guide/guide-renderer.js +4 -2
  11. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/item/item-renderer.js +98 -7
  13. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  14. package/dist/components/renderers/node-renderer.js +3 -1
  15. package/dist/components/renderers/roof/roof-materials.d.ts +4 -0
  16. package/dist/components/renderers/roof/roof-materials.d.ts.map +1 -0
  17. package/dist/components/renderers/roof/roof-materials.js +16 -0
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +5 -1
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +5 -0
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -0
  22. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +13 -0
  23. package/dist/components/renderers/scan/scan-renderer.d.ts.map +1 -1
  24. package/dist/components/renderers/scan/scan-renderer.js +3 -1
  25. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  26. package/dist/components/renderers/scene-renderer.js +3 -3
  27. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  28. package/dist/components/renderers/site/site-renderer.js +4 -19
  29. package/dist/components/renderers/slab/slab-renderer.js +1 -1
  30. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  31. package/dist/components/renderers/wall/wall-renderer.js +7 -3
  32. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  33. package/dist/components/renderers/window/window-renderer.js +2 -1
  34. package/dist/components/renderers/zone/zone-renderer.d.ts.map +1 -1
  35. package/dist/components/renderers/zone/zone-renderer.js +33 -13
  36. package/dist/components/viewer/ground-occluder.d.ts +2 -0
  37. package/dist/components/viewer/ground-occluder.d.ts.map +1 -0
  38. package/dist/components/viewer/ground-occluder.js +75 -0
  39. package/dist/components/viewer/index.d.ts +1 -0
  40. package/dist/components/viewer/index.d.ts.map +1 -1
  41. package/dist/components/viewer/index.js +59 -6
  42. package/dist/components/viewer/lights.d.ts.map +1 -1
  43. package/dist/components/viewer/lights.js +69 -5
  44. package/dist/components/viewer/perf-monitor.d.ts +2 -0
  45. package/dist/components/viewer/perf-monitor.d.ts.map +1 -0
  46. package/dist/components/viewer/perf-monitor.js +42 -0
  47. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  48. package/dist/components/viewer/post-processing.js +230 -107
  49. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  50. package/dist/components/viewer/selection-manager.js +47 -17
  51. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  52. package/dist/components/viewer/viewer-camera.js +2 -2
  53. package/dist/hooks/use-gltf-ktx2.d.ts +1 -1
  54. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  55. package/dist/hooks/use-gltf-ktx2.js +25 -7
  56. package/dist/hooks/use-grid-events.d.ts +12 -0
  57. package/dist/hooks/use-grid-events.d.ts.map +1 -0
  58. package/dist/hooks/use-grid-events.js +33 -0
  59. package/dist/hooks/use-node-events.d.ts +9 -1
  60. package/dist/hooks/use-node-events.d.ts.map +1 -1
  61. package/dist/hooks/use-node-events.js +5 -5
  62. package/dist/index.d.ts +4 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +4 -1
  65. package/dist/lib/asset-url.d.ts +1 -1
  66. package/dist/lib/asset-url.d.ts.map +1 -1
  67. package/dist/lib/asset-url.js +1 -1
  68. package/dist/lib/layers.d.ts +5 -0
  69. package/dist/lib/layers.d.ts.map +1 -0
  70. package/dist/lib/layers.js +4 -0
  71. package/dist/store/use-item-light-pool.d.ts +18 -0
  72. package/dist/store/use-item-light-pool.d.ts.map +1 -0
  73. package/dist/store/use-item-light-pool.js +30 -0
  74. package/dist/store/use-viewer.d.ts +56 -7
  75. package/dist/store/use-viewer.d.ts.map +1 -1
  76. package/dist/store/use-viewer.js +82 -17
  77. package/dist/systems/interactive/interactive-system.d.ts +2 -0
  78. package/dist/systems/interactive/interactive-system.d.ts.map +1 -0
  79. package/dist/systems/interactive/interactive-system.js +95 -0
  80. package/dist/systems/item-light/item-light-system.d.ts +2 -0
  81. package/dist/systems/item-light/item-light-system.d.ts.map +1 -0
  82. package/dist/systems/item-light/item-light-system.js +235 -0
  83. package/dist/systems/level/level-system.d.ts.map +1 -1
  84. package/dist/systems/level/level-system.js +19 -8
  85. package/dist/systems/level/level-utils.d.ts +17 -0
  86. package/dist/systems/level/level-utils.d.ts.map +1 -0
  87. package/dist/systems/level/level-utils.js +82 -0
  88. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  89. package/dist/systems/wall/wall-cutout.js +2 -4
  90. package/dist/systems/zone/zone-system.d.ts.map +1 -1
  91. package/dist/systems/zone/zone-system.js +29 -3
  92. package/package.json +7 -5
@@ -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';
@@ -1,4 +1,4 @@
1
- export declare const ASSETS_CDN_URL = "https://editor.pascal.app";
1
+ export declare const ASSETS_CDN_URL: string;
2
2
  /**
3
3
  * Resolves an asset URL to the appropriate format:
4
4
  * - If URL starts with http:// or https://, return as-is (external URL)
@@ -1 +1 @@
1
- {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/lib/asset-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,8BAA8B,CAAA;AAEzD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiB3E"}
1
+ {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../../src/lib/asset-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,QAAwE,CAAA;AAEnG;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgB5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAiB3E"}
@@ -1,5 +1,5 @@
1
1
  import { loadAssetUrl } from '@pascal-app/core';
2
- export const ASSETS_CDN_URL = 'https://editor.pascal.app';
2
+ export const ASSETS_CDN_URL = process.env.NEXT_PUBLIC_ASSETS_CDN_URL || 'https://editor.pascal.app';
3
3
  /**
4
4
  * Resolves an asset URL to the appropriate format:
5
5
  * - If URL starts with http:// or https://, return as-is (external URL)
@@ -0,0 +1,5 @@
1
+ /** Default Three.js layer for main scene geometry. */
2
+ export declare const SCENE_LAYER = 0;
3
+ /** Layer used for zone rendering (floor fills and wall borders). */
4
+ export declare const ZONE_LAYER = 2;
5
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/lib/layers.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B,oEAAoE;AACpE,eAAO,MAAM,UAAU,IAAI,CAAA"}
@@ -0,0 +1,4 @@
1
+ /** Default Three.js layer for main scene geometry. */
2
+ export const SCENE_LAYER = 0;
3
+ /** Layer used for zone rendering (floor fills and wall borders). */
4
+ export const ZONE_LAYER = 2;
@@ -0,0 +1,18 @@
1
+ import type { AnyNodeId, Interactive, LightEffect } from '@pascal-app/core';
2
+ export type LightRegistration = {
3
+ nodeId: AnyNodeId;
4
+ effect: LightEffect;
5
+ toggleIndex: number;
6
+ sliderIndex: number;
7
+ sliderMin: number;
8
+ sliderMax: number;
9
+ hasSlider: boolean;
10
+ };
11
+ type ItemLightPoolStore = {
12
+ registrations: Map<string, LightRegistration>;
13
+ register: (key: string, nodeId: AnyNodeId, effect: LightEffect, interactive: Interactive) => void;
14
+ unregister: (key: string) => void;
15
+ };
16
+ export declare const useItemLightPool: import("zustand").UseBoundStore<import("zustand").StoreApi<ItemLightPoolStore>>;
17
+ export {};
18
+ //# sourceMappingURL=use-item-light-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-item-light-pool.d.ts","sourceRoot":"","sources":["../../src/store/use-item-light-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAiB,MAAM,kBAAkB,CAAA;AAG1F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAC7C,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,KAAK,IAAI,CAAA;IACjG,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC,CAAA;AAED,eAAO,MAAM,gBAAgB,iFAiC1B,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { create } from 'zustand';
2
+ export const useItemLightPool = create((set) => ({
3
+ registrations: new Map(),
4
+ register: (key, nodeId, effect, interactive) => {
5
+ const toggleIndex = interactive.controls.findIndex((c) => c.kind === 'toggle');
6
+ const sliderIndex = interactive.controls.findIndex((c) => c.kind === 'slider');
7
+ const sliderControl = sliderIndex >= 0 ? interactive.controls[sliderIndex] : null;
8
+ const registration = {
9
+ nodeId,
10
+ effect,
11
+ toggleIndex,
12
+ sliderIndex,
13
+ hasSlider: sliderControl !== null,
14
+ sliderMin: sliderControl?.min ?? 0,
15
+ sliderMax: sliderControl?.max ?? 1,
16
+ };
17
+ set((s) => {
18
+ const next = new Map(s.registrations);
19
+ next.set(key, registration);
20
+ return { registrations: next };
21
+ });
22
+ },
23
+ unregister: (key) => {
24
+ set((s) => {
25
+ const next = new Map(s.registrations);
26
+ next.delete(key);
27
+ return { registrations: next };
28
+ });
29
+ },
30
+ }));
@@ -1,10 +1,10 @@
1
- import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from "@pascal-app/core";
2
- import type { Object3D } from "three";
1
+ import type { AnyNode, BaseNode, BuildingNode, LevelNode, ZoneNode } from '@pascal-app/core';
2
+ import type { Object3D } from 'three';
3
3
  type SelectionPath = {
4
- buildingId: BuildingNode["id"] | null;
5
- levelId: LevelNode["id"] | null;
6
- zoneId: ZoneNode["id"] | null;
7
- selectedIds: BaseNode["id"][];
4
+ buildingId: BuildingNode['id'] | null;
5
+ levelId: LevelNode['id'] | null;
6
+ zoneId: ZoneNode['id'] | null;
7
+ selectedIds: BaseNode['id'][];
8
8
  };
9
9
  type Outliner = {
10
10
  selectedObjects: Object3D[];
@@ -16,6 +16,10 @@ type ViewerState = {
16
16
  setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
17
17
  cameraMode: 'perspective' | 'orthographic';
18
18
  setCameraMode: (mode: 'perspective' | 'orthographic') => void;
19
+ theme: 'light' | 'dark';
20
+ setTheme: (theme: 'light' | 'dark') => void;
21
+ unit: 'metric' | 'imperial';
22
+ setUnit: (unit: 'metric' | 'imperial') => void;
19
23
  levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
20
24
  setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
21
25
  wallMode: 'up' | 'cutaway' | 'down';
@@ -24,14 +28,59 @@ type ViewerState = {
24
28
  setShowScans: (show: boolean) => void;
25
29
  showGuides: boolean;
26
30
  setShowGuides: (show: boolean) => void;
31
+ showGrid: boolean;
32
+ setShowGrid: (show: boolean) => void;
33
+ projectId: string | null;
34
+ setProjectId: (id: string | null) => void;
35
+ projectPreferences: Record<string, {
36
+ showScans?: boolean;
37
+ showGuides?: boolean;
38
+ showGrid?: boolean;
39
+ }>;
27
40
  setSelection: (updates: Partial<SelectionPath>) => void;
28
41
  resetSelection: () => void;
29
42
  outliner: Outliner;
30
43
  exportScene: (() => Promise<void>) | null;
31
44
  setExportScene: (fn: (() => Promise<void>) | null) => void;
45
+ debugColors: boolean;
46
+ setDebugColors: (enabled: boolean) => void;
32
47
  cameraDragging: boolean;
33
48
  setCameraDragging: (dragging: boolean) => void;
34
49
  };
35
- declare const useViewer: import("zustand").UseBoundStore<import("zustand").StoreApi<ViewerState>>;
50
+ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ViewerState>, "setState" | "persist"> & {
51
+ setState(partial: ViewerState | Partial<ViewerState> | ((state: ViewerState) => ViewerState | Partial<ViewerState>), replace?: false | undefined): unknown;
52
+ setState(state: ViewerState | ((state: ViewerState) => ViewerState), replace: true): unknown;
53
+ persist: {
54
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
55
+ cameraMode: "perspective" | "orthographic";
56
+ theme: "light" | "dark";
57
+ unit: "metric" | "imperial";
58
+ levelMode: "stacked" | "exploded" | "solo" | "manual";
59
+ wallMode: "up" | "cutaway" | "down";
60
+ projectPreferences: Record<string, {
61
+ showScans?: boolean;
62
+ showGuides?: boolean;
63
+ showGrid?: boolean;
64
+ }>;
65
+ }, unknown>>) => void;
66
+ clearStorage: () => void;
67
+ rehydrate: () => Promise<void> | void;
68
+ hasHydrated: () => boolean;
69
+ onHydrate: (fn: (state: ViewerState) => void) => () => void;
70
+ onFinishHydration: (fn: (state: ViewerState) => void) => () => void;
71
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
72
+ cameraMode: "perspective" | "orthographic";
73
+ theme: "light" | "dark";
74
+ unit: "metric" | "imperial";
75
+ levelMode: "stacked" | "exploded" | "solo" | "manual";
76
+ wallMode: "up" | "cutaway" | "down";
77
+ projectPreferences: Record<string, {
78
+ showScans?: boolean;
79
+ showGuides?: boolean;
80
+ showGrid?: boolean;
81
+ }>;
82
+ }, unknown>>;
83
+ };
84
+ }>;
36
85
  export default useViewer;
37
86
  //# sourceMappingURL=use-viewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAItC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;CAC/B,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;CAC5B,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEjE,UAAU,EAAE,aAAa,GAAG,cAAc,CAAA;IAC1C,aAAa,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,cAAc,KAAK,IAAI,CAAA;IAE7D,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrD,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAA;IAExE,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,KAAK,IAAI,CAAA;IAEtD,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAErC,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAGtC,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IACvD,cAAc,EAAE,MAAM,IAAI,CAAA;IAE1B,QAAQ,EAAE,QAAQ,CAAA;IAGlB,WAAW,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;IACzC,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAE1D,cAAc,EAAE,OAAO,CAAA;IACvB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;CAC/C,CAAA;AAED,QAAA,MAAM,SAAS,0EAwDZ,CAAC;AAEJ,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
@@ -1,33 +1,86 @@
1
- "use client";
2
- import { create } from "zustand";
3
- const useViewer = create()((set, get) => ({
1
+ 'use client';
2
+ import { create } from 'zustand';
3
+ import { persist } from 'zustand/middleware';
4
+ const useViewer = create()(persist((set) => ({
4
5
  selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
5
6
  hoveredId: null,
6
7
  setHoveredId: (id) => set({ hoveredId: id }),
7
- cameraMode: "perspective",
8
+ cameraMode: 'perspective',
8
9
  setCameraMode: (mode) => set({ cameraMode: mode }),
9
- levelMode: "stacked",
10
+ theme: 'light',
11
+ setTheme: (theme) => set({ theme }),
12
+ unit: 'metric',
13
+ setUnit: (unit) => set({ unit }),
14
+ levelMode: 'stacked',
10
15
  setLevelMode: (mode) => set({ levelMode: mode }),
11
- wallMode: 'cutaway',
16
+ wallMode: 'up',
12
17
  setWallMode: (mode) => set({ wallMode: mode }),
13
18
  showScans: true,
14
- setShowScans: (show) => set({ showScans: show }),
19
+ setShowScans: (show) => set((state) => {
20
+ const projectPreferences = { ...(state.projectPreferences || {}) };
21
+ if (state.projectId) {
22
+ projectPreferences[state.projectId] = {
23
+ ...(projectPreferences[state.projectId] || {}),
24
+ showScans: show,
25
+ };
26
+ }
27
+ return { showScans: show, projectPreferences };
28
+ }),
15
29
  showGuides: true,
16
- setShowGuides: (show) => set({ showGuides: show }),
30
+ setShowGuides: (show) => set((state) => {
31
+ const projectPreferences = { ...(state.projectPreferences || {}) };
32
+ if (state.projectId) {
33
+ projectPreferences[state.projectId] = {
34
+ ...(projectPreferences[state.projectId] || {}),
35
+ showGuides: show,
36
+ };
37
+ }
38
+ return { showGuides: show, projectPreferences };
39
+ }),
40
+ showGrid: true,
41
+ setShowGrid: (show) => set((state) => {
42
+ const projectPreferences = { ...(state.projectPreferences || {}) };
43
+ if (state.projectId) {
44
+ projectPreferences[state.projectId] = {
45
+ ...(projectPreferences[state.projectId] || {}),
46
+ showGrid: show,
47
+ };
48
+ }
49
+ return { showGrid: show, projectPreferences };
50
+ }),
51
+ projectId: null,
52
+ setProjectId: (id) => set((state) => {
53
+ if (!id)
54
+ return { projectId: id };
55
+ const prefs = state.projectPreferences?.[id] || {};
56
+ return {
57
+ projectId: id,
58
+ showScans: prefs.showScans ?? true,
59
+ showGuides: prefs.showGuides ?? true,
60
+ showGrid: prefs.showGrid ?? true,
61
+ };
62
+ }),
63
+ projectPreferences: {},
17
64
  setSelection: (updates) => set((state) => {
18
65
  const newSelection = { ...state.selection, ...updates };
19
- // Hierarchy Guard: If we change a high-level parent, reset the children
66
+ // Hierarchy Guard: If we change a high-level parent, reset the children unless explicitly provided
20
67
  if (updates.buildingId !== undefined) {
21
- newSelection.levelId = null;
22
- newSelection.zoneId = null;
23
- newSelection.selectedIds = [];
68
+ if (updates.levelId === undefined)
69
+ newSelection.levelId = null;
70
+ if (updates.zoneId === undefined)
71
+ newSelection.zoneId = null;
72
+ if (updates.selectedIds === undefined)
73
+ newSelection.selectedIds = [];
24
74
  }
25
- else if (updates.levelId !== undefined) {
26
- newSelection.zoneId = null;
27
- newSelection.selectedIds = [];
75
+ if (updates.levelId !== undefined) {
76
+ if (updates.zoneId === undefined)
77
+ newSelection.zoneId = null;
78
+ if (updates.selectedIds === undefined)
79
+ newSelection.selectedIds = [];
28
80
  }
29
- else if (updates.zoneId !== undefined) {
30
- newSelection.selectedIds = [];
81
+ if (updates.zoneId !== undefined) {
82
+ if (updates.selectedIds === undefined)
83
+ newSelection.selectedIds = [];
31
84
  }
32
85
  return { selection: newSelection };
33
86
  }),
@@ -42,7 +95,19 @@ const useViewer = create()((set, get) => ({
42
95
  outliner: { selectedObjects: [], hoveredObjects: [] },
43
96
  exportScene: null,
44
97
  setExportScene: (fn) => set({ exportScene: fn }),
98
+ debugColors: false,
99
+ setDebugColors: (enabled) => set({ debugColors: enabled }),
45
100
  cameraDragging: false,
46
101
  setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
102
+ }), {
103
+ name: 'viewer-preferences',
104
+ partialize: (state) => ({
105
+ cameraMode: state.cameraMode,
106
+ theme: state.theme,
107
+ unit: state.unit,
108
+ levelMode: state.levelMode,
109
+ wallMode: state.wallMode,
110
+ projectPreferences: state.projectPreferences,
111
+ }),
47
112
  }));
48
113
  export default useViewer;
@@ -0,0 +1,2 @@
1
+ export declare const InteractiveSystem: () => import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=interactive-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactive-system.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/interactive-system.tsx"],"names":[],"mappings":"AAwBA,eAAO,MAAM,iBAAiB,+CAgB7B,CAAA"}
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { pointInPolygon, sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
4
+ import { Html } from '@react-three/drei';
5
+ import { createPortal, useFrame } from '@react-three/fiber';
6
+ import { useState } from 'react';
7
+ import { Vector3 } from 'three';
8
+ import { useShallow } from 'zustand/react/shallow';
9
+ import useViewer from '../../store/use-viewer';
10
+ const _tempVec = new Vector3();
11
+ // ---- Parent: one overlay per interactive item ----
12
+ export const InteractiveSystem = () => {
13
+ const interactiveNodeIds = useScene(useShallow((state) => Object.values(state.nodes)
14
+ .filter((n) => n.type === 'item' && n.asset.interactive != null)
15
+ .map((n) => n.id)));
16
+ return (_jsx(_Fragment, { children: interactiveNodeIds.map((id) => (_jsx(ItemControlsOverlay, { nodeId: id }, id))) }));
17
+ };
18
+ // ---- Child: polls sceneRegistry then portals controls into the item group ----
19
+ const ItemControlsOverlay = ({ nodeId }) => {
20
+ const node = useScene((state) => state.nodes[nodeId]);
21
+ const [itemObj, setItemObj] = useState(null);
22
+ useFrame(() => {
23
+ if (itemObj)
24
+ return;
25
+ const obj = sceneRegistry.nodes.get(nodeId);
26
+ if (obj)
27
+ setItemObj(obj);
28
+ });
29
+ const controlValues = useInteractive(useShallow((state) => state.items[nodeId]?.controlValues));
30
+ const setControlValue = useInteractive((state) => state.setControlValue);
31
+ const zoneId = useViewer((s) => s.selection.zoneId);
32
+ const zonePolygon = useScene((s) => {
33
+ if (!zoneId)
34
+ return null;
35
+ const z = s.nodes[zoneId];
36
+ return z?.polygon ?? null;
37
+ });
38
+ if (!(itemObj && controlValues && node?.asset.interactive))
39
+ return null;
40
+ const { controls } = node.asset.interactive;
41
+ const [, height] = node.asset.dimensions;
42
+ let opacity = 0;
43
+ let pointerEvents = 'none';
44
+ if (zoneId && zonePolygon?.length) {
45
+ itemObj.getWorldPosition(_tempVec);
46
+ const inside = pointInPolygon(_tempVec.x, _tempVec.z, zonePolygon);
47
+ opacity = inside ? 1 : 0.1;
48
+ pointerEvents = inside ? 'auto' : 'none';
49
+ }
50
+ return createPortal(_jsx(Html, { center: true, distanceFactor: 8, occlude: true, position: [0, height + 0.3, 0], zIndexRange: [20, 0], children: _jsx("div", { style: {
51
+ display: 'flex',
52
+ flexDirection: 'column',
53
+ gap: 6,
54
+ background: 'rgba(0,0,0,0.75)',
55
+ backdropFilter: 'blur(8px)',
56
+ borderRadius: 8,
57
+ padding: '8px 12px',
58
+ minWidth: 120,
59
+ pointerEvents,
60
+ userSelect: 'none',
61
+ opacity,
62
+ transition: 'opacity 0.3s ease',
63
+ }, children: controls.map((control, i) => (_jsx(ControlWidget, { control: control, onChange: (v) => setControlValue(nodeId, i, v), value: controlValues[i] ?? false }, i))) }) }), itemObj);
64
+ };
65
+ // ---- Control widgets ----
66
+ const ControlWidget = ({ control, value, onChange, }) => {
67
+ const labelStyle = {
68
+ color: 'white',
69
+ fontSize: 11,
70
+ fontFamily: 'monospace',
71
+ display: 'flex',
72
+ flexDirection: 'column',
73
+ gap: 2,
74
+ };
75
+ if (control.kind === 'toggle') {
76
+ return (_jsx("button", { onClick: () => onChange(!value), style: {
77
+ background: value ? '#4ade80' : '#374151',
78
+ color: 'white',
79
+ border: 'none',
80
+ borderRadius: 4,
81
+ padding: '4px 8px',
82
+ cursor: 'pointer',
83
+ fontSize: 12,
84
+ fontFamily: 'monospace',
85
+ transition: 'background 0.2s',
86
+ }, children: control.label ?? (value ? 'On' : 'Off') }));
87
+ }
88
+ if (control.kind === 'slider') {
89
+ return (_jsxs("label", { style: labelStyle, children: [_jsxs("span", { children: [control.label, ": ", value, control.unit ? ` ${control.unit}` : ''] }), _jsx("input", { max: control.max, min: control.min, onChange: (e) => onChange(Number(e.target.value)), onPointerDown: (e) => e.stopPropagation(), step: control.step, type: "range", value: value })] }));
90
+ }
91
+ if (control.kind === 'temperature') {
92
+ return (_jsxs("label", { style: labelStyle, children: [_jsxs("span", { children: [control.label, ": ", value, "\u00B0", control.unit] }), _jsx("input", { max: control.max, min: control.min, onChange: (e) => onChange(Number(e.target.value)), onPointerDown: (e) => e.stopPropagation(), step: 1, type: "range", value: value })] }));
93
+ }
94
+ return null;
95
+ };
@@ -0,0 +1,2 @@
1
+ export declare function ItemLightSystem(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=item-light-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"AAwFA,wBAAgB,eAAe,4CA+M9B"}