@pascal-app/viewer 0.5.1 → 0.7.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 (121) hide show
  1. package/dist/components/error-boundary.d.ts +7 -3
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +3 -1
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +21 -4
  6. package/dist/components/renderers/column/column-renderer.d.ts +5 -0
  7. package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/column/column-renderer.js +594 -0
  9. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/door/door-renderer.js +8 -10
  11. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/fence/fence-renderer.js +16 -2
  13. package/dist/components/renderers/guide/guide-renderer.js +1 -1
  14. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  15. package/dist/components/renderers/item/item-renderer.js +2 -1
  16. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  17. package/dist/components/renderers/node-renderer.js +3 -1
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +19 -9
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +27 -8
  22. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  23. package/dist/components/renderers/site/site-renderer.js +7 -1
  24. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  25. package/dist/components/renderers/slab/slab-renderer.js +48 -7
  26. package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
  27. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
  28. package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
  29. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  30. package/dist/components/renderers/stair/stair-renderer.js +154 -39
  31. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  32. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +22 -10
  33. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  34. package/dist/components/renderers/wall/wall-renderer.js +4 -9
  35. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  36. package/dist/components/renderers/window/window-renderer.js +5 -2
  37. package/dist/components/viewer/frame-limiter.d.ts +6 -0
  38. package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
  39. package/dist/components/viewer/frame-limiter.js +31 -0
  40. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  41. package/dist/components/viewer/ground-occluder.js +5 -1
  42. package/dist/components/viewer/index.d.ts +2 -0
  43. package/dist/components/viewer/index.d.ts.map +1 -1
  44. package/dist/components/viewer/index.js +90 -20
  45. package/dist/components/viewer/post-processing.d.ts +13 -1
  46. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  47. package/dist/components/viewer/post-processing.js +98 -44
  48. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  49. package/dist/components/viewer/selection-manager.js +31 -3
  50. package/dist/hooks/use-node-events.d.ts +9 -1
  51. package/dist/hooks/use-node-events.d.ts.map +1 -1
  52. package/dist/hooks/use-node-events.js +2 -0
  53. package/dist/index.d.ts +6 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +5 -2
  56. package/dist/lib/materials.d.ts +7 -1
  57. package/dist/lib/materials.d.ts.map +1 -1
  58. package/dist/lib/materials.js +192 -2
  59. package/dist/lib/merged-outline-node.d.ts +2 -0
  60. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  61. package/dist/lib/merged-outline-node.js +36 -9
  62. package/dist/store/use-viewer.d.ts +6 -6
  63. package/dist/store/use-viewer.d.ts.map +1 -1
  64. package/dist/store/use-viewer.js +2 -2
  65. package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
  66. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
  67. package/dist/systems/ceiling/ceiling-system.js +91 -0
  68. package/dist/systems/column/column-geometry.d.ts +22 -0
  69. package/dist/systems/column/column-geometry.d.ts.map +1 -0
  70. package/dist/systems/column/column-geometry.js +108 -0
  71. package/dist/systems/door/door-animation-system.d.ts +2 -0
  72. package/dist/systems/door/door-animation-system.d.ts.map +1 -0
  73. package/dist/systems/door/door-animation-system.js +53 -0
  74. package/dist/systems/door/door-system.d.ts +2 -0
  75. package/dist/systems/door/door-system.d.ts.map +1 -0
  76. package/dist/systems/door/door-system.js +867 -0
  77. package/dist/systems/fence/fence-system.d.ts +2 -0
  78. package/dist/systems/fence/fence-system.d.ts.map +1 -0
  79. package/dist/systems/fence/fence-system.js +185 -0
  80. package/dist/systems/item/item-system.d.ts +2 -0
  81. package/dist/systems/item/item-system.d.ts.map +1 -0
  82. package/dist/systems/item/item-system.js +44 -0
  83. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +14 -0
  84. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +1 -0
  85. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +155 -0
  86. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +6 -0
  87. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +1 -0
  88. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +75 -0
  89. package/dist/systems/item-mesh-metadata/sync-request.d.ts +7 -0
  90. package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +1 -0
  91. package/dist/systems/item-mesh-metadata/sync-request.js +25 -0
  92. package/dist/systems/roof/roof-materials.d.ts +5 -0
  93. package/dist/systems/roof/roof-materials.d.ts.map +1 -0
  94. package/dist/systems/roof/roof-materials.js +46 -0
  95. package/dist/systems/roof/roof-system.d.ts +16 -0
  96. package/dist/systems/roof/roof-system.d.ts.map +1 -0
  97. package/dist/systems/roof/roof-system.js +797 -0
  98. package/dist/systems/slab/slab-system.d.ts +8 -0
  99. package/dist/systems/slab/slab-system.d.ts.map +1 -0
  100. package/dist/systems/slab/slab-system.js +155 -0
  101. package/dist/systems/stair/stair-materials.d.ts +7 -0
  102. package/dist/systems/stair/stair-materials.d.ts.map +1 -0
  103. package/dist/systems/stair/stair-materials.js +56 -0
  104. package/dist/systems/stair/stair-system.d.ts +2 -0
  105. package/dist/systems/stair/stair-system.d.ts.map +1 -0
  106. package/dist/systems/stair/stair-system.js +772 -0
  107. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  108. package/dist/systems/wall/wall-cutout.js +4 -123
  109. package/dist/systems/wall/wall-materials.d.ts +16 -0
  110. package/dist/systems/wall/wall-materials.d.ts.map +1 -0
  111. package/dist/systems/wall/wall-materials.js +148 -0
  112. package/dist/systems/wall/wall-system.d.ts +11 -0
  113. package/dist/systems/wall/wall-system.d.ts.map +1 -0
  114. package/dist/systems/wall/wall-system.js +592 -0
  115. package/dist/systems/window/window-animation-system.d.ts +2 -0
  116. package/dist/systems/window/window-animation-system.d.ts.map +1 -0
  117. package/dist/systems/window/window-animation-system.js +135 -0
  118. package/dist/systems/window/window-system.d.ts +12 -0
  119. package/dist/systems/window/window-system.d.ts.map +1 -0
  120. package/dist/systems/window/window-system.js +1430 -0
  121. package/package.json +6 -4
