@pascal-app/viewer 0.8.0 → 0.9.2

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 (284) hide show
  1. package/dist/components/error-boundary.d.ts +6 -0
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +6 -0
  4. package/dist/components/renderers/node-renderer.d.ts +6 -2
  5. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/node-renderer.js +41 -22
  7. package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
  8. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
  9. package/dist/components/renderers/parametric-node-renderer.js +37 -0
  10. package/dist/components/renderers/scene-renderer.d.ts +1 -1
  11. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  12. package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
  13. package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
  14. package/dist/components/viewer/bvh-ecctrl.js +552 -0
  15. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  16. package/dist/components/viewer/frame-limiter.js +39 -4
  17. package/dist/components/viewer/glb-interactive.d.ts +41 -0
  18. package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
  19. package/dist/components/viewer/glb-interactive.js +394 -0
  20. package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
  21. package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
  22. package/dist/components/viewer/glb-reference-nodes.js +80 -0
  23. package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
  24. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
  25. package/dist/components/viewer/glb-replace-instances.js +61 -0
  26. package/dist/components/viewer/glb-scene.d.ts +54 -0
  27. package/dist/components/viewer/glb-scene.d.ts.map +1 -0
  28. package/dist/components/viewer/glb-scene.js +920 -0
  29. package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
  30. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
  31. package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
  32. package/dist/components/viewer/index.d.ts +56 -3
  33. package/dist/components/viewer/index.d.ts.map +1 -1
  34. package/dist/components/viewer/index.js +280 -65
  35. package/dist/components/viewer/lights.d.ts +1 -1
  36. package/dist/components/viewer/lights.d.ts.map +1 -1
  37. package/dist/components/viewer/lights.js +201 -43
  38. package/dist/components/viewer/perf-monitor.d.ts +1 -1
  39. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  40. package/dist/components/viewer/perf-monitor.js +115 -12
  41. package/dist/components/viewer/post-processing.d.ts +7 -1
  42. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  43. package/dist/components/viewer/post-processing.js +395 -63
  44. package/dist/components/viewer/registered-systems.d.ts +12 -0
  45. package/dist/components/viewer/registered-systems.d.ts.map +1 -0
  46. package/dist/components/viewer/registered-systems.js +49 -0
  47. package/dist/components/viewer/scene-bvh.d.ts +18 -0
  48. package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
  49. package/dist/components/viewer/scene-bvh.js +97 -0
  50. package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
  51. package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
  52. package/dist/components/viewer/scene-bvh.test.js +13 -0
  53. package/dist/components/viewer/scene-environment.d.ts +2 -0
  54. package/dist/components/viewer/scene-environment.d.ts.map +1 -0
  55. package/dist/components/viewer/scene-environment.js +79 -0
  56. package/dist/components/viewer/selection-manager.d.ts +1 -1
  57. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  58. package/dist/components/viewer/selection-manager.js +37 -14
  59. package/dist/components/viewer/viewer-camera.d.ts +1 -1
  60. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  61. package/dist/components/viewer/walkthrough-controls.d.ts +2 -1
  62. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
  63. package/dist/components/viewer/walkthrough-controls.js +20 -0
  64. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  65. package/dist/hooks/use-gltf-ktx2.js +3 -22
  66. package/dist/hooks/use-node-events.d.ts +6 -74
  67. package/dist/hooks/use-node-events.d.ts.map +1 -1
  68. package/dist/hooks/use-node-events.js +28 -8
  69. package/dist/index.d.ts +48 -5
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +82 -4
  72. package/dist/lib/backdrop.d.ts +33 -0
  73. package/dist/lib/backdrop.d.ts.map +1 -0
  74. package/dist/lib/backdrop.js +92 -0
  75. package/dist/lib/box-uv.d.ts +13 -0
  76. package/dist/lib/box-uv.d.ts.map +1 -0
  77. package/dist/lib/box-uv.js +32 -0
  78. package/dist/lib/csg-utils.d.ts +10 -0
  79. package/dist/lib/csg-utils.d.ts.map +1 -0
  80. package/dist/lib/csg-utils.js +93 -0
  81. package/dist/lib/csg-utils.test.d.ts +2 -0
  82. package/dist/lib/csg-utils.test.d.ts.map +1 -0
  83. package/dist/lib/csg-utils.test.js +45 -0
  84. package/dist/lib/drawable-geometry.d.ts +21 -0
  85. package/dist/lib/drawable-geometry.d.ts.map +1 -0
  86. package/dist/lib/drawable-geometry.js +22 -0
  87. package/dist/lib/edge-style.d.ts +4 -0
  88. package/dist/lib/edge-style.d.ts.map +1 -0
  89. package/dist/lib/edge-style.js +31 -0
  90. package/dist/lib/gpu-perf.d.ts +4 -0
  91. package/dist/lib/gpu-perf.d.ts.map +1 -0
  92. package/dist/lib/gpu-perf.js +23 -0
  93. package/dist/lib/ink-edges.d.ts +9 -0
  94. package/dist/lib/ink-edges.d.ts.map +1 -0
  95. package/dist/lib/ink-edges.js +49 -0
  96. package/dist/lib/isolation.d.ts +39 -0
  97. package/dist/lib/isolation.d.ts.map +1 -0
  98. package/dist/lib/isolation.js +103 -0
  99. package/dist/lib/ktx2-loader.d.ts +43 -0
  100. package/dist/lib/ktx2-loader.d.ts.map +1 -0
  101. package/dist/lib/ktx2-loader.js +136 -0
  102. package/dist/lib/layers.d.ts +28 -0
  103. package/dist/lib/layers.d.ts.map +1 -1
  104. package/dist/lib/layers.js +28 -0
  105. package/dist/lib/materials.d.ts +38 -15
  106. package/dist/lib/materials.d.ts.map +1 -1
  107. package/dist/lib/materials.js +364 -72
  108. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  109. package/dist/lib/merged-outline-node.js +7 -0
  110. package/dist/lib/polygon-union.d.ts +4 -0
  111. package/dist/lib/polygon-union.d.ts.map +1 -0
  112. package/dist/lib/polygon-union.js +289 -0
  113. package/dist/lib/polygon-union.test.d.ts +2 -0
  114. package/dist/lib/polygon-union.test.d.ts.map +1 -0
  115. package/dist/lib/polygon-union.test.js +105 -0
  116. package/dist/lib/scene-themes.d.ts +30 -0
  117. package/dist/lib/scene-themes.d.ts.map +1 -0
  118. package/dist/lib/scene-themes.js +202 -0
  119. package/dist/lib/shadow-only.d.ts +4 -0
  120. package/dist/lib/shadow-only.d.ts.map +1 -0
  121. package/dist/lib/shadow-only.js +36 -0
  122. package/dist/lib/tsl-compat.d.ts +12 -0
  123. package/dist/lib/tsl-compat.d.ts.map +1 -0
  124. package/dist/lib/tsl-compat.js +13 -0
  125. package/dist/store/use-viewer.d.ts +92 -12
  126. package/dist/store/use-viewer.d.ts.map +1 -1
  127. package/dist/store/use-viewer.js +201 -6
  128. package/dist/store/use-viewer.test.d.ts +2 -0
  129. package/dist/store/use-viewer.test.d.ts.map +1 -0
  130. package/dist/store/use-viewer.test.js +42 -0
  131. package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
  132. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  133. package/dist/systems/ceiling/ceiling-system.js +66 -13
  134. package/dist/systems/door/door-animation.test.d.ts +2 -0
  135. package/dist/systems/door/door-animation.test.d.ts.map +1 -0
  136. package/dist/systems/door/door-animation.test.js +51 -0
  137. package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
  138. package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
  139. package/dist/systems/door/door-floor-alignment.test.js +50 -0
  140. package/dist/systems/door/door-system.d.ts +24 -0
  141. package/dist/systems/door/door-system.d.ts.map +1 -1
  142. package/dist/systems/door/door-system.js +864 -180
  143. package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
  144. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
  145. package/dist/systems/elevator/elevator-interaction-system.js +62 -0
  146. package/dist/systems/fence/fence-system.d.ts +20 -0
  147. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  148. package/dist/systems/fence/fence-system.js +229 -57
  149. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
  150. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
  151. package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
  152. package/dist/systems/geometry/geometry-system.d.ts +41 -0
  153. package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
  154. package/dist/systems/geometry/geometry-system.js +348 -0
  155. package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
  156. package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
  157. package/dist/systems/geometry/geometry-system.test.js +30 -0
  158. package/dist/systems/interactive/control-widget.d.ts +10 -0
  159. package/dist/systems/interactive/control-widget.d.ts.map +1 -0
  160. package/dist/systems/interactive/control-widget.js +35 -0
  161. package/dist/systems/interactive/interactive-system.d.ts +1 -1
  162. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  163. package/dist/systems/interactive/interactive-system.js +55 -56
  164. package/dist/systems/item/item-system.d.ts +7 -0
  165. package/dist/systems/item/item-system.d.ts.map +1 -1
  166. package/dist/systems/item/item-system.js +28 -17
  167. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  168. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  169. package/dist/systems/item-light/item-light-system.js +18 -5
  170. package/dist/systems/level/level-system.d.ts.map +1 -1
  171. package/dist/systems/level/level-system.js +28 -4
  172. package/dist/systems/level/level-utils.d.ts +0 -3
  173. package/dist/systems/level/level-utils.d.ts.map +1 -1
  174. package/dist/systems/level/level-utils.js +2 -41
  175. package/dist/systems/roof/roof-materials.d.ts +3 -2
  176. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  177. package/dist/systems/roof/roof-materials.js +59 -14
  178. package/dist/systems/roof/roof-system.d.ts +46 -6
  179. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  180. package/dist/systems/roof/roof-system.js +1365 -170
  181. package/dist/systems/slab/slab-system.d.ts +5 -3
  182. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  183. package/dist/systems/slab/slab-system.js +204 -68
  184. package/dist/systems/slab/slab-system.test.d.ts +2 -0
  185. package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
  186. package/dist/systems/slab/slab-system.test.js +63 -0
  187. package/dist/systems/stair/stair-materials.d.ts +4 -3
  188. package/dist/systems/stair/stair-materials.d.ts.map +1 -1
  189. package/dist/systems/stair/stair-materials.js +30 -15
  190. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  191. package/dist/systems/stair/stair-system.js +55 -63
  192. package/dist/systems/surface-hole-geometry.d.ts +3 -0
  193. package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
  194. package/dist/systems/surface-hole-geometry.js +4 -0
  195. package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
  196. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
  197. package/dist/systems/wall/opening-cutout-geometry.js +167 -0
  198. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
  199. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
  200. package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
  201. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  202. package/dist/systems/wall/wall-cutout.js +62 -17
  203. package/dist/systems/wall/wall-materials.d.ts +12 -7
  204. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  205. package/dist/systems/wall/wall-materials.js +279 -46
  206. package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
  207. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
  208. package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
  209. package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
  210. package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
  211. package/dist/systems/wall/wall-support-extension.test.js +48 -0
  212. package/dist/systems/wall/wall-system.d.ts +2 -2
  213. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  214. package/dist/systems/wall/wall-system.js +493 -193
  215. package/dist/systems/window/window-animation-system.d.ts +12 -0
  216. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  217. package/dist/systems/window/window-animation-system.js +16 -5
  218. package/dist/systems/window/window-system.d.ts +7 -0
  219. package/dist/systems/window/window-system.d.ts.map +1 -1
  220. package/dist/systems/window/window-system.js +308 -18
  221. package/package.json +5 -6
  222. package/dist/components/renderers/building/building-renderer.d.ts +0 -5
  223. package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
  224. package/dist/components/renderers/building/building-renderer.js +0 -11
  225. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
  226. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
  227. package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
  228. package/dist/components/renderers/column/column-renderer.d.ts +0 -5
  229. package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
  230. package/dist/components/renderers/column/column-renderer.js +0 -597
  231. package/dist/components/renderers/door/door-renderer.d.ts +0 -5
  232. package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
  233. package/dist/components/renderers/door/door-renderer.js +0 -16
  234. package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
  235. package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
  236. package/dist/components/renderers/fence/fence-renderer.js +0 -15
  237. package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
  238. package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
  239. package/dist/components/renderers/guide/guide-renderer.js +0 -38
  240. package/dist/components/renderers/item/item-renderer.d.ts +0 -5
  241. package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
  242. package/dist/components/renderers/item/item-renderer.js +0 -172
  243. package/dist/components/renderers/level/level-renderer.d.ts +0 -5
  244. package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
  245. package/dist/components/renderers/level/level-renderer.js +0 -11
  246. package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
  247. package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
  248. package/dist/components/renderers/roof/roof-materials.js +0 -16
  249. package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
  250. package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
  251. package/dist/components/renderers/roof/roof-renderer.js +0 -32
  252. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
  253. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
  254. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
  255. package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
  256. package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
  257. package/dist/components/renderers/scan/scan-renderer.js +0 -54
  258. package/dist/components/renderers/site/site-renderer.d.ts +0 -5
  259. package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
  260. package/dist/components/renderers/site/site-renderer.js +0 -106
  261. package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
  262. package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
  263. package/dist/components/renderers/slab/slab-renderer.js +0 -58
  264. package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
  265. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
  266. package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
  267. package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
  268. package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
  269. package/dist/components/renderers/stair/stair-renderer.js +0 -603
  270. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
  271. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
  272. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
  273. package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
  274. package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
  275. package/dist/components/renderers/wall/wall-renderer.js +0 -16
  276. package/dist/components/renderers/window/window-renderer.d.ts +0 -5
  277. package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
  278. package/dist/components/renderers/window/window-renderer.js +0 -21
  279. package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
  280. package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
  281. package/dist/components/renderers/zone/zone-renderer.js +0 -181
  282. package/dist/components/viewer/ground-occluder.d.ts +0 -2
  283. package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
  284. package/dist/components/viewer/ground-occluder.js +0 -79
