@pascal-app/editor 1.0.7 → 1.1.1

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 (336) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +101 -45
  5. package/src/components/editor/duplicate-node.ts +2 -1
  6. package/src/components/editor/editor-layout-v2.tsx +1 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +20 -1983
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +5 -3
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +8 -4
  15. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  16. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  17. package/src/components/editor/floorplan-panel.tsx +199 -30
  18. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  19. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  20. package/src/components/editor/follow-pascal.test.tsx +1 -2
  21. package/src/components/editor/follow-pascal.tsx +1 -1
  22. package/src/components/editor/footprint-height-handle.tsx +2 -2
  23. package/src/components/editor/group-actions.ts +1 -2
  24. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  25. package/src/components/editor/group-move-3d.ts +1 -2
  26. package/src/components/editor/group-rotate-handle.tsx +1 -2
  27. package/src/components/editor/group-transform-shared.ts +3 -1
  28. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  29. package/src/components/editor/index.tsx +43 -13
  30. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  31. package/src/components/editor/measurement-pill.tsx +1 -1
  32. package/src/components/editor/node-arrow-handles.tsx +2 -2
  33. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  34. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  35. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  36. package/src/components/editor/quick-measurement-card.tsx +1 -1
  37. package/src/components/editor/room-controls.tsx +10 -6
  38. package/src/components/editor/room-handles-2d.tsx +3 -3
  39. package/src/components/editor/room-handles.tsx +4 -5
  40. package/src/components/editor/room-highlight.tsx +6 -105
  41. package/src/components/editor/room-labels.test.ts +8 -822
  42. package/src/components/editor/room-labels.tsx +41 -1670
  43. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  44. package/src/components/editor/selection-manager.tsx +5 -3
  45. package/src/components/editor/site-edge-labels.tsx +7 -3
  46. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  47. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  48. package/src/components/editor/views/plugins-view.tsx +1 -1
  49. package/src/components/editor/views/view-bar.tsx +9 -9
  50. package/src/components/editor/wall-measurement-label.tsx +31 -15
  51. package/src/components/editor/wall-move-side-handles.tsx +11 -7
  52. package/src/components/editor/wall-pull-grip.tsx +2 -2
  53. package/src/components/editor/wall-region-handles.tsx +2 -2
  54. package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
  55. package/src/components/editor-2d/floorplan-group-move.tsx +3 -4
  56. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +13 -13
  57. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +2 -3
  58. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  59. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +23 -6
  60. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  61. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  62. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  63. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  64. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +14 -3
  65. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  66. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +30 -10
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +39 -35
  69. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  70. package/src/components/systems/roof/roof-edit-system.tsx +33 -21
  71. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  72. package/src/components/tools/distribution/distribution-run-tool.tsx +151 -33
  73. package/src/components/tools/item/move-tool.tsx +1 -6
  74. package/src/components/tools/item/placement-history.test.tsx +1 -1
  75. package/src/components/tools/item/use-placement-coordinator.tsx +3 -4
  76. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +7 -3
  77. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  78. package/src/components/tools/registry/move-registry-node-tool.tsx +39 -16
  79. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  80. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  81. package/src/components/tools/shared/placement-box.tsx +2 -6
  82. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  83. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  84. package/src/components/tools/shared/segment-angle.ts +0 -4
  85. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  86. package/src/components/tools/tool-manager.tsx +1 -24
  87. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  88. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  89. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  90. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  91. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  92. package/src/components/ui/action-menu/index.tsx +3 -3
  93. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  94. package/src/components/ui/action-menu/placement.ts +0 -18
  95. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  96. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  97. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  98. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  99. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  100. package/src/components/ui/controls/select-control.tsx +1 -1
  101. package/src/components/ui/controls/slider-control.tsx +18 -6
  102. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  103. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  104. package/src/components/ui/floating-level-selector.tsx +3 -3
  105. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  106. package/src/components/ui/helpers/helper-manager.test.tsx +54 -1
  107. package/src/components/ui/helpers/helper-manager.tsx +55 -1
  108. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  109. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  110. package/src/components/ui/panels/floor-foundation-panel.tsx +3 -3
  111. package/src/components/ui/panels/floor-opening-panel.tsx +5 -5
  112. package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
  113. package/src/components/ui/panels/multi-height-mode.tsx +3 -3
  114. package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
  115. package/src/components/ui/panels/node-display.ts +1 -1
  116. package/src/components/ui/panels/panel-manager.tsx +5 -3
  117. package/src/components/ui/panels/panel-wrapper.tsx +42 -19
  118. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  119. package/src/components/ui/panels/room-construction-rows.tsx +7 -7
  120. package/src/components/ui/panels/room-name-options.ts +1 -5
  121. package/src/components/ui/panels/room-openings-field.tsx +1 -1
  122. package/src/components/ui/panels/room-panel.tsx +7 -9
  123. package/src/components/ui/primitives/color-dot.tsx +2 -2
  124. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  125. package/src/components/ui/primitives/sidebar.tsx +3 -3
  126. package/src/components/ui/right-stack.test.ts +13 -6
  127. package/src/components/ui/right-stack.tsx +22 -11
  128. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  129. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  130. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  131. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  132. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  133. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  134. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +2 -6
  135. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  136. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  137. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
  138. package/src/components/ui/sidebar/panels/site-panel/index.tsx +3 -10
  139. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  140. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  141. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  142. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
  143. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  144. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  145. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  146. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
  147. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  148. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  149. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +2 -2
  150. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  151. package/src/components/viewer-overlay.tsx +1 -2
  152. package/src/hooks/use-auto-frame.ts +1 -2
  153. package/src/hooks/use-keyboard.ts +1 -2
  154. package/src/hooks/use-selected-room.ts +5 -7
  155. package/src/i18n/floor-footprint-name.ts +1 -1
  156. package/src/i18n/locales/en/messages.po +1036 -306
  157. package/src/i18n/locales/en/messages.ts +1 -1
  158. package/src/i18n/locales/en-XA/messages.ts +1 -1
  159. package/src/i18n/locales/es/messages.po +2267 -439
  160. package/src/i18n/locales/fr/messages.po +2267 -439
  161. package/src/i18n/locales/ja/messages.po +2267 -439
  162. package/src/i18n/locales/zh-CN/messages.po +2267 -439
  163. package/src/i18n/locales.test.ts +1 -1
  164. package/src/index.tsx +183 -178
  165. package/src/lib/below-grade.test.tsx +4 -4
  166. package/src/lib/camera-facing.test.ts +1 -1
  167. package/src/lib/camera-facing.ts +2 -3
  168. package/src/lib/contextual-help.ts +6 -0
  169. package/src/lib/editor-level-display.ts +1 -1
  170. package/src/lib/elevation-guides.ts +21 -6
  171. package/src/lib/elevator-support.ts +1 -72
  172. package/src/lib/floor-footprints.test.ts +3 -3
  173. package/src/lib/floor-footprints.ts +1 -9
  174. package/src/lib/floor-foundation-support.test.ts +8 -3
  175. package/src/lib/floor-opening-draft.ts +2 -2
  176. package/src/lib/floor-region-session.ts +1 -1
  177. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  178. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  179. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  180. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  181. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  182. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  183. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  184. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  185. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  186. package/src/lib/floorplan/geometry.ts +2 -112
  187. package/src/lib/floorplan/index.ts +0 -9
  188. package/src/lib/floorplan/items.ts +2 -1
  189. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  190. package/src/lib/floorplan/schedules.ts +6 -3
  191. package/src/lib/floorplan/sheet-export.ts +7 -3
  192. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  193. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  194. package/src/lib/follow-pascal-presentation.ts +1 -1
  195. package/src/lib/gesture-lifecycle.ts +3 -1
  196. package/src/lib/glb-export.ts +1 -1
  197. package/src/lib/interaction/scope.ts +1 -0
  198. package/src/lib/layer-thickness.ts +1 -1
  199. package/src/lib/level-selection.test.ts +18 -2
  200. package/src/lib/level-selection.ts +31 -8
  201. package/src/lib/linear-display.ts +1 -1
  202. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  203. package/src/lib/material-paint.ts +1 -1
  204. package/src/lib/measurement-label.test.ts +1 -1
  205. package/src/lib/measurements.test.ts +7 -5
  206. package/src/lib/mezzanine-selection.test.ts +1 -1
  207. package/src/lib/node-action-movement.test.tsx +115 -0
  208. package/src/lib/node-action-movement.ts +2 -0
  209. package/src/lib/paint-region-mode.ts +1 -1
  210. package/src/lib/placement-metadata.ts +4 -0
  211. package/src/lib/point-ask/describe-target.ts +6 -8
  212. package/src/lib/point-ask/region.ts +1 -1
  213. package/src/lib/point-ask/session.ts +1 -2
  214. package/src/lib/print-shell-compiler.ts +12 -1
  215. package/src/lib/print-wall-solids.test.ts +11 -0
  216. package/src/lib/print-wall-solids.ts +9 -0
  217. package/src/lib/roof-duplication.test.ts +100 -0
  218. package/src/lib/roof-duplication.ts +30 -5
  219. package/src/lib/room-built-on.test.ts +2 -5
  220. package/src/lib/room-built-on.ts +1 -15
  221. package/src/lib/room-construction-commands.ts +3 -2
  222. package/src/lib/room-controls-phase6.test.ts +1 -1
  223. package/src/lib/room-handle-drag.ts +8 -37
  224. package/src/lib/room-name-catalog.test.ts +2 -1
  225. package/src/lib/room-name-messages.test.ts +2 -1
  226. package/src/lib/room-name-messages.ts +1 -118
  227. package/src/lib/room-name-storage.test.ts +2 -1
  228. package/src/lib/room-push-dimensions.test.ts +1 -1
  229. package/src/lib/room-selection-commands.ts +2 -2
  230. package/src/lib/room-selection.test.ts +6 -6
  231. package/src/lib/room-transform-session.ts +1 -2
  232. package/src/lib/room-zone-routing.ts +2 -2
  233. package/src/lib/scene-clipboard.ts +2 -1
  234. package/src/lib/selection-routing.test.ts +0 -28
  235. package/src/lib/selection-routing.ts +1 -40
  236. package/src/lib/sfx-bus.ts +8 -32
  237. package/src/lib/sfx-player.test.ts +72 -1
  238. package/src/lib/sfx-player.ts +49 -337
  239. package/src/lib/snapping-mode.test.ts +23 -1
  240. package/src/lib/viewer-selection.test.ts +38 -10
  241. package/src/lib/wall-pull.test.ts +1 -1
  242. package/src/lib/wall-pull.ts +1 -27
  243. package/src/lib/wall-region-list.ts +3 -1
  244. package/src/lib/window-interaction.test.ts +4 -2
  245. package/src/store/use-editor.tsx +22 -26
  246. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  247. package/src/store/use-placement-typing.test.ts +165 -0
  248. package/src/store/use-placement-typing.ts +186 -0
  249. package/src/store/use-wall-region-handles-selection.ts +1 -2
  250. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  251. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  252. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  253. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  254. package/src/components/editor/first-person/ground-holes.ts +0 -130
  255. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  256. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  257. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  258. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  259. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  260. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  261. package/src/components/editor/localized-html.tsx +0 -22
  262. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  263. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  264. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  265. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  266. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  267. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  268. package/src/components/editor-2d/svg-paths.ts +0 -124
  269. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  270. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  271. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  272. package/src/components/ui/action-menu/action-button.tsx +0 -89
  273. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  274. package/src/components/ui/primitives/button.tsx +0 -69
  275. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  276. package/src/components/ui/primitives/popover.tsx +0 -42
  277. package/src/components/ui/primitives/switch.tsx +0 -29
  278. package/src/components/ui/primitives/tooltip.tsx +0 -57
  279. package/src/components/ui/scene-loader.tsx +0 -73
  280. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  281. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  282. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  283. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  284. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  285. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  286. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  287. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  288. package/src/components/viewer/render-settings-menu.tsx +0 -294
  289. package/src/components/viewer/sky-control.tsx +0 -710
  290. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  291. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  292. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  293. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  294. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  295. package/src/components/viewer/viewer-rooms.tsx +0 -248
  296. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  297. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  298. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  299. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  300. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  301. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  302. package/src/components/viewer/viewer-stage.tsx +0 -221
  303. package/src/components/walkthrough-hud.tsx +0 -193
  304. package/src/hooks/use-mobile.ts +0 -19
  305. package/src/i18n/level-name.ts +0 -39
  306. package/src/i18n/scene-theme-name.ts +0 -16
  307. package/src/lib/below-grade.ts +0 -133
  308. package/src/lib/camera-pose.test.ts +0 -237
  309. package/src/lib/camera-pose.ts +0 -172
  310. package/src/lib/door-interaction.ts +0 -102
  311. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  312. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  313. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  314. package/src/lib/floorplan/marks.test.ts +0 -213
  315. package/src/lib/floorplan/marks.ts +0 -238
  316. package/src/lib/measurement-label.ts +0 -68
  317. package/src/lib/measurements.ts +0 -225
  318. package/src/lib/node-display-label.ts +0 -37
  319. package/src/lib/room-assembly-overlay.test.ts +0 -275
  320. package/src/lib/room-assembly-overlay.ts +0 -265
  321. package/src/lib/room-selection.ts +0 -417
  322. package/src/lib/scene-bounds.test.ts +0 -183
  323. package/src/lib/scene-bounds.ts +0 -169
  324. package/src/lib/typing-target.ts +0 -9
  325. package/src/lib/viewer-selection.ts +0 -595
  326. package/src/lib/window-interaction.ts +0 -142
  327. package/src/lib/zone-content.ts +0 -133
  328. package/src/store/camera-pose-store.test.ts +0 -29
  329. package/src/store/camera-pose-store.ts +0 -27
  330. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  331. package/src/store/navigation-sync-pose-store.ts +0 -27
  332. package/src/store/use-audio.test.ts +0 -146
  333. package/src/store/use-audio.tsx +0 -169
  334. package/src/store/use-first-person-hud.ts +0 -40
  335. package/src/store/use-follow-pascal.test.ts +0 -62
  336. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,212 +0,0 @@