@@ -1,21 +1,33 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { CeilingSystem, DoorSystem, FenceSystem, ItemSystem, RoofSystem, SlabSystem, StairSystem, WallSystem, WindowSystem, } from '@pascal-app/core';
4
3
  import { Bvh } from '@react-three/drei';
5
4
  import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
6
5
  import { useEffect, useMemo, useRef } from 'react';
7
6
  import * as THREE from 'three/webgpu';
8
7
  import useViewer from '../../store/use-viewer';
8
+ import { CeilingSystem } from '../../systems/ceiling/ceiling-system';
9
+ import { DoorAnimationSystem } from '../../systems/door/door-animation-system';
10
+ import { DoorSystem } from '../../systems/door/door-system';
11
+ import { FenceSystem } from '../../systems/fence/fence-system';
9
12
  import { GuideSystem } from '../../systems/guide/guide-system';
13
+ import { ItemSystem } from '../../systems/item/item-system';
10
14
  import { ItemLightSystem } from '../../systems/item-light/item-light-system';
11
15
  import { LevelSystem } from '../../systems/level/level-system';
16
+ import { RoofSystem } from '../../systems/roof/roof-system';
12
17
  import { ScanSystem } from '../../systems/scan/scan-system';
18
+ import { SlabSystem } from '../../systems/slab/slab-system';
19
+ import { StairSystem } from '../../systems/stair/stair-system';
13
20
  import { WallCutout } from '../../systems/wall/wall-cutout';
21
+ import { WallSystem } from '../../systems/wall/wall-system';
22
+ import { WindowAnimationSystem } from '../../systems/window/window-animation-system';
23
+ import { WindowSystem } from '../../systems/window/window-system';
14
24
  import { ZoneSystem } from '../../systems/zone/zone-system';
25
+ import { ErrorBoundary } from '../error-boundary';
15
26
  import { SceneRenderer } from '../renderers/scene-renderer';
27
+ import FrameLimiter from './frame-limiter';
16
28
  import { Lights } from './lights';
17
29
  import { PerfMonitor } from './perf-monitor';
18
- import PostProcessing from './post-processing';
30
+ import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
19
31
  import { SelectionManager } from './selection-manager';
20
32
  import { ViewerCamera } from './viewer-camera';
