@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,47 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import { LevelNode, WallNode } from '@pascal-app/core/schema'
3
- import { buildFloorplanContext } from './floorplan-readonly'
4
-
5
- const viewState = {
6
- selected: false,
7
- unit: 'metric' as const,
8
- highlighted: false,
9
- hovered: false,
10
- moving: false,
11
- palette: undefined,
12
- }
13
-
14
- describe('read-only floorplan context', () => {
15
- test('does not invent siblings for parentless nodes', () => {
16
- const first = LevelNode.parse({ id: 'level_first', type: 'level' })
17
- const second = LevelNode.parse({ id: 'level_second', type: 'level' })
18
- const nodes = { [first.id]: first, [second.id]: second }
19
-
20
- expect(buildFloorplanContext(first, nodes, viewState).siblings).toEqual([])
21
- })
22
-
23
- test('resolves same-kind siblings from the parent child order', () => {
24
- const first = WallNode.parse({
25
- id: 'wall_first',
26
- type: 'wall',
27
- parentId: 'level_ground',
28
- start: [0, 0],
29
- end: [1, 0],
30
- })
31
- const second = WallNode.parse({
32
- id: 'wall_second',
33
- type: 'wall',
34
- parentId: 'level_ground',
35
- start: [1, 0],
36
- end: [2, 0],
37
- })
38
- const level = LevelNode.parse({
39
- id: 'level_ground',
40
- type: 'level',
41
- children: [first.id, second.id],
42
- })
43
- const nodes = { [level.id]: level, [first.id]: first, [second.id]: second }
44
-
45
- expect(buildFloorplanContext(first, nodes, viewState).siblings).toEqual([second])
46
- })
47
- })
@@ -1,95 +0,0 @@
1
- import type { AnyNode, AnyNodeId, FloorplanPalette, GeometryContext } from '@pascal-app/core'
2
- import {
3
- createFloorplanContextExtensions,
4
- type FloorplanWallDimensionFaces,
5
- } from './floorplan-extension'
6
-
7
- export type FloorplanViewState = {
8
- automaticDimensions?: boolean
9
- selected: boolean
10
- unit: 'metric' | 'imperial'
11
- metricNotation?: 'meters' | 'millimeters'
12
- purpose?: 'edit' | 'document'
13
- /** Sheet drafting conventions (see `FloorplanContextExtension.drafting`). */
14
- drafting?: boolean
15
- /** Room labels drawn as screen pills (see `FloorplanContextExtension.roomLabelOverlay`). */
16
- roomLabelOverlay?: boolean
17
- wallDimensionFaces?: FloorplanWallDimensionFaces
18
- highlighted: boolean
19
- hovered: boolean
20
- moving: boolean
21
- focusedUnitId?: string
22
- focusedUnitMemberIds?: readonly string[]
23
- palette: FloorplanPalette | undefined
24
- }
25
-
26
- export function buildFloorplanContext(
27
- node: AnyNode,
28
- nodes: Record<string, AnyNode>,
29
- viewState: FloorplanViewState,
30
- levelData?: unknown,
31
- ): GeometryContext {
32
- const resolve = <N = AnyNode>(id: AnyNodeId): N | undefined => nodes[id] as N | undefined
33
- const childIds = (node as { children?: AnyNodeId[] }).children
34
- const children = Array.isArray(childIds)
35
- ? childIds.map((id) => nodes[id]).filter((child): child is AnyNode => child !== undefined)
36
- : []
37
- const parentId = node.parentId as AnyNodeId | null
38
- const parent = parentId ? (nodes[parentId] ?? null) : null
39
- let siblings: AnyNode[] = []
40
- if (parent) {
41
- const parentChildIds = (parent as { children?: AnyNodeId[] }).children
42
- siblings = Array.isArray(parentChildIds)
43
- ? parentChildIds
44
- .filter((id) => id !== node.id)
45
- .map((id) => nodes[id])
46
- .filter((sibling): sibling is AnyNode => sibling?.type === node.type)
47
- : Object.values(nodes).filter(
48
- (candidate) =>
49
- candidate.id !== node.id &&
50
- candidate.type === node.type &&
51
- candidate.parentId === node.parentId,
52
- )
53
- }
54
-
55
- return {
56
- resolve,
57
- children,
58
- siblings,
59
- parent,
60
- levelData,
61
- sceneNodes: nodes,
62
- extensions: createFloorplanContextExtensions({
63
- automaticDimensions: viewState.automaticDimensions,
64
- metricNotation: viewState.metricNotation ?? 'meters',
65
- purpose: viewState.purpose ?? 'edit',
66
- drafting: viewState.drafting === true,
67
- roomLabelOverlay: viewState.roomLabelOverlay === true,
68
- wallDimensionFaces: viewState.wallDimensionFaces,
69
- }),
70
- viewState: viewState.palette
71
- ? {
72
- selected: viewState.selected,
73
- unit: viewState.unit,
74
- highlighted: viewState.highlighted,
75
- hovered: viewState.hovered,
76
- moving: viewState.moving,
77
- focusedUnitId: viewState.focusedUnitId,
78
- focusedUnitMemberIds: viewState.focusedUnitMemberIds,
79
- palette: viewState.palette,
80
- }
81
- : undefined,
82
- }
83
- }
84
-
85
- export function floorplanLayerRank(type: string): number {
86
- switch (type) {
87
- case 'zone':
88
- return 0
89
- case 'slab':
90
- case 'ceiling':
91
- return 1
92
- default:
93
- return 2
94
- }
95
- }
@@ -1,213 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { AnyNode, AnyNodeId } from '@pascal-app/core'
3
- import { persistResolvedMarks, resolveMarkDetail, resolveMarks } from './marks'
4
-
5
- /**
6
- * Fixture: a rectangular level, walls running clockwise on screen from the
7
- * north-west corner.
8
- *
9
- * north [0,0] → [6,0] doors d_n1 @1, d_n2 @4
10
- * east [6,0] → [6,4] window w_e1 @2
11
- * south [6,4] → [0,4] door d_s1 @2
12
- * west [0,4] → [0,0] (interior partition, no openings)
13
- */
14
- function fixture(levelOrdinal = 0): Record<string, AnyNode> {
15
- const nodes: Record<string, AnyNode> = {}
16
- const node = (value: Record<string, unknown>) => {
17
- nodes[value.id as string] = {
18
- object: 'node',
19
- parentId: null,
20
- visible: true,
21
- metadata: {},
22
- children: [],
23
- ...value,
24
- } as unknown as AnyNode
25
- }
26
-
27
- node({
28
- id: 'level_1',
29
- type: 'level',
30
- level: levelOrdinal,
31
- children: ['w_n', 'w_e', 'w_s', 'w_w'],
32
- })
33
- node({
34
- id: 'w_n',
35
- type: 'wall',
36
- parentId: 'level_1',
37
- start: [0, 0],
38
- end: [6, 0],
39
- thickness: 0.15,
40
- frontSide: 'exterior',
41
- backSide: 'interior',
42
- children: ['d_n1', 'd_n2'],
43
- })
44
- node({
45
- id: 'w_e',
46
- type: 'wall',
47
- parentId: 'level_1',
48
- start: [6, 0],
49
- end: [6, 4],
50
- thickness: 0.15,
51
- frontSide: 'exterior',
52
- backSide: 'interior',
53
- children: ['win_e1'],
54
- })
55
- node({
56
- id: 'w_s',
57
- type: 'wall',
58
- parentId: 'level_1',
59
- start: [6, 4],
60
- end: [0, 4],
61
- thickness: 0.15,
62
- frontSide: 'exterior',
63
- backSide: 'interior',
64
- children: ['d_s1'],
65
- })
66
- node({
67
- id: 'w_w',
68
- type: 'wall',
69
- parentId: 'level_1',
70
- start: [0, 4],
71
- end: [0, 0],
72
- thickness: 0.1,
73
- frontSide: 'interior',
74
- backSide: 'interior',
75
- children: [],
76
- })
77
-
78
- const door = (id: string, wallId: string, along: number) =>
79
- node({
80
- id,
81
- type: 'door',
82
- parentId: wallId,
83
- wallId,
84
- width: 0.9,
85
- height: 2.1,
86
- position: [along, 1.05, 0],
87
- })
88
- door('d_n1', 'w_n', 1)
89
- door('d_n2', 'w_n', 4)
90
- door('d_s1', 'w_s', 2)
91
- node({
92
- id: 'win_e1',
93
- type: 'window',
94
- parentId: 'w_e',
95
- wallId: 'w_e',
96
- width: 1.2,
97
- height: 1.2,
98
- position: [2, 1.1, 0],
99
- })
100
- return nodes
101
- }
102
-
103
- describe('resolveMarks', () => {
104
- test('doors are D101…, windows W101… on level 0', () => {
105
- const marks = resolveMarks(fixture(), 'level_1' as AnyNodeId)
106
- expect(marks.get('d_n1')).toBe('D101')
107
- expect(marks.get('d_n2')).toBe('D102')
108
- expect(marks.get('d_s1')).toBe('D103')
109
- expect(marks.get('win_e1')).toBe('W101')
110
- })
111
-
112
- test('level ordinal shifts the base by 100', () => {
113
- const marks = resolveMarks(fixture(1), 'level_1' as AnyNodeId)
114
- expect(marks.get('d_n1')).toBe('D201')
115
- expect(marks.get('win_e1')).toBe('W201')
116
- })
117
-
118
- test('openings are ordered clockwise from the north-west, exterior walls first', () => {
119
- const nodes = fixture()
120
- // Move the west partition's opening onto it to prove interior comes last.
121
- ;(nodes.w_w as unknown as { children: string[] }).children = ['d_w1']
122
- nodes.d_w1 = {
123
- object: 'node',
124
- id: 'd_w1',
125
- type: 'door',
126
- parentId: 'w_w',
127
- wallId: 'w_w',
128
- visible: true,
129
- metadata: {},
130
- width: 0.8,
131
- height: 2.1,
132
- position: [2, 1.05, 0],
133
- children: [],
134
- } as unknown as AnyNode
135
- const marks = resolveMarks(nodes, 'level_1' as AnyNodeId)
136
- // North (exterior) first, then the other exterior walls, interior last.
137
- expect(marks.get('d_w1')).toBe('D104')
138
- })
139
-
140
- test('an explicit mark wins and is never reassigned', () => {
141
- const nodes = fixture()
142
- ;(nodes.d_n2 as unknown as { mark: string }).mark = 'D-GARAGE'
143
- const marks = resolveMarks(nodes, 'level_1' as AnyNodeId)
144
- expect(marks.get('d_n2')).toBe('D-GARAGE')
145
- expect(marks.get('d_n1')).toBe('D101')
146
- expect(marks.get('d_s1')).toBe('D102')
147
- })
148
-
149
- test('duplicate explicit marks are reported, not silently renumbered', () => {
150
- const nodes = fixture()
151
- ;(nodes.d_n1 as unknown as { mark: string }).mark = 'D101'
152
- ;(nodes.d_n2 as unknown as { mark: string }).mark = 'D101'
153
- const resolution = resolveMarkDetail(nodes, 'level_1' as AnyNodeId)
154
- expect(resolution.issues).toEqual(['Duplicate door mark D101 (2 instances)'])
155
- })
156
-
157
- test('numbering is stable under insertion once persisted', () => {
158
- const nodes = fixture()
159
- const first = resolveMarkDetail(nodes, 'level_1' as AnyNodeId)
160
- expect([...first.assignments.values()]).toEqual(['D101', 'D102', 'D103', 'W101'])
161
-
162
- // Persist, the way an edit session does the first time it resolves.
163
- const written = persistResolvedMarks(first, (id, data) => {
164
- ;(nodes[id] as unknown as Record<string, unknown>).mark = data.mark
165
- })
166
- expect(written).toBe(4)
167
-
168
- // Insert a door BETWEEN d_n1 and d_n2 on the same wall.
169
- ;(nodes.w_n as unknown as { children: string[] }).children = ['d_n1', 'd_new', 'd_n2']
170
- nodes.d_new = {
171
- object: 'node',
172
- id: 'd_new',
173
- type: 'door',
174
- parentId: 'w_n',
175
- wallId: 'w_n',
176
- visible: true,
177
- metadata: {},
178
- width: 0.8,
179
- height: 2.1,
180
- position: [2.5, 1.05, 0],
181
- children: [],
182
- } as unknown as AnyNode
183
-
184
- const second = resolveMarkDetail(nodes, 'level_1' as AnyNodeId)
185
- // Existing doors keep their marks; the new one takes the next free number.
186
- expect(second.marks.get('d_n1')).toBe('D101')
187
- expect(second.marks.get('d_n2')).toBe('D102')
188
- expect(second.marks.get('d_s1')).toBe('D103')
189
- expect(second.marks.get('d_new')).toBe('D104')
190
- expect([...second.assignments.keys()]).toEqual(['d_new'])
191
- })
192
-
193
- test('WITHOUT persisting, inserting mid-wall renumbers — which is why we persist', () => {
194
- const nodes = fixture()
195
- ;(nodes.w_n as unknown as { children: string[] }).children = ['d_n1', 'd_new', 'd_n2']
196
- nodes.d_new = {
197
- object: 'node',
198
- id: 'd_new',
199
- type: 'door',
200
- parentId: 'w_n',
201
- wallId: 'w_n',
202
- visible: true,
203
- metadata: {},
204
- width: 0.8,
205
- height: 2.1,
206
- position: [2.5, 1.05, 0],
207
- children: [],
208
- } as unknown as AnyNode
209
- const marks = resolveMarks(nodes, 'level_1' as AnyNodeId)
210
- expect(marks.get('d_new')).toBe('D102')
211
- expect(marks.get('d_n2')).toBe('D103')
212
- })
213
- })
@@ -1,238 +0,0 @@
1
- import type {
2
- AnyNode,
3
- AnyNodeId,
4
- DoorNode,
5
- LevelNode,
6
- WallNode,
7
- WindowNode,
8
- } from '@pascal-app/core'
9
-
10
- /**
11
- * Deterministic door / window marks per level — WS3.
12
- *
13
- * `packages/core/src/schema/nodes/door.ts:75-79` promises "the deterministic
14
- * level fallback (101, 102, ...)"; this is the implementation.
15
- *
16
- * Numbering
17
- * ---------
18
- * base = (level.level + 1) * 100 — level 0 → 100, level 1 → 200
19
- * doors = `D${base + n}` → D101, D102 …
20
- * windows= `W${base + n}` → W101, W102 …
21
- *
22
- * Order
23
- * -----
24
- * Openings are visited **clockwise** around the level, starting from the
25
- * north-west-most exterior wall, exterior walls first and interior walls
26
- * after. Clockwise is the ascending-angle sweep of the wall midpoint about
27
- * the level centroid, anchored on the north-west diagonal (plan axes are
28
- * x east, z south — z grows down the screen — so a screen-clockwise sweep
29
- * is increasing atan2(z, x)). Openings on the same wall are ordered along
30
- * the wall direction.
31
- *
32
- * Stability
33
- * ---------
34
- * An explicit `mark` on the node always wins and is never reassigned.
35
- * `resolveMarks` also reports, in `assignments`, the marks it invented for
36
- * nodes that had none — the caller is expected to PERSIST those back onto
37
- * `node.mark` the first time they are resolved in an edit session
38
- * (`persistResolvedMarks`). That is what makes numbering stable under
39
- * insertion: once written, an existing opening keeps its number forever and
40
- * a newly inserted opening only ever takes the next free number. Without
41
- * the write-back, inserting a door in the middle of a wall would silently
42
- * renumber every door after it.
43
- */
44
-
45
- export type OpeningMarkKind = 'door' | 'window'
46
-
47
- export type MarkResolution = {
48
- /** Every door and window on the level → its mark. */
49
- marks: ReadonlyMap<string, string>
50
- /** Only the marks that were invented (node.mark was empty). */
51
- assignments: ReadonlyMap<string, string>
52
- /** Duplicate explicit marks and other reportable defects. */
53
- issues: readonly string[]
54
- }
55
-
56
- export type MarkSceneInput = {
57
- nodes: Readonly<Record<string, AnyNode>>
58
- }
59
-
60
- /**
61
- * Resolve marks for every door and window under `levelId`.
62
- *
63
- * `scene` may be a `{ nodes }` snapshot or the raw node map.
64
- */
65
- export function resolveMarks(
66
- scene: MarkSceneInput | Readonly<Record<string, AnyNode>>,
67
- levelId: AnyNodeId,
68
- ): Map<string, string> {
69
- return new Map(resolveMarkDetail(scene, levelId).marks)
70
- }
71
-
72
- export function resolveMarkDetail(
73
- scene: MarkSceneInput | Readonly<Record<string, AnyNode>>,
74
- levelId: AnyNodeId,
75
- ): MarkResolution {
76
- const nodes = normalizeNodes(scene)
77
- const level = nodes[levelId]
78
- const ordinal = level && level.type === 'level' ? ((level as LevelNode).level ?? 0) : 0
79
- const base = (Math.max(0, ordinal) + 1) * 100
80
-
81
- const openings = orderedOpenings(nodes, levelId)
82
- const marks = new Map<string, string>()
83
- const assignments = new Map<string, string>()
84
- const issues: string[] = []
85
-
86
- for (const kind of ['door', 'window'] as const) {
87
- const prefix = kind === 'door' ? 'D' : 'W'
88
- const list = openings.filter((opening) => opening.type === kind)
89
- const used = new Set<string>()
90
- const explicitOwners = new Map<string, string[]>()
91
-
92
- for (const opening of list) {
93
- const explicit = opening.mark?.trim()
94
- if (!explicit) continue
95
- marks.set(opening.id, explicit)
96
- const normalized = explicit.toLocaleUpperCase()
97
- used.add(normalized)
98
- const owners = explicitOwners.get(normalized)
99
- if (owners) owners.push(opening.id)
100
- else explicitOwners.set(normalized, [opening.id])
101
- }
102
-
103
- let sequence = 1
104
- for (const opening of list) {
105
- if (marks.has(opening.id)) continue
106
- let candidate = `${prefix}${base + sequence}`
107
- while (used.has(candidate.toLocaleUpperCase())) {
108
- sequence++
109
- candidate = `${prefix}${base + sequence}`
110
- }
111
- marks.set(opening.id, candidate)
112
- assignments.set(opening.id, candidate)
113
- used.add(candidate.toLocaleUpperCase())
114
- sequence++
115
- }
116
-
117
- for (const [mark, owners] of explicitOwners) {
118
- if (owners.length > 1) {
119
- issues.push(`Duplicate ${kind} mark ${mark} (${owners.length} instances)`)
120
- }
121
- }
122
- }
123
-
124
- return { marks, assignments, issues }
125
- }
126
-
127
- /**
128
- * Write invented marks back onto the nodes so numbering never drifts.
129
- *
130
- * Call once per level per edit session (the caller decides when — this
131
- * module stays pure). `update` is typically `sceneApi.update`.
132
- */
133
- export function persistResolvedMarks(
134
- resolution: MarkResolution,
135
- update: (id: AnyNodeId, data: Record<string, unknown>) => void,
136
- ): number {
137
- let written = 0
138
- for (const [id, mark] of resolution.assignments) {
139
- update(id as AnyNodeId, { mark })
140
- written++
141
- }
142
- return written
143
- }
144
-
145
- // ── Ordering ─────────────────────────────────────────────────────────
146
-
147
- type Opening = (DoorNode | WindowNode) & { type: 'door' | 'window' }
148
-
149
- /**
150
- * Every door and window under the level, clockwise from the NW-most
151
- * exterior wall, exterior walls first.
152
- */
153
- export function orderedOpenings(
154
- nodes: Readonly<Record<string, AnyNode>>,
155
- levelId: AnyNodeId,
156
- ): Opening[] {
157
- const walls: WallNode[] = []
158
- const orphanOpenings: Opening[] = []
159
-
160
- const visit = (id: string) => {
161
- const node = nodes[id]
162
- if (!node) return
163
- if (node.visible === false) return
164
- if (node.type === 'wall') walls.push(node as WallNode)
165
- else if (node.type === 'door' || node.type === 'window') {
166
- const opening = node as Opening
167
- if (!opening.wallId || !nodes[opening.wallId]) orphanOpenings.push(opening)
168
- }
169
- for (const childId of (node as { children?: string[] }).children ?? []) visit(childId)
170
- }
171
- visit(levelId)
172
-
173
- const centroid = wallCentroid(walls)
174
- const ordered = [...walls].sort((left, right) => {
175
- const leftExterior = isExteriorWall(left) ? 0 : 1
176
- const rightExterior = isExteriorWall(right) ? 0 : 1
177
- if (leftExterior !== rightExterior) return leftExterior - rightExterior
178
- return clockwiseKey(left, centroid) - clockwiseKey(right, centroid)
179
- })
180
-
181
- const result: Opening[] = []
182
- for (const wall of ordered) {
183
- const wallOpenings: Opening[] = []
184
- for (const childId of wall.children ?? []) {
185
- const child = nodes[childId]
186
- if (child && child.visible !== false && (child.type === 'door' || child.type === 'window')) {
187
- wallOpenings.push(child as Opening)
188
- }
189
- }
190
- wallOpenings.sort((left, right) => left.position[0] - right.position[0])
191
- result.push(...wallOpenings)
192
- }
193
- result.push(...orphanOpenings)
194
- return result
195
- }
196
-
197
- function wallCentroid(walls: readonly WallNode[]): [number, number] {
198
- if (walls.length === 0) return [0, 0]
199
- let x = 0
200
- let z = 0
201
- for (const wall of walls) {
202
- x += (wall.start[0] + wall.end[0]) / 2
203
- z += (wall.start[1] + wall.end[1]) / 2
204
- }
205
- return [x / walls.length, z / walls.length]
206
- }
207
-
208
- /**
209
- * Sweep key: 0 at the north-west direction, increasing clockwise on screen.
210
- *
211
- * Plan axes are x east, z south — z grows DOWN the screen — so a clockwise
212
- * screen sweep (up → right → down → left) is the INCREASING atan2(z, x)
213
- * direction. The north-west diagonal sits at atan2(-1, -1) = -3π/4, so
214
- * anchoring there and wrapping into [0, 2π) yields north, east, south, west.
215
- */
216
- const NORTH_WEST_ANGLE = (-3 * Math.PI) / 4
217
-
218
- function clockwiseKey(wall: WallNode, centroid: readonly [number, number]): number {
219
- const midX = (wall.start[0] + wall.end[0]) / 2 - centroid[0]
220
- const midZ = (wall.start[1] + wall.end[1]) / 2 - centroid[1]
221
- let sweep = Math.atan2(midZ, midX) - NORTH_WEST_ANGLE
222
- while (sweep < 0) sweep += Math.PI * 2
223
- while (sweep >= Math.PI * 2) sweep -= Math.PI * 2
224
- return sweep
225
- }
226
-
227
- function isExteriorWall(wall: WallNode): boolean {
228
- return wall.frontSide === 'exterior' || wall.backSide === 'exterior'
229
- }
230
-
231
- function normalizeNodes(
232
- scene: MarkSceneInput | Readonly<Record<string, AnyNode>>,
233
- ): Readonly<Record<string, AnyNode>> {
234
- const candidate = scene as MarkSceneInput
235
- return candidate && typeof candidate === 'object' && 'nodes' in candidate
236
- ? candidate.nodes
237
- : (scene as Readonly<Record<string, AnyNode>>)
238
- }
@@ -1,68 +0,0 @@
1
- import { type MeasurementPoint, measurementCentroid, measurementNormal } from '@pascal-app/core'
2
- import { ShapeUtils, Vector2, Vector3 } from 'three'
3
-
4
- type MeasurementPolygonProjection = {
5
- contour: Vector2[]
6
- triangles: number[][]
7
- }
8
-
9
- function projectMeasurementPolygon(
10
- points: readonly MeasurementPoint[],
11
- ): MeasurementPolygonProjection | null {
12
- const normalValue = measurementNormal(points)
13
- const originValue = points[0]
14
- if (!(normalValue && originValue)) return null
15
-
16
- const normal = new Vector3(...normalValue)
17
- const reference = Math.abs(normal.y) < 0.9 ? new Vector3(0, 1, 0) : new Vector3(1, 0, 0)
18
- const tangent = new Vector3().crossVectors(reference, normal).normalize()
19
- const bitangent = new Vector3().crossVectors(normal, tangent).normalize()
20
- const origin = new Vector3(...originValue)
21
- const contour = points.map((point) => {
22
- const relative = new Vector3(...point).sub(origin)
23
- return new Vector2(relative.dot(tangent), relative.dot(bitangent))
24
- })
25
-
26
- return { contour, triangles: ShapeUtils.triangulateShape(contour, []) }
27
- }
28
-
29
- export function triangulateMeasurementPolygon(points: readonly MeasurementPoint[]): number[][] {
30
- return projectMeasurementPolygon(points)?.triangles ?? []
31
- }
32
-
33
- export function measurementPolygonLabelAnchor(
34
- points: readonly MeasurementPoint[],
35
- ): MeasurementPoint | null {
36
- const projection = projectMeasurementPolygon(points)
37
- if (!projection || projection.triangles.length === 0) return measurementCentroid(points)
38
-
39
- let largestTriangle: number[] | null = null
40
- let largestArea = Number.NEGATIVE_INFINITY
41
- for (const triangle of projection.triangles) {
42
- const [firstIndex, secondIndex, thirdIndex] = triangle
43
- const first = firstIndex === undefined ? null : projection.contour[firstIndex]
44
- const second = secondIndex === undefined ? null : projection.contour[secondIndex]
45
- const third = thirdIndex === undefined ? null : projection.contour[thirdIndex]
46
- if (!(first && second && third)) continue
47
- const area = Math.abs(
48
- (second.x - first.x) * (third.y - first.y) - (second.y - first.y) * (third.x - first.x),
49
- )
50
- if (area > largestArea) {
51
- largestArea = area
52
- largestTriangle = triangle
53
- }
54
- }
55
-
56
- if (!largestTriangle) return measurementCentroid(points)
57
- const [firstIndex, secondIndex, thirdIndex] = largestTriangle
58
- const first = firstIndex === undefined ? null : points[firstIndex]
59
- const second = secondIndex === undefined ? null : points[secondIndex]
60
- const third = thirdIndex === undefined ? null : points[thirdIndex]
61
- if (!(first && second && third)) return measurementCentroid(points)
62
-
63
- return [
64
- (first[0] + second[0] + third[0]) / 3,
65
- (first[1] + second[1] + third[1]) / 3,
66
- (first[2] + second[2] + third[2]) / 3,
67
- ]
68
- }