@@ -1,56 +1,27 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Bvh } from '@react-three/drei';
3
+ import { nodeRegistry, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
4
4
  import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
5
- import { useEffect, useMemo, useRef } from 'react';
5
+ import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
6
6
  import * as THREE from 'three/webgpu';
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';
12
- import { GuideSystem } from '../../systems/guide/guide-system';
13
- import { ItemSystem } from '../../systems/item/item-system';
14
- import { ItemLightSystem } from '../../systems/item-light/item-light-system';
15
- import { LevelSystem } from '../../systems/level/level-system';
16
- import { RoofSystem } from '../../systems/roof/roof-system';
17
- import { ScanSystem } from '../../systems/scan/scan-system';
18
- import { SlabSystem } from '../../systems/slab/slab-system';
19
- import { StairSystem } from '../../systems/stair/stair-system';
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';
24
- import { ZoneSystem } from '../../systems/zone/zone-system';
7
+ import { hasDrawableGeometry } from '../../lib/drawable-geometry';
8
+ import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf';
9
+ import { applyIsolation, clearIsolation } from '../../lib/isolation';
10
+ import { ensureKtx2Support } from '../../lib/ktx2-loader';
11
+ import { getSceneTheme } from '../../lib/scene-themes';
12
+ import useViewer, {} from '../../store/use-viewer';
13
+ import { FloorElevationSystem } from '../../systems/floor-elevation/floor-elevation-system';
14
+ import { GeometrySystem } from '../../systems/geometry/geometry-system';
25
15
  import { ErrorBoundary } from '../error-boundary';
26
16
  import { SceneRenderer } from '../renderers/scene-renderer';
27
17
  import FrameLimiter from './frame-limiter';
28
18
  import { Lights } from './lights';
29
19
  import { PerfMonitor } from './perf-monitor';
30
20
  import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
21
+ import { RegisteredSystems } from './registered-systems';
22
+ import { SceneBvh } from './scene-bvh';
31
23
  import { SelectionManager } from './selection-manager';
32
24
  import { ViewerCamera } from './viewer-camera';
33
- function AnimatedBackground({ isDark }) {
34
- const targetColor = useMemo(() => new THREE.Color(), []);
35
- const initialized = useRef(false);
36
- useFrame(({ scene }, delta) => {
37
- const dt = Math.min(delta, 0.1) * 4;
38
- const targetHex = isDark ? '#1f2433' : '#ffffff';
39
- if (!(scene.background && scene.background instanceof THREE.Color)) {
40
- scene.background = new THREE.Color(targetHex);
41
- initialized.current = true;
42
- return;
43
- }
44
- if (!initialized.current) {
45
- scene.background.set(targetHex);
46
- initialized.current = true;
47
- return;
48
- }
49
- targetColor.set(targetHex);
50
- scene.background.lerp(targetColor, dt);
51
- });
52
- return null;
53
- }
54
25
  extend(THREE);
55
26
  // R3F's <Canvas> useLayoutEffect has no deps, so any re-render (theme switch,
56
27
  // parent re-render, StrictMode double-mount) re-invokes `configure()`. With a
@@ -67,9 +38,77 @@ extend(THREE);
67
38
  // concurrent configure() calls await the same init instead of creating two
68
39
  // renderers in parallel and only caching the second.
69
40
  const WEBGPU_RENDERER_CACHE = new WeakMap();
41
+ const SCENE_READY_SETTLED_FRAMES = 2;
42
+ const SCENE_READY_MAX_WAIT_FRAMES = 180;
43
+ const DIRTY_BUILD_KINDS = new Set([
44
+ 'ceiling',
45
+ 'door',
46
+ 'item',
47
+ 'roof',
48
+ 'roof-segment',
49
+ 'stair',
50
+ 'stair-segment',
51
+ 'wall',
52
+ 'window',
53
+ ]);
54
+ const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet();
55
+ function canCreateWebGLContext() {
56
+ if (typeof document === 'undefined')
57
+ return false;
58
+ const canvas = document.createElement('canvas');
59
+ try {
60
+ return Boolean(canvas.getContext('webgl2') ?? canvas.getContext('webgl'));
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ function canMountGpuViewer() {
67
+ if (typeof window === 'undefined')
68
+ return false;
69
+ if (!('gpu' in navigator) && !canCreateWebGLContext())
70
+ return false;
71
+ return true;
72
+ }
73
+ function UnsupportedGpuViewerFallback() {
74
+ return (_jsx("div", { className: "flex h-full min-h-64 w-full items-center justify-center bg-[#fafafa] p-6 text-center text-neutral-900", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-lg", children: "3D viewer unavailable" }), _jsx("p", { className: "mt-2 text-neutral-600 text-sm", children: "This browser or environment does not expose WebGPU or WebGL, so Pascal cannot render the 3D scene here. Try opening the editor in a browser with hardware acceleration enabled." })] }) }));
75
+ }
76
+ /**
77
+ * Renderer-level safety net against the empty-vertex-buffer crash.
78
+ *
79
+ * Wraps the per-object render function so any draw whose geometry has a count-0
80
+ * `position` attribute is skipped instead of submitted. One such draw leaves
81
+ * WebGPU vertex buffer slot 0 unbound, which the validator rejects and which
82
+ * poisons the *whole* command encoder — so a single stray empty mesh (e.g. a
83
+ * transient placeholder, or a derived edge/outline geometry) flickers the entire
84
+ * canvas, not just itself. See `hasDrawableGeometry`.
85
+ *
86
+ * The custom render-object function is the documented three.js hook for this
87
+ * (`Renderer.setRenderObjectFunction`); it must call `renderObject()` for
88
+ * everything it keeps. `MergedOutlineNode` captures and restores this function
89
+ * around its passes, so the guard survives outline rendering (its own passes
90
+ * carry the same check inline).
91
+ */
92
+ function installEmptyDrawGuard(renderer) {
93
+ renderer.setRenderObjectFunction((object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId) => {
94
+ if (!hasDrawableGeometry(geometry)) {
95
+ if (warnedEmptyDraw && !warnedEmptyDraw.has(geometry ?? object)) {
96
+ warnedEmptyDraw.add(geometry ?? object);
97
+ console.warn('[viewer] skipped a draw with an empty position buffer (would poison the WebGPU command encoder)', { name: object?.name, type: object?.type, material: material?.name });
98
+ }
99
+ return;
100
+ }
101
+ ;
102
+ renderer.renderObject(object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId);
103
+ });
104
+ }
70
105
  function GPUDeviceWatcher() {
71
106
  const gl = useThree((s) => s.gl);
72
107
  useEffect(() => {
108
+ // Detect KTX2 transcode support as soon as the renderer exists, so catalog
109
+ // `.ktx2` finish textures load even in scenes with no GLB items (whose
110
+ // loader would otherwise be the only thing to call this).
111
+ ensureKtx2Support(gl);
73
112
  const backend = gl.backend;
74
113
  const device = backend?.device;
75
114
  if (!device) {
@@ -84,46 +123,214 @@ function GPUDeviceWatcher() {
84
123
  features: Array.from(device.features ?? []),
85
124
  });
86
125
  device.lost.then((info) => {
87
- console.error(`[viewer] WebGPU device lost: reason="${info.reason}", message="${info.message}". ` +
126
+ console.error(`[viewer] WebGPU device lost: reason="${info.reason ?? 'unknown'}", message="${info.message ?? ''}". ` +
88
127
  'The page must be reloaded to recover the GPU context.');
89
128
  });
90
129
  // Uncaptured errors are normally silent (only console-warned by Chrome at
91
130
  // best). Pipe them to console.error so silent mobile crashes show up.
92
131
  const onUncapturedError = (event) => {
93
- console.error('[viewer] WebGPU uncaptured error:', event.error.message, event.error);
132
+ console.error('[viewer] WebGPU uncaptured error:', event?.error?.message, event?.error);
94
133
  };
95
- device.addEventListener('uncapturederror', onUncapturedError);
134
+ device.addEventListener?.('uncapturederror', onUncapturedError);
96
135
  return () => {
97
- device.removeEventListener('uncapturederror', onUncapturedError);
136
+ device.removeEventListener?.('uncapturederror', onUncapturedError);
98
137
  };
99
138
  }, [gl]);
100
139
  return null;
101
140
  }
102
- const Viewer = ({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, }) => {
103
- const theme = useViewer((state) => state.theme);
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) => {
141
+ function ToneMappingExposure() {
142
+ const sceneTheme = useViewer((state) => state.sceneTheme);
143
+ const gl = useThree((state) => state.gl);
144
+ const invalidate = useThree((state) => state.invalidate);
145
+ useEffect(() => {
146
+ gl.toneMappingExposure = getSceneTheme(sceneTheme).toneMappingExposure;
147
+ invalidate();
148
+ }, [gl, invalidate, sceneTheme]);
149
+ return null;
150
+ }
151
+ function hasPendingSceneBuildWork() {
152
+ const { dirtyNodes, nodes, rootNodeIds } = useScene.getState();
153
+ for (const id of dirtyNodes) {
154
+ const node = nodes[id];
155
+ if (!node)
156
+ continue;
157
+ // Unreachable nodes (orphaned by a broken detach: the parent doesn't list
158
+ // them in `children`, or no parent and not a root) never render — every
159
+ // renderer enumerates the parent's `children` array — so no system will
160
+ // ever build them or clear their mark. They must not hold scene-ready
161
+ // hostage (observed in prod scene data: two dangling windows kept every
162
+ // bake of that scene waiting out the full readiness cap).
163
+ const parent = node.parentId ? nodes[node.parentId] : undefined;
164
+ const reachable = parent
165
+ ? parent.children?.includes(id) === true
166
+ : rootNodeIds.includes(id);
167
+ if (!reachable)
168
+ continue;
169
+ const def = nodeRegistry.get(node.type);
170
+ if (def?.geometry || def?.capabilities?.floorPlaced || DIRTY_BUILD_KINDS.has(node.type)) {
171
+ return true;
172
+ }
173
+ }
174
+ return false;
175
+ }
176
+ function hasCommittedSceneRoot() {
177
+ const { nodes, rootNodeIds } = useScene.getState();
178
+ if (rootNodeIds.length === 0)
179
+ return Object.keys(nodes).length === 0;
180
+ return rootNodeIds.some((id) => sceneRegistry.nodes.has(id));
181
+ }
182
+ function SceneReadyTracker({ onSceneReadyChange, sceneReadyKey, sceneReadyMaxWaitMs, }) {
183
+ const invalidate = useThree((state) => state.invalidate);
184
+ const readyRef = useRef(false);
185
+ const settledFramesRef = useRef(0);
186
+ const waitedFramesRef = useRef(0);
187
+ const waitStartRef = useRef(null);
188
+ const onSceneReadyChangeRef = useRef(onSceneReadyChange);
189
+ useEffect(() => {
190
+ onSceneReadyChangeRef.current = onSceneReadyChange;
191
+ }, [onSceneReadyChange]);
192
+ useEffect(() => {
193
+ void sceneReadyKey;
194
+ readyRef.current = false;
195
+ settledFramesRef.current = 0;
196
+ waitedFramesRef.current = 0;
197
+ waitStartRef.current = null;
198
+ onSceneReadyChangeRef.current?.(false);
199
+ invalidate();
200
+ }, [invalidate, sceneReadyKey]);
201
+ useFrame(() => {
202
+ if (!(onSceneReadyChangeRef.current && !readyRef.current))
203
+ return;
204
+ waitedFramesRef.current += 1;
205
+ waitStartRef.current ??= performance.now();
206
+ // Give-up cap so a permanently-dirty node can't block readiness forever.
207
+ // The frame-count default assumes display-rate frames; a host whose frame
208
+ // cadence is decoupled from wall time (the headless bake page's timer-driven
209
+ // loop runs 180 frames in 3.6s — faster than a cold item download) passes
210
+ // `sceneReadyMaxWaitMs` to make the cap wall-clock instead.
211
+ const capReached = sceneReadyMaxWaitMs
212
+ ? performance.now() - waitStartRef.current >= sceneReadyMaxWaitMs
213
+ : waitedFramesRef.current >= SCENE_READY_MAX_WAIT_FRAMES;
214
+ if (!capReached && (!hasCommittedSceneRoot() || hasPendingSceneBuildWork())) {
215
+ settledFramesRef.current = 0;
216
+ invalidate();
217
+ return;
218
+ }
219
+ settledFramesRef.current += 1;
220
+ if (settledFramesRef.current < SCENE_READY_SETTLED_FRAMES) {
221
+ invalidate();
222
+ return;
223
+ }
224
+ readyRef.current = true;
225
+ onSceneReadyChangeRef.current(true);
226
+ }, 10);
227
+ return null;
228
+ }
229
+ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, isolate, sceneReadyKey, onSceneReadyChange, sceneReadyMaxWaitMs, disablePostFx = false, }, ref) {
230
+ useImperativeHandle(ref, () => ({
231
+ setIsolated: (ids) => applyIsolation(ids),
232
+ }), []);
233
+ // Track the most recently-applied isolation so the cleanup path can
234
+ // restore visibility even if the prop is removed while the component is
235
+ // still mounted. `clearIsolation()` is a no-op when nothing was applied.
236
+ const isolateRef = useRef(undefined);
237
+ useEffect(() => {
238
+ isolateRef.current = isolate ?? null;
239
+ applyIsolation(isolate ?? null);
240
+ return () => {
241
+ // Only clear if this effect was the one that applied — protects
242
+ // against a parent unmount racing with a setIsolated() consumer.
243
+ if (isolateRef.current === isolate)
244
+ clearIsolation();
245
+ };
246
+ }, [isolate]);
247
+ const [rendererInitFailed, setRendererInitFailed] = useState(false);
248
+ // Capability detection runs after mount. We start optimistic (true) so the
249
+ // server-rendered markup and the first client render agree (no hydration
250
+ // mismatch); the effect flips it to false only on environments that expose
251
+ // neither WebGPU nor WebGL.
252
+ const [canMountViewer, setCanMountViewer] = useState(true);
253
+ useEffect(() => {
254
+ if (!canMountGpuViewer())
255
+ setCanMountViewer(false);
256
+ }, []);
257
+ const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
258
+ const transparentBackground = useViewer((state) => state.transparentBackground);
259
+ // The shadows toggle drives `renderer.shadowMap.enabled` (via the Canvas
260
+ // `shadows` prop) rather than the lights' `castShadow`: toggling castShadow
261
+ // off disposes the shadow map's GPU texture but three r184's WebGPU node
262
+ // cache keeps the shadows-on builder state that references it, so toggling
263
+ // back on reuses destroyed resources and every frame submit fails with a
264
+ // GPUValidationError. Disabling at the renderer level rebuilds materials
265
+ // without disposing anything, so the round-trip is safe.
266
+ const shadowsEnabled = useViewer((state) => state.shadows);
267
+ useLayoutEffect(() => {
268
+ if (transparent === undefined)
269
+ return;
270
+ useViewer.getState().setTransparentBackground(transparent);
271
+ return () => {
272
+ useViewer.getState().setTransparentBackground(false);
273
+ };
274
+ }, [transparent]);
275
+ const defaultShading = defaultRender?.shading;
276
+ const defaultTextures = defaultRender?.textures;
277
+ const defaultColorPreset = defaultRender?.colorPreset;
278
+ const hasDefaultRender = defaultRender != null;
279
+ useEffect(() => {
280
+ const ctx = renderContext;
281
+ useViewer.getState().setRenderContext(ctx);
282
+ const { shading, shadingByContext, setShading } = useViewer.getState();
283
+ setShading(shadingByContext[ctx] ?? defaultShading ?? shading);
284
+ if (!hasDefaultRender || typeof window === 'undefined')
285
+ return;
286
+ let persistedState = {};
287
+ const rawPreferences = window.localStorage.getItem('viewer-preferences');
288
+ if (rawPreferences) {
289
+ try {
290
+ const parsed = JSON.parse(rawPreferences);
291
+ if (parsed &&
292
+ typeof parsed === 'object' &&
293
+ parsed.state &&
294
+ typeof parsed.state === 'object') {
295
+ persistedState = parsed.state;
296
+ }
297
+ }
298
+ catch { }
299
+ }
300
+ if (defaultTextures !== undefined && !('textures' in persistedState)) {
301
+ useViewer.getState().setTextures(defaultTextures);
302
+ }
303
+ if (defaultColorPreset && !('colorPreset' in persistedState)) {
304
+ useViewer.getState().setColorPreset(defaultColorPreset);
305
+ }
306
+ }, [defaultColorPreset, defaultShading, defaultTextures, hasDefaultRender, renderContext]);
307
+ // Coarse-pointer devices (phones/tablets) get a tighter DPR ceiling to keep
308
+ // fragment-shader cost down — saves another ~30% over 1.5x on high-DPI mobile.
309
+ // Desktops (fine pointer) keep the original 1.5 cap.
310
+ const maxDpr = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches ? 1.25 : 1.5;
311
+ const showGpuFallback = !canMountViewer || rendererInitFailed;
312
+ // When we can't mount the GPU canvas, the SceneReadyTracker never mounts and
313
+ // the host editor would otherwise wait on its scene-readiness timeout. Signal
314
+ // readiness explicitly so the host can drop its loader immediately.
315
+ useEffect(() => {
316
+ if (showGpuFallback)
317
+ onSceneReadyChange?.(true);
318
+ }, [showGpuFallback, onSceneReadyChange]);
319
+ if (showGpuFallback) {
320
+ return _jsx(UnsupportedGpuViewerFallback, {});
321
+ }
322
+ return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], frameloop: "never", gl: ((props) => {
105
323
  const canvas = props.canvas;
106
324
  const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
107
325
  if (cached)
108
326
  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
327
  const promise = (async () => {
118
328
  try {
119
- const renderer = new THREE.WebGPURenderer(props);
329
+ const renderer = new THREE.WebGPURenderer({ ...props, alpha: true });
120
330
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
121
- renderer.toneMappingExposure = 0.9;
331
+ renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
122
332
  await renderer.init();
123
- console.log('[viewer] WebGPURenderer ready', {
124
- backend: renderer.backend?.constructor?.name,
125
- isWebGPU: renderer.isWebGPURenderer === true,
126
- });
333
+ installEmptyDrawGuard(renderer);
127
334
  return renderer;
128
335
  }
129
336
  catch (err) {
@@ -133,6 +340,7 @@ const Viewer = ({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager
133
340
  if (canvas)
134
341
  WEBGPU_RENDERER_CACHE.delete(canvas);
135
342
  console.error('[viewer] WebGPURenderer init failed', err);
343
+ setRendererInitFailed(true);
136
344
  throw err;
137
345
  }
138
346
  })();
@@ -143,12 +351,19 @@ const Viewer = ({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager
143
351
  debounce: 100,
144
352
  }, shadows: {
145
353
  type: THREE.PCFShadowMap,
146
- enabled: true,
147
- }, children: [_jsx(FrameLimiter, { fps: 50 }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(Bvh, { children: _jsx(SceneRenderer, {}) })) : (_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] })] }));
148
- };
354
+ enabled: shadowsEnabled,
355
+ }, children: [_jsx(FrameLimiter, { fps: 50 }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), children] })] }));
356
+ });
149
357
  const DebugRenderer = () => {
150
358
  useFrame(({ gl, scene, camera }) => {
359
+ const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
151
360
  gl.render(scene, camera);
361
+ if (PERF_OVERLAY_ENABLED) {
362
+ const queue = gl.backend?.device?.queue;
363
+ queue?.onSubmittedWorkDone?.().then(() => {
364
+ pushGpuSample(performance.now() - submittedAt);
365
+ });
366
+ }
152
367
  });
153
368
  return null;
154
369
  };
@@ -1,2 +1,2 @@
1
- export declare function Lights(): import("react/jsx-runtime").JSX.Element;
1
+ export declare function Lights(): import("react").JSX.Element;
2
2
  //# sourceMappingURL=lights.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AAMA,wBAAgB,MAAM,4CAoIrB"}
1
+ {"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AA4EA,wBAAgB,MAAM,gCAgPrB"}