21
33
  function AnimatedBackground({ isDark }) {
@@ -40,41 +52,99 @@ function AnimatedBackground({ isDark }) {
40
52
  return null;
41
53
  }
42
54
  extend(THREE);
43
- /**
44
- * Monitors the WebGPU device for loss events and logs them.
45
- * WebGPU device loss can happen when:
46
- * - Tab is backgrounded and OS reclaims GPU
47
- * - Driver crash or GPU reset
48
- * - Browser security policy kills the context
49
- */
55
+ // R3F's <Canvas> useLayoutEffect has no deps, so any re-render (theme switch,
56
+ // parent re-render, StrictMode double-mount) re-invokes `configure()`. With a
57
+ // sync `gl` factory that's harmless — the renderer is created once and reused.
58
+ // With an async factory (WebGPURenderer needs `await init()`), two configure
59
+ // calls can race: both see `state.gl == null` and both create a renderer. The
60
+ // first to resolve gets `setSize`/`setDpr` called on it; the second overwrites
61
+ // `state.gl` but R3F's store already holds the new size/dpr, so the new
62
+ // renderer is never resized and stays at the canvas's 300×150 default.
63
+ //
64
+ // Caching by canvas guarantees both branches return the same instance, so
65
+ // "duplicate" configure calls become no-ops on an already-sized renderer.
66
+ // We cache the in-flight Promise (not just the resolved renderer) so two
67
+ // concurrent configure() calls await the same init instead of creating two
68
+ // renderers in parallel and only caching the second.
69
+ const WEBGPU_RENDERER_CACHE = new WeakMap();
50
70
  function GPUDeviceWatcher() {
51
71
  const gl = useThree((s) => s.gl);
52
72
  useEffect(() => {
53
73
  const backend = gl.backend;
54
74
  const device = backend?.device;
55
- if (!device)
75
+ if (!device) {
76
+ console.warn('[viewer] No WebGPU device on backend — running on a fallback renderer.', {
77
+ backend: backend?.constructor?.name ?? 'unknown',
78
+ rendererType: gl.constructor?.name ?? 'unknown',
79
+ });
56
80
  return;
81
+ }
82
+ console.log('[viewer] WebGPU device ready', {
83
+ label: device.label,
84
+ features: device.features ? Array.from(device.features) : [],
85
+ });
57
86
  device.lost.then((info) => {
58
- console.error(`[viewer] WebGPU device lost: reason="${info.reason}", message="${info.message}". ` +
87
+ console.error(`[viewer] WebGPU device lost: reason="${info.reason ?? 'unknown'}", message="${info.message ?? ''}". ` +
59
88
  'The page must be reloaded to recover the GPU context.');
60
89
  });
90
+ // Uncaptured errors are normally silent (only console-warned by Chrome at
91
+ // best). Pipe them to console.error so silent mobile crashes show up.
92
+ const onUncapturedError = (event) => {
93
+ console.error('[viewer] WebGPU uncaptured error:', event?.error?.message, event?.error);
94
+ };
95
+ device.addEventListener?.('uncapturederror', onUncapturedError);
96
+ return () => {
97
+ device.removeEventListener?.('uncapturederror', onUncapturedError);
98
+ };
61
99
  }, [gl]);
62
100
  return null;
63
101
  }
64
- const Viewer = ({ children, selectionManager = 'default', perf = false, }) => {
102
+ const Viewer = ({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, }) => {
65
103
  const theme = useViewer((state) => state.theme);
66
- return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, 1.5], gl: (props) => {
67
- const renderer = new THREE.WebGPURenderer(props);
68
- renderer.toneMapping = THREE.ACESFilmicToneMapping;
69
- renderer.toneMappingExposure = 0.9;
70
- // renderer.init() // Only use when using <DebugRenderer />
71
- return renderer;
72
- }, resize: {
104
+ return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, 1.5], frameloop: "never", gl: ((props) => {
105
+ const canvas = props.canvas;
106
+ const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
107
+ if (cached)
108
+ return cached;
109
+ // Surface the env we're about to ask WebGPU for — catches "no
110
+ // navigator.gpu" / "adapter request failed" silently failing in
111
+ // mobile WebViews where WebGPU is gated behind flags.
112
+ const hasGpu = typeof navigator !== 'undefined' && 'gpu' in navigator;
113
+ console.log('[viewer] Creating WebGPURenderer', {
114
+ hasNavigatorGPU: hasGpu,
115
+ ua: typeof navigator !== 'undefined' ? navigator.userAgent : 'n/a',
116
+ });
117
+ const promise = (async () => {
118
+ try {
119
+ const renderer = new THREE.WebGPURenderer(props);
120
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
121
+ renderer.toneMappingExposure = 0.9;
122
+ await renderer.init();
123
+ console.log('[viewer] WebGPURenderer ready', {
124
+ backend: renderer.backend?.constructor?.name,
125
+ isWebGPU: renderer.isWebGPURenderer === true,
126
+ });
127
+ return renderer;
128
+ }
129
+ catch (err) {
130
+ // Drop the failed promise from the cache so a future Canvas
131
+ // mount on the same DOM can retry instead of inheriting the
132
+ // rejection forever.
133
+ if (canvas)
134
+ WEBGPU_RENDERER_CACHE.delete(canvas);
135
+ console.error('[viewer] WebGPURenderer init failed', err);
136
+ throw err;
137
+ }
138
+ })();
139
+ if (canvas)
140
+ WEBGPU_RENDERER_CACHE.set(canvas, promise);
141
+ return promise;
142
+ }), resize: {
73
143
  debounce: 100,
74
144
  }, shadows: {
75
145
  type: THREE.PCFShadowMap,
76
146
  enabled: true,
77
- }, children: [_jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorSystem, {}), _jsx(FenceSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(StairSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] }));
147
+ }, children: [_jsx(FrameLimiter, { fps: 50 }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorAnimationSystem, {}), _jsx(WindowAnimationSystem, {}), _jsx(DoorSystem, {}), _jsx(FenceSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(StairSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, { hoverStyles: hoverStyles }), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] })] }));
78
148
  };