1
- /**
2
- * The sculpted ground as walkthrough collision geometry.
3
- *
4
- * The first-person collider world is a single merged BVH mesh, and before terrain
5
- * the ground contributed one flat 2 km box to it. That box is wrong in both
6
- * directions on a sculpted site: it holds the player up at the datum inside an
7
- * excavation, and buries them inside a hill. This module replaces it.
8
- *
9
- * Two properties it has to hold, because the whole point is that walking agrees
10
- * with every other consumer of the field:
11
- *
12
- * - **The surface is the field**, sampled at every vertex — the same
13
- * `heightAtSample`/`normalAt` the rendered mesh uses (`terrain-geometry.ts`),
14
- * so what you see is what you stand on. It is deliberately not a decimated
15
- * proxy at realistic field sizes.
16
- * - **It extends past the field**, because `heightAt` clamps to the border
17
- * outside it. A collider that stopped at the field edge would drop the player
18
- * into the void one step past the site, which is exactly the bug the flat box
19
- * existed to prevent. The skirt extrudes each border sample outward at its own
20
- * height, so the collider and `heightAt` agree everywhere, not just inside.
21
- *
22
- * Non-indexed triangles with position + normal, matching what the other collider
23
- * contributors emit — `mergeGeometries` requires every input to agree on both.
24
- */
25
-
26
- import { heightAtSample, normalAt, type TerrainField } from '@pascal-app/core'
27
- import * as THREE from 'three'
28
-
29
- /**
30
- * How far past the field the ground keeps holding the player, in metres. Matches
31
- * the flat box's half-extent so a site's collider bounds do not change size when
32
- * it gains terrain.
33
- */
34
- const SKIRT_REACH = 1000
35
-
36
- /**
37
- * Cell budget before the collider decimates.
38
- *
39
- * 66k cells is a 257² field — the largest anything realistic produces, and ~131k
40
- * triangles, which the BVH builds in well under a frame's worth of budget. The
41
- * schema allows up to 4097² (a decode-bomb guard, not a real terrain); at that
42
- * size an exact collider would be 100M triangles, so past the budget this steps
43
- * the sample grid and the collider becomes a faithful-but-coarser version of the
44
- * same surface rather than an out-of-memory crash.
45
- */
46
- const MAX_COLLIDER_CELLS = 66_000
47
-
48
- function colliderStride(field: TerrainField): number {
49
- const cells = Math.max(1, (field.cols - 1) * (field.rows - 1))
50
- if (cells <= MAX_COLLIDER_CELLS) return 1
51
- return Math.ceil(Math.sqrt(cells / MAX_COLLIDER_CELLS))
52
- }
53
-
54
- /** The sample indices the collider walks: every `stride`-th, always including the border. */
55
- function sampleLine(count: number, stride: number): number[] {
56
- const line: number[] = []
57
- for (let i = 0; i < count - 1; i += stride) line.push(i)
58
- line.push(count - 1)
59
- return line
60
- }
61
-
62
- type Vertex = { x: number; y: number; z: number }
63
-
64
- class TriangleSink {
65
- private readonly positions: number[] = []
66
- private readonly normals: number[] = []
67
-
68
- constructor(private readonly field: TerrainField) {}
69
-
70
- private push(v: Vertex): void {
71
- this.positions.push(v.x, v.y, v.z)
72
- const [nx, ny, nz] = normalAt(this.field, v.x, v.z)
73
- this.normals.push(nx, ny, nz)
74
- }
75
-
76
- /**
77
- * One cell, given its four corners named by axis order. Winding matches
78
- * `buildTerrainMesh`: `(p00, p01, p10)` and `(p10, p01, p11)` faces +Y, which is
79
- * what the spawn resolver's `normal.dot(UP) > 0.2` floor test needs.
80
- */
81
- quad(p00: Vertex, p10: Vertex, p01: Vertex, p11: Vertex): void {
82
- this.push(p00)
83
- this.push(p01)
84
- this.push(p10)
85
- this.push(p10)
86
- this.push(p01)
87
- this.push(p11)
88
- }
89
-
90
- build(): THREE.BufferGeometry | null {
91
- if (this.positions.length === 0) return null
92
- const geometry = new THREE.BufferGeometry()
93
- geometry.setAttribute(
94
- 'position',
95
- new THREE.BufferAttribute(new Float32Array(this.positions), 3),
96
- )
97
- geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.normals), 3))
98
- return geometry
99
- }
100
- }
101
-
102
- /**
103
- * Terrain collision geometry in the site's own frame — the caller applies the
104
- * site's world matrix, exactly as it did for the flat box it replaces.
105
- */
106
- export function createTerrainColliderGeometry(field: TerrainField): THREE.BufferGeometry | null {
107
- if (field.cols < 2 || field.rows < 2) return null
108
-
109
- const stride = colliderStride(field)
110
- const cols = sampleLine(field.cols, stride)
111
- const rows = sampleLine(field.rows, stride)
112
- const sink = new TriangleSink(field)
113
-
114
- const xAt = (col: number) => field.origin[0] + col * field.spacing
115
- const zAt = (row: number) => field.origin[1] + row * field.spacing
116
- const at = (col: number, row: number): Vertex => ({
117
- x: xAt(col),
118
- y: heightAtSample(field, col, row),
119
- z: zAt(row),
120
- })
121
-
122
- for (let ri = 0; ri < rows.length - 1; ri++) {
123
- const r0 = rows[ri] as number
124
- const r1 = rows[ri + 1] as number
125
- for (let ci = 0; ci < cols.length - 1; ci++) {
126
- const c0 = cols[ci] as number
127
- const c1 = cols[ci + 1] as number
128
- sink.quad(at(c0, r0), at(c1, r0), at(c0, r1), at(c1, r1))
129
- }
130
- }
131
-
132
- // The skirt: each border sample extruded outward at its own height, so stepping
133
- // off the field lands on the border height rather than on nothing. This is the
134
- // collider spelling of `heightAt`'s edge clamp.
135
- const firstCol = 0
136
- const lastCol = field.cols - 1
137
- const firstRow = 0
138
- const lastRow = field.rows - 1
139
- const westX = xAt(firstCol) - SKIRT_REACH
140
- const eastX = xAt(lastCol) + SKIRT_REACH
141
- const northZ = zAt(firstRow) - SKIRT_REACH
142
- const southZ = zAt(lastRow) + SKIRT_REACH
143
-
144
- for (let ci = 0; ci < cols.length - 1; ci++) {
145
- const c0 = cols[ci] as number
146
- const c1 = cols[ci + 1] as number
147
- const north0 = at(c0, firstRow)
148
- const north1 = at(c1, firstRow)
149
- sink.quad(
150
- { x: north0.x, y: north0.y, z: northZ },
151
- { x: north1.x, y: north1.y, z: northZ },
152
- north0,
153
- north1,
154
- )
155
- const south0 = at(c0, lastRow)
156
- const south1 = at(c1, lastRow)
157
- sink.quad(
158
- south0,
159
- south1,
160
- { x: south0.x, y: south0.y, z: southZ },
161
- {
162
- x: south1.x,
163
- y: south1.y,
164
- z: southZ,
165
- },
166
- )
167
- }
168
-
169
- for (let ri = 0; ri < rows.length - 1; ri++) {
170
- const r0 = rows[ri] as number
171
- const r1 = rows[ri + 1] as number
172
- const west0 = at(firstCol, r0)
173
- const west1 = at(firstCol, r1)
174
- sink.quad(
175
- { x: westX, y: west0.y, z: west0.z },
176
- west0,
177
- { x: westX, y: west1.y, z: west1.z },
178
- west1,
179
- )
180
- const east0 = at(lastCol, r0)
181
- const east1 = at(lastCol, r1)
182
- sink.quad(east0, { x: eastX, y: east0.y, z: east0.z }, east1, {
183
- x: eastX,
184
- y: east1.y,
185
- z: east1.z,
186
- })
187
- }
188
-
189
- // Corner quads, flat at the corner sample's height — the diagonal region no edge
190
- // strip covers, and where `heightAt` also reports the corner value.
191
- const corners: Array<[Vertex, number, number]> = [
192
- [at(firstCol, firstRow), westX, northZ],
193
- [at(lastCol, firstRow), eastX, northZ],
194
- [at(firstCol, lastRow), westX, southZ],
195
- [at(lastCol, lastRow), eastX, southZ],
196
- ]
197
- for (const [corner, farX, farZ] of corners) {
198
- const minX = Math.min(corner.x, farX)
199
- const maxX = Math.max(corner.x, farX)
200
- const minZ = Math.min(corner.z, farZ)
201
- const maxZ = Math.max(corner.z, farZ)
202
- const y = corner.y
203
- sink.quad(
204
- { x: minX, y, z: minZ },
205
- { x: maxX, y, z: minZ },
206
- { x: minX, y, z: maxZ },
207
- { x: maxX, y, z: maxZ },
208
- )
209
- }
210
-
211
- return sink.build()
212
- }
@@ -1,249 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { CameraPose } from '@pascal-app/core'
3
- import { publishInitialCameraPose } from '../../lib/camera-pose'
4
- import type { NavigationSyncPose } from '../../store/use-editor'
5
- import {
6
- cameraPoseToFloorplanNavigationPose,
7
- createFloorplanCameraNavigationChannel,
8
- createFloorplanCameraSyncBridge,
9
- floorplanNavigationPoseToCameraPose,
10
- } from './floorplan-camera-sync'
11
-
12
- const cameraPose: CameraPose = {
13
- fov: 50,
14
- position: [3, 4, 4],
15
- projection: 'perspective',
16
- target: [0, 1, 0],
17
- viewWidth: 12,
18
- }
19
-
20
- function cameraPoseAtAzimuth(
21
- azimuth: number,
22
- target: [number, number, number] = [0, 1, 0],
23
- ): CameraPose {
24
- const horizontalDistance = 5
25
- return {
26
- ...cameraPose,
27
- position: [
28
- target[0] + Math.sin(azimuth) * horizontalDistance,
29
- 4,
30
- target[2] + Math.cos(azimuth) * horizontalDistance,
31
- ],
32
- target,
33
- }
34
- }
35
-
36
- describe('floorplan camera sync', () => {
37
- test('derives the floor-plan navigation pose from the generic camera pose', () => {
38
- expect(
39
- cameraPoseToFloorplanNavigationPose({
40
- position: [10, 5, 0],
41
- projection: 'perspective',
42
- target: [0, 0, 0],
43
- viewWidth: 20,
44
- }),
45
- ).toEqual({
46
- source: '3d',
47
- target: [0, 0, 0],
48
- azimuth: Math.PI / 2,
49
- viewWidth: 20,
50
- })
51
- })
52
-
53
- test('applies a floor-plan pose while preserving the generic camera elevation and projection', () => {
54
- expect(
55
- floorplanNavigationPoseToCameraPose(
56
- {
57
- source: '2d',
58
- revision: 4,
59
- target: [10, 2, 20],
60
- azimuth: Math.PI / 2,
61
- viewWidth: 8,
62
- },
63
- cameraPose,
64
- ),
65
- ).toEqual({
66
- fov: 50,
67
- position: [15, 5, 20],
68
- projection: 'perspective',
69
- target: [10, 2, 20],
70
- viewWidth: 8,
71
- })
72
- })
73
-
74
- test('owns two-way synchronization without echoing tiny camera changes', () => {
75
- const published: Array<Omit<NavigationSyncPose, 'revision'>> = []
76
- const applied: CameraPose[] = []
77
- const bridge = createFloorplanCameraSyncBridge({
78
- applyCameraPose: (pose) => applied.push(pose),
79
- publishNavigationPose: (pose) => published.push(pose),
80
- })
81
-
82
- bridge.receiveCameraPose(cameraPose)
83
- bridge.receiveCameraPose({
84
- ...cameraPose,
85
- position: [3.0001, 4, 4],
86
- })
87
- bridge.receiveNavigationPose({
88
- source: '2d',
89
- revision: 1,
90
- target: [10, 2, 20],
91
- azimuth: Math.PI / 2,
92
- viewWidth: 8,
93
- })
94
-
95
- expect(published).toHaveLength(1)
96
- expect(published[0]?.source).toBe('3d')
97
- expect(applied).toHaveLength(1)
98
- expect(applied[0]).toMatchObject({
99
- fov: 50,
100
- projection: 'perspective',
101
- target: [10, 2, 20],
102
- viewWidth: 8,
103
- })
104
- expect(applied[0]?.position[0]).toBeCloseTo(15)
105
- expect(applied[0]?.position[1]).toBeCloseTo(5)
106
- expect(applied[0]?.position[2]).toBeCloseTo(20)
107
- })
108
-
109
- test('publishes sub-degree camera rotation for a real-time compass', () => {
110
- const published: Array<Omit<NavigationSyncPose, 'revision'>> = []
111
- const bridge = createFloorplanCameraSyncBridge({
112
- applyCameraPose: () => {},
113
- publishNavigationPose: (pose) => published.push(pose),
114
- })
115
-
116
- bridge.receiveCameraPose(cameraPoseAtAzimuth(0))
117
- bridge.receiveCameraPose(cameraPoseAtAzimuth((0.9 * Math.PI) / 180))
118
- expect(published).toHaveLength(2)
119
- expect(published[1]?.azimuth).toBeCloseTo((0.9 * Math.PI) / 180)
120
- })
121
-
122
- test('keeps a sub-degree camera rotation when it arrives with a pan', () => {
123
- const published: Array<Omit<NavigationSyncPose, 'revision'>> = []
124
- const bridge = createFloorplanCameraSyncBridge({
125
- applyCameraPose: () => {},
126
- publishNavigationPose: (pose) => published.push(pose),
127
- })
128
-
129
- bridge.receiveCameraPose(cameraPoseAtAzimuth(0))
130
- bridge.receiveCameraPose(cameraPoseAtAzimuth((0.5 * Math.PI) / 180, [1, 1, 0]))
131
-
132
- expect(published).toHaveLength(2)
133
- expect(published[1]).toMatchObject({
134
- target: [1, 1, 0],
135
- })
136
- expect(published[1]?.azimuth).toBeCloseTo((0.5 * Math.PI) / 180)
137
- })
138
-
139
- test('keeps streaming live camera headings in 3D-only mode', () => {
140
- const published: Array<Omit<NavigationSyncPose, 'revision'>> = []
141
- const applied: CameraPose[] = []
142
- const bridge = createFloorplanCameraSyncBridge({
143
- active: false,
144
- applyCameraPose: (pose) => applied.push(pose),
145
- publishNavigationPose: (pose) => published.push(pose),
146
- })
147
- const latestPose = cameraPoseAtAzimuth(Math.PI / 2, [8, 1, 4])
148
-
149
- bridge.receiveCameraPose(cameraPoseAtAzimuth(0))
150
- bridge.receiveCameraPose(latestPose)
151
- bridge.receiveNavigationPose({
152
- source: '2d',
153
- revision: 1,
154
- target: [10, 2, 20],
155
- azimuth: Math.PI / 2,
156
- viewWidth: 8,
157
- })
158
-
159
- expect(published).toEqual([
160
- {
161
- source: '3d',
162
- target: cameraPose.target,
163
- azimuth: 0,
164
- viewWidth: 12,
165
- },
166
- {
167
- source: '3d',
168
- target: latestPose.target,
169
- azimuth: Math.PI / 2,
170
- viewWidth: 12,
171
- },
172
- ])
173
- expect(applied).toEqual([])
174
-
175
- bridge.setActive(true)
176
- expect(published).toHaveLength(2)
177
- expect(applied).toEqual([])
178
- })
179
-
180
- test('applies the north-alignment pose used by the compass in 3D view', () => {
181
- const applied: CameraPose[] = []
182
- const bridge = createFloorplanCameraSyncBridge({
183
- applyCameraPose: (pose) => applied.push(pose),
184
- publishNavigationPose: () => {},
185
- })
186
- const currentPose = cameraPoseAtAzimuth(Math.PI / 2, [8, 1, 4])
187
-
188
- bridge.receiveCameraPose(currentPose)
189
- bridge.receiveNavigationPose({
190
- source: '2d',
191
- revision: 1,
192
- target: [...currentPose.target],
193
- azimuth: 0,
194
- viewWidth: currentPose.viewWidth!,
195
- })
196
-
197
- expect(applied).toHaveLength(1)
198
- expect(applied[0]).toMatchObject({
199
- target: currentPose.target,
200
- projection: currentPose.projection,
201
- viewWidth: currentPose.viewWidth,
202
- })
203
- expect(applied[0]?.position[0]).toBeCloseTo(currentPose.target[0])
204
- expect(applied[0]?.position[1]).toBeCloseTo(currentPose.position[1])
205
- expect(applied[0]?.position[2]).toBeCloseTo(currentPose.target[2] + 5)
206
- })
207
-
208
- test('applies 2D-first navigation on initial load without a 3D interaction', () => {
209
- const applied: CameraPose[] = []
210
- const bridge = createFloorplanCameraSyncBridge({
211
- applyCameraPose: (pose) => applied.push(pose),
212
- publishNavigationPose: () => {},
213
- })
214
-
215
- bridge.receiveNavigationPose({
216
- source: '2d',
217
- revision: 1,
218
- target: [10, 2, 20],
219
- azimuth: Math.PI / 2,
220
- viewWidth: 8,
221
- })
222
- expect(applied).toEqual([])
223
-
224
- publishInitialCameraPose(() => bridge.receiveCameraPose(cameraPose))
225
- expect(applied).toHaveLength(1)
226
- })
227
-
228
- test('delivers the live camera stream through transient subscribers', () => {
229
- const channel = createFloorplanCameraNavigationChannel()
230
- const received: NavigationSyncPose[] = []
231
- const unsubscribe = channel.subscribe((pose) => received.push(pose))
232
- const input = {
233
- source: '3d' as const,
234
- target: [0, 1, 2] as [number, number, number],
235
- azimuth: 0.5,
236
- viewWidth: 12,
237
- }
238
-
239
- channel.publish(input)
240
- channel.publish({ ...input, azimuth: 0.75 })
241
- unsubscribe()
242
- channel.publish({ ...input, azimuth: 1 })
243
-
244
- expect(received).toEqual([
245
- { ...input, revision: 1 },
246
- { ...input, azimuth: 0.75, revision: 2 },
247
- ])
248
- })
249
- })
@@ -1,213 +0,0 @@
1
- 'use client'
2
-
3
- import { type CameraPose, emitter } from '@pascal-app/core'
4
- import { useEffect, useRef } from 'react'
5
- import { subscribeCameraPose } from '../../store/camera-pose-store'
6
- import { subscribeNavigationSyncPose } from '../../store/navigation-sync-pose-store'
7
- import type { NavigationSyncPose, NavigationSyncPoseInput } from '../../store/use-editor'
8
-
9
- const POSITION_EPSILON = 0.001
10
- const AZIMUTH_EPSILON = 1e-4
11
- const VIEW_WIDTH_EPSILON = 0.001
12
-
13
- type FloorplanNavigationSnapshot = Omit<NavigationSyncPoseInput, 'source'>
14
-
15
- function angleDeltaRadians(a: number, b: number) {
16
- return Math.atan2(Math.sin(a - b), Math.cos(a - b))
17
- }
18
-
19
- function normalizeNearZero(value: number) {
20
- return Math.abs(value) < Number.EPSILON * 10 ? 0 : value
21
- }
22
-
23
- function navigationSnapshot(pose: NavigationSyncPoseInput): FloorplanNavigationSnapshot {
24
- return {
25
- target: [...pose.target],
26
- azimuth: pose.azimuth,
27
- viewWidth: pose.viewWidth,
28
- }
29
- }
30
-
31
- function navigationSnapshotsEqual(
32
- previous: FloorplanNavigationSnapshot,
33
- next: FloorplanNavigationSnapshot,
34
- ) {
35
- return (
36
- Math.abs(previous.target[0] - next.target[0]) < POSITION_EPSILON &&
37
- Math.abs(previous.target[1] - next.target[1]) < POSITION_EPSILON &&
38
- Math.abs(previous.target[2] - next.target[2]) < POSITION_EPSILON &&
39
- Math.abs(angleDeltaRadians(previous.azimuth, next.azimuth)) < AZIMUTH_EPSILON &&
40
- Math.abs(previous.viewWidth - next.viewWidth) < VIEW_WIDTH_EPSILON
41
- )
42
- }
43
-
44
- export function cameraPoseToFloorplanNavigationPose(
45
- pose: CameraPose,
46
- ): NavigationSyncPoseInput | null {
47
- if (!(pose.viewWidth !== undefined && Number.isFinite(pose.viewWidth) && pose.viewWidth > 0)) {
48
- return null
49
- }
50
-
51
- return {
52
- source: '3d',
53
- target: [...pose.target],
54
- azimuth: Math.atan2(pose.position[0] - pose.target[0], pose.position[2] - pose.target[2]),
55
- viewWidth: pose.viewWidth,
56
- }
57
- }
58
-
59
- export function floorplanNavigationPoseToCameraPose(
60
- navigationPose: NavigationSyncPose,
61
- cameraPose: CameraPose,
62
- ): CameraPose {
63
- const offsetX = cameraPose.position[0] - cameraPose.target[0]
64
- const offsetY = cameraPose.position[1] - cameraPose.target[1]
65
- const offsetZ = cameraPose.position[2] - cameraPose.target[2]
66
- const horizontalDistance = Math.hypot(offsetX, offsetZ)
67
- const horizontalX = normalizeNearZero(Math.sin(navigationPose.azimuth) * horizontalDistance)
68
- const horizontalZ = normalizeNearZero(Math.cos(navigationPose.azimuth) * horizontalDistance)
69
- const target: [number, number, number] = [...navigationPose.target]
70
-
71
- return {
72
- ...(cameraPose.fov === undefined ? {} : { fov: cameraPose.fov }),
73
- position: [target[0] + horizontalX, target[1] + offsetY, target[2] + horizontalZ],
74
- projection: cameraPose.projection,
75
- target,
76
- viewWidth: navigationPose.viewWidth,
77
- }
78
- }
79
-
80
- export type FloorplanCameraSyncBridge = {
81
- receiveCameraPose: (pose: CameraPose) => void
82
- receiveNavigationPose: (pose: NavigationSyncPose | null) => void
83
- setActive: (active: boolean) => void
84
- }
85
-
86
- export type FloorplanCameraNavigationChannel = {
87
- publish: (pose: NavigationSyncPoseInput) => void
88
- subscribe: (listener: (pose: NavigationSyncPose) => void) => () => void
89
- }
90
-
91
- export function createFloorplanCameraNavigationChannel(): FloorplanCameraNavigationChannel {
92
- const listeners = new Set<(pose: NavigationSyncPose) => void>()
93
- let revision = 0
94
-
95
- return {
96
- publish: (pose) => {
97
- revision += 1
98
- const revisedPose = { ...pose, revision }
99
- for (const listener of listeners) {
100
- listener(revisedPose)
101
- }
102
- },
103
- subscribe: (listener) => {
104
- listeners.add(listener)
105
- return () => listeners.delete(listener)
106
- },
107
- }
108
- }
109
-
110
- const liveCameraNavigation = createFloorplanCameraNavigationChannel()
111
-
112
- export function subscribeFloorplanCameraNavigation(listener: (pose: NavigationSyncPose) => void) {
113
- return liveCameraNavigation.subscribe(listener)
114
- }
115
-
116
- export function createFloorplanCameraSyncBridge({
117
- active: initialActive = true,
118
- applyCameraPose,
119
- publishNavigationPose,
120
- }: {
121
- active?: boolean
122
- applyCameraPose: (pose: CameraPose) => void
123
- publishNavigationPose: (pose: NavigationSyncPoseInput) => void
124
- }): FloorplanCameraSyncBridge {
125
- let active = initialActive
126
- let latestCameraPose: CameraPose | null = null
127
- let pendingNavigationPose: NavigationSyncPose | null = null
128
- let lastAppliedNavigationRevision = 0
129
- let lastPublishedNavigation: FloorplanNavigationSnapshot | null = null
130
-
131
- const applyPendingNavigationPose = () => {
132
- if (!(latestCameraPose && pendingNavigationPose)) return false
133
- if (pendingNavigationPose.revision === lastAppliedNavigationRevision) {
134
- pendingNavigationPose = null
135
- return false
136
- }
137
-
138
- const appliedPose = floorplanNavigationPoseToCameraPose(pendingNavigationPose, latestCameraPose)
139
- const appliedNavigationPose = cameraPoseToFloorplanNavigationPose(appliedPose)
140
- latestCameraPose = appliedPose
141
- lastAppliedNavigationRevision = pendingNavigationPose.revision
142
- pendingNavigationPose = null
143
- if (appliedNavigationPose) {
144
- lastPublishedNavigation = navigationSnapshot(appliedNavigationPose)
145
- }
146
- applyCameraPose(appliedPose)
147
- return true
148
- }
149
-
150
- const publishCameraNavigationPose = (pose: CameraPose) => {
151
- const navigationPose = cameraPoseToFloorplanNavigationPose(pose)
152
- if (!navigationPose) return
153
- const nextSnapshot = navigationSnapshot(navigationPose)
154
- if (
155
- lastPublishedNavigation &&
156
- navigationSnapshotsEqual(lastPublishedNavigation, nextSnapshot)
157
- ) {
158
- return
159
- }
160
-
161
- lastPublishedNavigation = nextSnapshot
162
- publishNavigationPose(navigationPose)
163
- }
164
-
165
- return {
166
- receiveCameraPose: (pose) => {
167
- latestCameraPose = pose
168
- if (active && applyPendingNavigationPose()) return
169
-
170
- publishCameraNavigationPose(pose)
171
- },
172
- receiveNavigationPose: (pose) => {
173
- if (
174
- !active ||
175
- pose?.source !== '2d' ||
176
- pose.revision === lastAppliedNavigationRevision ||
177
- pose.revision === pendingNavigationPose?.revision
178
- ) {
179
- return
180
- }
181
-
182
- pendingNavigationPose = pose
183
- applyPendingNavigationPose()
184
- },
185
- setActive: (nextActive) => {
186
- if (active === nextActive) return
187
- active = nextActive
188
- if (!active) {
189
- pendingNavigationPose = null
190
- return
191
- }
192
- if (latestCameraPose) publishCameraNavigationPose(latestCameraPose)
193
- },
194
- }
195
- }
196
-
197
- export function useFloorplanCameraSyncBridge() {
198
- // The compass is portalled into the always-visible viewer area, so its
199
- // orientation-only 2D pose must still reach the camera while the floorplan
200
- // itself is hidden in 3D view.
201
- const bridgeRef = useRef<FloorplanCameraSyncBridge | null>(null)
202
- if (!bridgeRef.current) {
203
- bridgeRef.current = createFloorplanCameraSyncBridge({
204
- applyCameraPose: (pose) => emitter.emit('camera-controls:apply-pose', pose),
205
- publishNavigationPose: (pose) => liveCameraNavigation.publish(pose),
206
- })
207
- }
208
- const bridge = bridgeRef.current
209
-
210
- useEffect(() => subscribeCameraPose(bridge.receiveCameraPose), [bridge])
211
-
212
- useEffect(() => subscribeNavigationSyncPose(bridge.receiveNavigationPose), [bridge])
213
- }
@@ -1,22 +0,0 @@
1
- 'use client'
2
-
3
- import { PascalI18nProvider, usePascalI18n } from '@pascal-app/core'
4
- import { Html as DreiHtml } from '@react-three/drei'
5
- import { type ComponentProps, forwardRef } from 'react'
6
-
7
- /**
8
- * drei's `Html`, keeping the editor's i18n scope. `Html` renders its children
9
- * in a separate React root, which sees none of the surrounding context, so a
10
- * component inside it would translate with the default (English) scope instead
11
- * of the editor's `?lang=` or `<Editor locale>` one.
12
- */
13
- export const Html = forwardRef<HTMLDivElement, Omit<ComponentProps<typeof DreiHtml>, 'ref'>>(
14
- function Html({ children, ...props }, ref) {
15
- const i18n = usePascalI18n()
16
- return (
17
- <DreiHtml {...props} ref={ref}>
18
- <PascalI18nProvider i18n={i18n}>{children}</PascalI18nProvider>
19
- </DreiHtml>
20
- )
21
- },
22
- )