79
149
  const DebugRenderer = () => {
80
150
  useFrame(({ gl, scene, camera }) => {
@@ -12,6 +12,18 @@ export declare const SSGI_PARAMS: {
12
12
  useScreenSpaceSampling: boolean;
13
13
  useTemporalFiltering: boolean;
14
14
  };
15
- declare const PostProcessingPasses: () => null;
15
+ export type HoverStyle = {
16
+ visibleColor: number;
17
+ hiddenColor: number;
18
+ strength: number;
19
+ pulse: boolean;
20
+ };
21
+ export type HoverStyles = {
22
+ default: HoverStyle;
23
+ } & Record<string, HoverStyle>;
24
+ export declare const DEFAULT_HOVER_STYLES: HoverStyles;
25
+ declare const PostProcessingPasses: ({ hoverStyles, }: {
26
+ hoverStyles?: HoverStyles;
27
+ }) => null;
16
28
  export default PostProcessingPasses;
17
29
  //# sourceMappingURL=post-processing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAQD,QAAA,MAAM,oBAAoB,YAmTzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
1
+ {"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAQD,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,UAAU,CAAA;CACpB,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAS9B,eAAO,MAAM,oBAAoB,EAAE,WAElC,CAAA;AAiBD,QAAA,MAAM,oBAAoB,GAAI,kBAE3B;IACD,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,SAkVA,CAAA;AAED,eAAe,oBAAoB,CAAA"}
@@ -27,13 +27,32 @@ const MAX_PIPELINE_RETRIES = 3;
27
27
  const RETRY_DELAY_MS = 500;
28
28
  const DARK_BG = '#1f2433';
29
29
  const LIGHT_BG = '#ffffff';
30
- const PostProcessingPasses = () => {
31
- const { gl: renderer, scene, camera } = useThree();
30
+ const DEFAULT_HOVER_STYLE = {
31
+ visibleColor: 0x00_aa_ff,
32
+ hiddenColor: 0xf3_ff_47,
33
+ strength: 5,
34
+ pulse: true,
35
+ };
36
+ export const DEFAULT_HOVER_STYLES = {
37
+ default: DEFAULT_HOVER_STYLE,
38
+ };
39
+ function sanitizeOutlineObjects(objects) {
40
+ let nextIndex = 0;
41
+ for (const object of objects) {
42
+ if (!(object && typeof object.id === 'number' && object.parent)) {
43
+ continue;
44
+ }
45
+ objects[nextIndex] = object;
46
+ nextIndex++;
47
+ }
48
+ objects.length = nextIndex;
49
+ }
50
+ const PostProcessingPasses = ({ hoverStyles = DEFAULT_HOVER_STYLES, }) => {
51
+ const { gl: renderer, invalidate, scene, camera } = useThree();
32
52
  const renderPipelineRef = useRef(null);
33
53
  const hasPipelineErrorRef = useRef(false);
34
54
  const retryCountRef = useRef(0);
35
55
  const rebuildTimeoutRef = useRef(null);
36
- const [isInitialized, setIsInitialized] = useState(false);
37
56
  // Background color uniform — updated every frame via lerp, read by the TSL pipeline.
38
57
  // Initialised from the current theme so there's no flash on first render.
39
58
  const initBg = useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG;
@@ -47,6 +66,10 @@ const PostProcessingPasses = () => {
47
66
  return l;
48
67
  }, []);
49
68
  const hoverHighlightMode = useViewer((s) => s.hoverHighlightMode);
69
+ const hoverVisibleColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.visibleColor)), []);
70
+ const hoverHiddenColor = useMemo(() => uniform(new Color(DEFAULT_HOVER_STYLE.hiddenColor)), []);
71
+ const hoverStrength = useMemo(() => uniform(DEFAULT_HOVER_STYLE.strength), []);
72
+ const hoverPulseMix = useMemo(() => uniform(DEFAULT_HOVER_STYLE.pulse ? 0 : 1), []);
50
73
  // Subscribe to projectId so the pipeline rebuilds on project switch
51
74
  const projectId = useViewer((s) => s.projectId);
52
75
  // Bump this to force a pipeline rebuild (used by retry logic)
@@ -58,30 +81,6 @@ const PostProcessingPasses = () => {
58
81
  }
59
82
  setPipelineVersion((v) => v + 1);
60
83
  }, []);
61
- // Renderer initialization
62
- useEffect(() => {
63
- let mounted = true;
64
- const initRenderer = async () => {
65
- try {
66
- if (renderer && renderer.init) {
67
- await renderer.init();
68
- }
69
- if (mounted) {
70
- setIsInitialized(true);
71
- }
72
- }
73
- catch (error) {
74
- console.error('[viewer] Failed to initialize renderer for post-processing.', error);
75
- if (mounted) {
76
- setIsInitialized(false);
77
- }
78
- }
79
- };
80
- initRenderer();
81
- return () => {
82
- mounted = false;
83
- };
84
- }, [renderer]);
85
84
  // Reset retry state when project changes
86
85
  useEffect(() => {
87
86
  // Intentionally touch projectId so the effect reruns on project switches.
@@ -100,19 +99,64 @@ const PostProcessingPasses = () => {
100
99
  }
101
100
  };
102
101
  }, []);
102
+ useEffect(() => {
103
+ const style = hoverStyles[hoverHighlightMode] ?? hoverStyles.default;
104
+ hoverVisibleColor.value.setHex(style.visibleColor);
105
+ hoverHiddenColor.value.setHex(style.hiddenColor);
106
+ hoverStrength.value = style.strength;
107
+ hoverPulseMix.value = style.pulse ? 0 : 1;
108
+ invalidate();
109
+ }, [
110
+ hoverHiddenColor,
111
+ hoverHighlightMode,
112
+ hoverPulseMix,
113
+ hoverStrength,
114
+ hoverStyles,
115
+ hoverVisibleColor,
116
+ invalidate,
117
+ ]);
103
118
  // Build / rebuild the post-processing pipeline
104
119
  useEffect(() => {
105
120
  // Intentionally touch these so React/biome treat project switches and retry bumps
106
121
  // as explicit rebuild triggers instead of accidental extra dependencies.
107
122
  void projectId;
108
123
  void pipelineVersion;
109
- if (!(renderer && scene && camera && isInitialized)) {
124
+ if (!(renderer && scene && camera)) {
125
+ console.warn('[viewer/post-processing] Skipping pipeline build — missing dependency.', {
126
+ hasRenderer: !!renderer,
127
+ hasScene: !!scene,
128
+ hasCamera: !!camera,
129
+ });
110
130
  return;
111
131
  }
132
+ console.log('[viewer/post-processing] Building pipeline', {
133
+ version: pipelineVersion,
134
+ ssgi: SSGI_PARAMS.enabled,
135
+ hoverHighlightMode,
136
+ projectId,
137
+ rendererCtor: renderer.constructor?.name,
138
+ });
112
139
  hasPipelineErrorRef.current = false;
140
+ // WebGPU availability check: SSGI, denoise, and RenderPipeline are all
141
+ // WebGPU-only APIs. When the browser falls back to WebGL2 (no
142
+ // `navigator.gpu`, or the device couldn't be created), building the
143
+ // pipeline either throws silently or produces a broken output where
144
+ // the scene renders for a few frames and then goes black as the retry
145
+ // loop fights the direct-render fallback path. Short-circuit here so
146
+ // `useFrame` uses the direct `renderer.render(scene, camera)` path
147
+ // exclusively and never attempts the TSL pipeline.
148
+ const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator;
149
+ if (!hasWebGPU) {
150
+ console.warn('[viewer] WebGPU unavailable — rendering without post-processing (SSGI, outlines, denoise).');
151
+ hasPipelineErrorRef.current = true;
152
+ renderPipelineRef.current = null;
153
+ return;
154
+ }
113
155
  // Clear outliner arrays synchronously to prevent stale Object3D refs
114
156
  // from the previous project leaking into the new pipeline's outline passes.
115
157
  const outliner = useViewer.getState().outliner;
158
+ sanitizeOutlineObjects(outliner.selectedObjects);
159
+ sanitizeOutlineObjects(outliner.hoveredObjects);
116
160
  outliner.selectedObjects.length = 0;
117
161
  outliner.hoveredObjects.length = 0;
118
162
  try {
@@ -185,13 +229,9 @@ const PostProcessingPasses = () => {
185
229
  .add(outlineNode.primaryHiddenEdge.mul(selectedHiddenColor))
186
230
  .mul(selectedStrength);
187
231
  // Hovered: blue visible, yellow hidden, pulsing
188
- const hoverVisibleColor = uniform(new Color(hoverHighlightMode === 'delete' ? 0xef_44_44 : 0x00_aa_ff));
189
- const hoverHiddenColor = uniform(new Color(hoverHighlightMode === 'delete' ? 0x99_1b_1b : 0xf3_ff_47));
190
- const hoverStrength = uniform(hoverHighlightMode === 'delete' ? 6 : 5);
191
232
  const pulsePeriod = uniform(3);
192
- const osc = hoverHighlightMode === 'delete'
193
- ? float(1)
194
- : oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5); // [ 0.5, 1.0 ]
233
+ const oscillating = oscSine(time.div(pulsePeriod).mul(2)).mul(0.5).add(0.5);
234
+ const osc = mix(oscillating, float(1), hoverPulseMix);
195
235
  const hoverOutline = outlineNode.secondaryVisibleEdge
196
236
  .mul(hoverVisibleColor)
197
237
  .add(outlineNode.secondaryHiddenEdge.mul(hoverHiddenColor))
@@ -203,10 +243,15 @@ const PostProcessingPasses = () => {
203
243
  renderPipeline.outputNode = finalOutput;
204
244
  renderPipelineRef.current = renderPipeline;
205
245
  retryCountRef.current = 0;
246
+ console.log('[viewer/post-processing] Pipeline built OK', { version: pipelineVersion });
206
247
  }
207
248
  catch (error) {
208
249
  hasPipelineErrorRef.current = true;
209
- console.error('[viewer] Failed to set up post-processing pipeline. Rendering without post FX.', error);
250
+ console.error('[viewer/post-processing] Failed to set up post-processing pipeline. Rendering without post FX.', {
251
+ version: pipelineVersion,
252
+ ssgi: SSGI_PARAMS.enabled,
253
+ rendererCtor: renderer.constructor?.name,
254
+ }, error);
210
255
  if (renderPipelineRef.current) {
211
256
  renderPipelineRef.current.dispose();
212
257
  }
@@ -219,20 +264,25 @@ const PostProcessingPasses = () => {
219
264
  renderPipelineRef.current = null;
220
265
  };
221
266
  }, [
267
+ camera,
268
+ hoverHiddenColor,
269
+ hoverPulseMix,
270
+ hoverStrength,
271
+ hoverVisibleColor,
272
+ pipelineVersion,
273
+ projectId,
222
274
  renderer,
223
275
  scene,
224
- camera,
225
- hoverHighlightMode,
226
- isInitialized,
227
276
  zoneLayers,
228
- projectId,
229
- pipelineVersion,
230
277
  ]);
231
278
  useFrame((_, delta) => {
232
279
  // Animate background colour toward the current theme target (same lerp as AnimatedBackground)
233
280
  bgTarget.current.set(useViewer.getState().theme === 'dark' ? DARK_BG : LIGHT_BG);
234
281
  bgCurrent.current.lerp(bgTarget.current, Math.min(delta, 0.1) * 4);
235
282
  bgUniform.current.value.copy(bgCurrent.current);
283
+ const outliner = useViewer.getState().outliner;
284
+ sanitizeOutlineObjects(outliner.selectedObjects);
285
+ sanitizeOutlineObjects(outliner.hoveredObjects);
236
286
  if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
237
287
  try {
238
288
  if (renderer.setClearAlpha) {
@@ -243,7 +293,7 @@ const PostProcessingPasses = () => {
243
293
  renderer.render(scene, camera);
244
294
  }
245
295
  catch (fallbackError) {
246
- console.error('[viewer] Fallback render failed.', fallbackError);
296
+ console.error('[viewer/post-processing] Fallback render failed.', fallbackError);
247
297
  }
248
298
  return;
249
299
  }
@@ -256,7 +306,11 @@ const PostProcessingPasses = () => {
256
306
  }
257
307
  catch (error) {
258
308
  hasPipelineErrorRef.current = true;
259
- console.error('[viewer] Post-processing render pass failed.', error);
309
+ console.error('[viewer/post-processing] Render pass failed.', {
310
+ retryCount: retryCountRef.current,
311
+ rendererCtor: renderer.constructor?.name,
312
+ error,
313
+ });
260
314
  if (renderPipelineRef.current) {
261
315
  renderPipelineRef.current.dispose();
262
316
  }
@@ -264,14 +318,14 @@ const PostProcessingPasses = () => {
264
318
  if (retryCountRef.current < MAX_PIPELINE_RETRIES) {
265
319
  // Auto-retry: schedule a pipeline rebuild if we haven't exceeded the retry limit
266
320
  retryCountRef.current++;
267
- console.warn(`[viewer] Scheduling post-processing rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
321
+ console.warn(`[viewer/post-processing] Scheduling pipeline rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`);
268
322
  if (rebuildTimeoutRef.current !== null) {
269
323
  clearTimeout(rebuildTimeoutRef.current);
270
324
  }
271
325
  rebuildTimeoutRef.current = setTimeout(requestPipelineRebuild, RETRY_DELAY_MS);
272
326
  }
273
327
  else {
274
- console.error('[viewer] Post-processing retries exhausted. Rendering without post FX for this session.');
328
+ console.error('[viewer/post-processing] Retries exhausted. Rendering without post FX for this session.');
275
329
  }
276
330
  }
277
331
  }, 1);
@@ -1 +1 @@
1
- {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAiRA,eAAO,MAAM,gBAAgB,+CAuE5B,CAAA"}
1
+ {"version":3,"file":"selection-manager.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/selection-manager.tsx"],"names":[],"mappings":"AAoSA,eAAO,MAAM,gBAAgB,+CA4E5B,CAAA"}
@@ -82,6 +82,10 @@ const isNodeInZone = (node, levelId, zoneId) => {
82
82
  const item = node;
83
83
  return pointInPolygonWithTolerance(item.position[0], item.position[2], zone.polygon);
84
84
  }
85
+ if (node.type === 'column') {
86
+ const column = node;
87
+ return pointInPolygonWithTolerance(column.position[0], column.position[2], zone.polygon);
88
+ }
85
89
  if (node.type === 'wall') {
86
90
  const wall = node;
87
91
  const startIn = pointInPolygonWithTolerance(wall.start[0], wall.start[1], zone.polygon);
@@ -168,9 +172,20 @@ const getStrategy = () => {
168
172
  isValid: (node) => node.type === 'zone' && node.parentId === levelId,
169
173
  };
170
174
  }
171
- // Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs, windows, doors)
175
+ // Zone selected -> can select/hover contents (walls, items, columns, slabs, ceilings, roofs, windows, doors)
172
176
  return {
173
- types: ['wall', 'fence', 'item', 'slab', 'ceiling', 'roof', 'roof-segment', 'window', 'door'],
177
+ types: [
178
+ 'wall',
179
+ 'fence',
180
+ 'item',
181
+ 'column',
182
+ 'slab',
183
+ 'ceiling',
184
+ 'roof',
185
+ 'roof-segment',
186
+ 'window',
187
+ 'door',
188
+ ],
174
189
  handleClick: (node, nativeEvent) => {
175
190
  let nodeToSelect = node;
176
191
  if (node.type === 'roof-segment' && node.parentId) {
@@ -199,6 +214,7 @@ const getStrategy = () => {
199
214
  'wall',
200
215
  'fence',
201
216
  'item',
217
+ 'column',
202
218
  'slab',
203
219
  'ceiling',
204
220
  'roof',
@@ -222,6 +238,10 @@ export const SelectionManager = () => {
222
238
  return;
223
239
  if (strategy.isValid(event.node)) {
224
240
  event.stopPropagation();
241
+ if (event.node.type === 'slab') {
242
+ useViewer.setState({ hoveredId: null });
243
+ return;
244
+ }
225
245
  useViewer.setState({ hoveredId: event.node.id });
226
246
  }
227
247
  };
@@ -254,6 +274,7 @@ export const SelectionManager = () => {
254
274
  'wall',
255
275
  'fence',
256
276
  'item',
277
+ 'column',
257
278
  'slab',
258
279
  'ceiling',
259
280
  'roof',
@@ -311,10 +332,14 @@ const OutlinerSync = () => {
311
332
  const selection = useViewer((s) => s.selection);
312
333
  const hoveredId = useViewer((s) => s.hoveredId);
313
334
  const outliner = useViewer((s) => s.outliner);
335
+ const nodes = useScene((s) => s.nodes);
314
336
  useEffect(() => {
315
337
  // Sync selected objects
316
338
  outliner.selectedObjects.length = 0;
317
339
  for (const id of selection.selectedIds) {
340
+ const node = nodes[id];
341
+ if (node?.type === 'slab')
342
+ continue;
318
343
  const obj = sceneRegistry.nodes.get(id);
319
344
  if (obj)
320
345
  outliner.selectedObjects.push(obj);
@@ -322,10 +347,13 @@ const OutlinerSync = () => {
322
347
  // Sync hovered objects
323
348
  outliner.hoveredObjects.length = 0;
324
349
  if (hoveredId) {
350
+ const hoveredNode = nodes[hoveredId];
351
+ if (hoveredNode?.type === 'slab')
352
+ return;
325
353
  const obj = sceneRegistry.nodes.get(hoveredId);
326
354
  if (obj)
327
355
  outliner.hoveredObjects.push(obj);
328
356
  }
329
- }, [selection, hoveredId, outliner]);
357
+ }, [selection, hoveredId, outliner, nodes]);
330
358
  return null;
331
359
  };
@@ -1,4 +1,4 @@
1
- import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type DoorEvent, type DoorNode, type FenceEvent, type FenceNode, 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 StairEvent, type StairNode, type StairSegmentEvent, type StairSegmentNode, 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 ColumnEvent, type ColumnNode, type DoorEvent, type DoorNode, type FenceEvent, type FenceNode, 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 SpawnEvent, type SpawnNode, type StairEvent, type StairNode, type StairSegmentEvent, type StairSegmentNode, 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: {
@@ -33,10 +33,18 @@ type NodeConfig = {
33
33
  node: SlabNode;
34
34
  event: SlabEvent;
35
35
  };
36
+ spawn: {
37
+ node: SpawnNode;
38
+ event: SpawnEvent;
39
+ };
36
40
  ceiling: {
37
41
  node: CeilingNode;
38
42
  event: CeilingEvent;
39
43
  };
44
+ column: {
45
+ node: ColumnNode;
46
+ event: ColumnEvent;
47
+ };
40
48
  roof: {
41
49
  node: RoofNode;
42
50
  event: RoofEvent;
@@ -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,EAChB,KAAK,SAAS,EACd,KAAK,QAAQ,EAGb,KAAK,UAAU,EACf,KAAK,SAAS,EACd,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,UAAU,EACf,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,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,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,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,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAA;IAC7C,eAAe,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC;QAAC,KAAK,EAAE,iBAAiB,CAAA;KAAE,CAAA;IACrE,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"}
1
+ {"version":3,"file":"use-node-events.d.ts","sourceRoot":"","sources":["../../src/hooks/use-node-events.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,8 @@ export function useNodeEvents(node, type) {
9
9
  position: [e.point.x, e.point.y, e.point.z],
10
10
  localPosition: [localPoint.x, localPoint.y, localPoint.z],
11
11
  normal: e.face ? [e.face.normal.x, e.face.normal.y, e.face.normal.z] : undefined,
12
+ faceIndex: e.faceIndex ?? undefined,
13
+ object: e.object,
12
14
  stopPropagation: () => e.stopPropagation(),
13
15
  nativeEvent: e,
14
16
  };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { SSGI_PARAMS } from './components/viewer/post-processing';
2
+ export type { HoverStyle, HoverStyles } from './components/viewer/post-processing';
3
+ export { DEFAULT_HOVER_STYLES, SSGI_PARAMS, } from './components/viewer/post-processing';
3
4
  export { WalkthroughControls } from './components/viewer/walkthrough-controls';
4
5
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
5
6
  export { SCENE_LAYER, ZONE_LAYER } from './lib/layers';
6
- export { clearMaterialCache, createDefaultMaterial, createMaterial, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, } from './lib/materials';
7
+ export { applyMaterialPresetToMaterials, clearMaterialCache, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, } from './lib/materials';
7
8
  export { mergedOutline } from './lib/merged-outline-node';
8
9
  export { default as useViewer } from './store/use-viewer';
9
10
  export { InteractiveSystem } from './systems/interactive/interactive-system';
10
11
  export { snapLevelsToTruePositions } from './systems/level/level-utils';
12
+ export { getRoofMaterialArray } from './systems/roof/roof-materials';
13
+ export { getStairBodyMaterials, getStairRailingMaterial } from './systems/stair/stair-materials';
14
+ export { getVisibleWallMaterials } from './systems/wall/wall-materials';
11
15
  //# 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,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,GAChB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,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"}
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,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AAClF,OAAO,EACL,oBAAoB,EACpB,WAAW,GACZ,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EACL,8BAA8B,EAC9B,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,2BAA2B,EAC3B,wBAAwB,EACxB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,GAChB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,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;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AACpE,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAChG,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA"}
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  export { default as Viewer } from './components/viewer';
2
- export { SSGI_PARAMS } from './components/viewer/post-processing';
2
+ export { DEFAULT_HOVER_STYLES, SSGI_PARAMS, } from './components/viewer/post-processing';
3
3
  export { WalkthroughControls } from './components/viewer/walkthrough-controls';
4
4
  export { ASSETS_CDN_URL, resolveAssetUrl, resolveCdnUrl } from './lib/asset-url';
5
5
  export { SCENE_LAYER, ZONE_LAYER } from './lib/layers';
6
- export { clearMaterialCache, createDefaultMaterial, createMaterial, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, } from './lib/materials';
6
+ export { applyMaterialPresetToMaterials, clearMaterialCache, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, DEFAULT_CEILING_MATERIAL, DEFAULT_DOOR_MATERIAL, DEFAULT_ROOF_MATERIAL, DEFAULT_SLAB_MATERIAL, DEFAULT_WALL_MATERIAL, DEFAULT_WINDOW_MATERIAL, disposeMaterial, } from './lib/materials';
7
7
  export { mergedOutline } from './lib/merged-outline-node';
8
8
  export { default as useViewer } from './store/use-viewer';
9
9
  export { InteractiveSystem } from './systems/interactive/interactive-system';
10
10
  export { snapLevelsToTruePositions } from './systems/level/level-utils';
11
+ export { getRoofMaterialArray } from './systems/roof/roof-materials';
12
+ export { getStairBodyMaterials, getStairRailingMaterial } from './systems/stair/stair-materials';
13
+ export { getVisibleWallMaterials } from './systems/wall/wall-materials';
@@ -1,5 +1,11 @@
1
- import { type MaterialSchema } from '@pascal-app/core';
1
+ import { type MaterialPresetPayload, type MaterialSchema } from '@pascal-app/core';
2
2
  import * as THREE from 'three';
3
+ import { MeshStandardNodeMaterial } from 'three/webgpu';
4
+ export declare const baseMaterial: MeshStandardNodeMaterial;
5
+ export declare const glassMaterial: MeshStandardNodeMaterial;
6
+ export declare function applyMaterialPresetToMaterials(materialInput: THREE.Material | THREE.Material[], preset: MaterialPresetPayload | null | undefined): void;
7
+ export declare function createMaterialFromPreset(preset: MaterialPresetPayload): THREE.MeshStandardMaterial;
8
+ export declare function createMaterialFromPresetRef(materialPreset?: string): THREE.MeshStandardMaterial | null;
3
9
  export declare function createMaterial(material?: MaterialSchema): THREE.MeshStandardMaterial;
4
10
  export declare function createDefaultMaterial(color?: string, roughness?: number): THREE.MeshStandardMaterial;
5
11
  export declare const DEFAULT_WALL_MATERIAL: THREE.MeshStandardMaterial;
@@ -1 +1 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,cAAc,EAAmB,MAAM,kBAAkB,CAAA;AAChG,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAc9B,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAmBpF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,SAAY,EACjB,SAAS,SAAM,GACd,KAAK,CAAC,oBAAoB,CAO5B;AAED,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,uBAAuB,4BAOlC,CAAA;AACF,eAAO,MAAM,wBAAwB,4BAAyC,CAAA;AAC9E,eAAO,MAAM,qBAAqB,4BAAyC,CAAA;AAC3E,eAAO,MAAM,sBAAsB,4BAAwC,CAAA;AAE3E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC"}
1
+ {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EAGpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAEvD,eAAO,MAAM,YAAY,0BAIvB,CAAA;AAEF,eAAO,MAAM,aAAa,0BAOxB,CAAA;AA2MF,wBAAgB,8BAA8B,CAC5C,aAAa,EAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,EAChD,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,QAcjD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAAC,oBAAoB,CAWlG;AAED,wBAAgB,2BAA2B,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAItG;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC,oBAAoB,CAsBpF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,SAAY,EACjB,SAAS,SAAM,GACd,KAAK,CAAC,oBAAoB,CAO5B;AAED,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,qBAAqB,4BAAwC,CAAA;AAC1E,eAAO,MAAM,uBAAuB,4BAOlC,CAAA;AACF,eAAO,MAAM,wBAAwB,4BAAyC,CAAA;AAC9E,eAAO,MAAM,qBAAqB,4BAAyC,CAAA;AAC3E,eAAO,MAAM,sBAAsB,4BAAwC,CAAA;AAE3E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAE9D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAWzC"}