@kerros/editor 0.0.0-stage → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -2
  3. package/dist/SitePlanner.js +1189 -0
  4. package/dist/SitePlanner.js.map +1 -0
  5. package/dist/adapters/basemap.js +11 -0
  6. package/dist/adapters/basemap.js.map +1 -0
  7. package/dist/adapters/persistence.js +121 -0
  8. package/dist/adapters/persistence.js.map +1 -0
  9. package/dist/adapters/status.js +11 -0
  10. package/dist/adapters/status.js.map +1 -0
  11. package/dist/adapters/useProjectPersistence.js +56 -0
  12. package/dist/adapters/useProjectPersistence.js.map +1 -0
  13. package/dist/components/AiImportPanel.js +635 -0
  14. package/dist/components/AiImportPanel.js.map +1 -0
  15. package/dist/components/AnalysisPreview.js +179 -0
  16. package/dist/components/AnalysisPreview.js.map +1 -0
  17. package/dist/components/ElevatorPanel.js +120 -0
  18. package/dist/components/ElevatorPanel.js.map +1 -0
  19. package/dist/components/Icons.js +57 -0
  20. package/dist/components/Icons.js.map +1 -0
  21. package/dist/components/ImportDialog.js +467 -0
  22. package/dist/components/ImportDialog.js.map +1 -0
  23. package/dist/components/Inspector.js +1184 -0
  24. package/dist/components/Inspector.js.map +1 -0
  25. package/dist/components/NavigatePanel.js +301 -0
  26. package/dist/components/NavigatePanel.js.map +1 -0
  27. package/dist/components/NavigationGraph.js +234 -0
  28. package/dist/components/NavigationGraph.js.map +1 -0
  29. package/dist/components/StructureExplorer.js +510 -0
  30. package/dist/components/StructureExplorer.js.map +1 -0
  31. package/dist/components/StructureView.js +286 -0
  32. package/dist/components/StructureView.js.map +1 -0
  33. package/dist/components/controls.js +209 -0
  34. package/dist/components/controls.js.map +1 -0
  35. package/dist/components/graphLayout.js +27 -0
  36. package/dist/components/graphLayout.js.map +1 -0
  37. package/dist/host.js +175 -0
  38. package/dist/host.js.map +1 -0
  39. package/dist/i18n.js +88 -0
  40. package/dist/i18n.js.map +1 -0
  41. package/dist/import/analysisPreview.js +41 -0
  42. package/dist/import/analysisPreview.js.map +1 -0
  43. package/dist/import/brief.js +29 -0
  44. package/dist/import/brief.js.map +1 -0
  45. package/dist/import/checkpoint.js +50 -0
  46. package/dist/import/checkpoint.js.map +1 -0
  47. package/dist/import/detect.js +120 -0
  48. package/dist/import/detect.js.map +1 -0
  49. package/dist/import/documentSvg.js +49 -0
  50. package/dist/import/documentSvg.js.map +1 -0
  51. package/dist/import/instructions.js +20 -0
  52. package/dist/import/instructions.js.map +1 -0
  53. package/dist/import/openings.js +61 -0
  54. package/dist/import/openings.js.map +1 -0
  55. package/dist/import/planImport.js +233 -0
  56. package/dist/import/planImport.js.map +1 -0
  57. package/dist/import/register.js +47 -0
  58. package/dist/import/register.js.map +1 -0
  59. package/dist/import/runs.js +67 -0
  60. package/dist/import/runs.js.map +1 -0
  61. package/dist/import/session.js +23 -0
  62. package/dist/import/session.js.map +1 -0
  63. package/dist/import/types.js +13 -0
  64. package/dist/import/types.js.map +1 -0
  65. package/dist/import/usage.js +29 -0
  66. package/dist/import/usage.js.map +1 -0
  67. package/dist/import/walls.js +112 -0
  68. package/dist/import/walls.js.map +1 -0
  69. package/dist/index.js +178 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/map/CabinMaterials.js +81 -0
  72. package/dist/map/CabinMaterials.js.map +1 -0
  73. package/dist/map/CabinMirror.js +43 -0
  74. package/dist/map/CabinMirror.js.map +1 -0
  75. package/dist/map/Doors.js +80 -0
  76. package/dist/map/Doors.js.map +1 -0
  77. package/dist/map/ElevatorCabin.js +93 -0
  78. package/dist/map/ElevatorCabin.js.map +1 -0
  79. package/dist/map/FixtureLights.js +117 -0
  80. package/dist/map/FixtureLights.js.map +1 -0
  81. package/dist/map/MapCanvas.js +2161 -0
  82. package/dist/map/MapCanvas.js.map +1 -0
  83. package/dist/map/Passenger.js +69 -0
  84. package/dist/map/Passenger.js.map +1 -0
  85. package/dist/map/SceneLayer.js +1534 -0
  86. package/dist/map/SceneLayer.js.map +1 -0
  87. package/dist/map/UndergroundContext.js +153 -0
  88. package/dist/map/UndergroundContext.js.map +1 -0
  89. package/dist/map/ambience.js +194 -0
  90. package/dist/map/ambience.js.map +1 -0
  91. package/dist/map/architecture.js +305 -0
  92. package/dist/map/architecture.js.map +1 -0
  93. package/dist/map/bathMusic.js +92 -0
  94. package/dist/map/bathMusic.js.map +1 -0
  95. package/dist/map/cameraAim.js +33 -0
  96. package/dist/map/cameraAim.js.map +1 -0
  97. package/dist/map/elevators.js +56 -0
  98. package/dist/map/elevators.js.map +1 -0
  99. package/dist/map/excavation.js +52 -0
  100. package/dist/map/excavation.js.map +1 -0
  101. package/dist/map/exteriors.js +25 -0
  102. package/dist/map/exteriors.js.map +1 -0
  103. package/dist/map/features.js +218 -0
  104. package/dist/map/features.js.map +1 -0
  105. package/dist/map/fences.js +29 -0
  106. package/dist/map/fences.js.map +1 -0
  107. package/dist/map/journey.js +112 -0
  108. package/dist/map/journey.js.map +1 -0
  109. package/dist/map/levels.js +6 -0
  110. package/dist/map/levels.js.map +1 -0
  111. package/dist/map/lighting.js +143 -0
  112. package/dist/map/lighting.js.map +1 -0
  113. package/dist/map/loadBasemap.js +26 -0
  114. package/dist/map/loadBasemap.js.map +1 -0
  115. package/dist/map/materials.js +183 -0
  116. package/dist/map/materials.js.map +1 -0
  117. package/dist/map/poolLighting.js +39 -0
  118. package/dist/map/poolLighting.js.map +1 -0
  119. package/dist/map/projection.js +24 -0
  120. package/dist/map/projection.js.map +1 -0
  121. package/dist/map/rampGeometry.js +38 -0
  122. package/dist/map/rampGeometry.js.map +1 -0
  123. package/dist/map/route.js +107 -0
  124. package/dist/map/route.js.map +1 -0
  125. package/dist/map/stairSurfaces.js +115 -0
  126. package/dist/map/stairSurfaces.js.map +1 -0
  127. package/dist/map/surfaces.js +136 -0
  128. package/dist/map/surfaces.js.map +1 -0
  129. package/dist/map/textures.js +95 -0
  130. package/dist/map/textures.js.map +1 -0
  131. package/dist/map/underground.js +34 -0
  132. package/dist/map/underground.js.map +1 -0
  133. package/dist/map/walk.js +433 -0
  134. package/dist/map/walk.js.map +1 -0
  135. package/dist/map/walkContext.js +46 -0
  136. package/dist/map/walkContext.js.map +1 -0
  137. package/dist/map/walkDimensions.js +7 -0
  138. package/dist/map/walkDimensions.js.map +1 -0
  139. package/dist/map/walkJourney.js +85 -0
  140. package/dist/map/walkJourney.js.map +1 -0
  141. package/dist/map/walkSurfaces.js +106 -0
  142. package/dist/map/walkSurfaces.js.map +1 -0
  143. package/dist/map/wallJoins.js +70 -0
  144. package/dist/map/wallJoins.js.map +1 -0
  145. package/dist/map/water.js +274 -0
  146. package/dist/map/water.js.map +1 -0
  147. package/dist/model/authoring.js +99 -0
  148. package/dist/model/authoring.js.map +1 -0
  149. package/dist/model/boundaries.js +548 -0
  150. package/dist/model/boundaries.js.map +1 -0
  151. package/dist/model/coverage.js +30 -0
  152. package/dist/model/coverage.js.map +1 -0
  153. package/dist/model/document.js +52 -0
  154. package/dist/model/document.js.map +1 -0
  155. package/dist/model/doors.js +47 -0
  156. package/dist/model/doors.js.map +1 -0
  157. package/dist/model/factory.js +56 -0
  158. package/dist/model/factory.js.map +1 -0
  159. package/dist/model/geometry.js +454 -0
  160. package/dist/model/geometry.js.map +1 -0
  161. package/dist/model/history.js +29 -0
  162. package/dist/model/history.js.map +1 -0
  163. package/dist/model/imports.js +45 -0
  164. package/dist/model/imports.js.map +1 -0
  165. package/dist/model/inference.js +182 -0
  166. package/dist/model/inference.js.map +1 -0
  167. package/dist/model/materials.js +11 -0
  168. package/dist/model/materials.js.map +1 -0
  169. package/dist/model/mutations.js +157 -0
  170. package/dist/model/mutations.js.map +1 -0
  171. package/dist/model/navigation.js +275 -0
  172. package/dist/model/navigation.js.map +1 -0
  173. package/dist/model/ontology.js +109 -0
  174. package/dist/model/ontology.js.map +1 -0
  175. package/dist/model/passages.js +5 -0
  176. package/dist/model/passages.js.map +1 -0
  177. package/dist/model/portals.js +42 -0
  178. package/dist/model/portals.js.map +1 -0
  179. package/dist/model/precision.js +11 -0
  180. package/dist/model/precision.js.map +1 -0
  181. package/dist/model/project.js +31 -0
  182. package/dist/model/project.js.map +1 -0
  183. package/dist/model/ramps.js +10 -0
  184. package/dist/model/ramps.js.map +1 -0
  185. package/dist/model/roomNavigation.js +87 -0
  186. package/dist/model/roomNavigation.js.map +1 -0
  187. package/dist/model/routeClearance.js +72 -0
  188. package/dist/model/routeClearance.js.map +1 -0
  189. package/dist/model/spaces.js +104 -0
  190. package/dist/model/spaces.js.map +1 -0
  191. package/dist/model/stairGeometry.js +40 -0
  192. package/dist/model/stairGeometry.js.map +1 -0
  193. package/dist/model/stairTraversal.js +48 -0
  194. package/dist/model/stairTraversal.js.map +1 -0
  195. package/dist/model/topology.js +63 -0
  196. package/dist/model/topology.js.map +1 -0
  197. package/dist/model/types.js +35 -0
  198. package/dist/model/types.js.map +1 -0
  199. package/dist/model/validate.js +240 -0
  200. package/dist/model/validate.js.map +1 -0
  201. package/dist/model/vertical.js +129 -0
  202. package/dist/model/vertical.js.map +1 -0
  203. package/dist/model/walls.js +261 -0
  204. package/dist/model/walls.js.map +1 -0
  205. package/dist/planner/DrawingDock.js +231 -0
  206. package/dist/planner/DrawingDock.js.map +1 -0
  207. package/dist/planner/FloorControls.js +93 -0
  208. package/dist/planner/FloorControls.js.map +1 -0
  209. package/dist/planner/MapSettings.js +143 -0
  210. package/dist/planner/MapSettings.js.map +1 -0
  211. package/dist/planner/PlannerHelp.js +57 -0
  212. package/dist/planner/PlannerHelp.js.map +1 -0
  213. package/dist/planner/ProjectSidebar.js +246 -0
  214. package/dist/planner/ProjectSidebar.js.map +1 -0
  215. package/dist/planner/floors.js +18 -0
  216. package/dist/planner/floors.js.map +1 -0
  217. package/dist/planner/useDrawingTools.js +341 -0
  218. package/dist/planner/useDrawingTools.js.map +1 -0
  219. package/dist/planner/useMapDisplay.js +51 -0
  220. package/dist/planner/useMapDisplay.js.map +1 -0
  221. package/dist/planner/usePlannerKeyboard.js +138 -0
  222. package/dist/planner/usePlannerKeyboard.js.map +1 -0
  223. package/dist/planner/useReferenceAlignment.js +50 -0
  224. package/dist/planner/useReferenceAlignment.js.map +1 -0
  225. package/dist/styles.css +4931 -0
  226. package/dist/theme/index.js +34 -0
  227. package/dist/theme/index.js.map +1 -0
  228. package/dist/types/SitePlanner.d.ts +5 -0
  229. package/dist/types/adapters/basemap.d.ts +2 -0
  230. package/dist/types/adapters/persistence.d.ts +29 -0
  231. package/dist/types/adapters/status.d.ts +4 -0
  232. package/dist/types/adapters/useProjectPersistence.d.ts +13 -0
  233. package/dist/types/components/AiImportPanel.d.ts +9 -0
  234. package/dist/types/components/AnalysisPreview.d.ts +4 -0
  235. package/dist/types/components/ElevatorPanel.d.ts +18 -0
  236. package/dist/types/components/Icons.d.ts +8 -0
  237. package/dist/types/components/ImportDialog.d.ts +42 -0
  238. package/dist/types/components/Inspector.d.ts +38 -0
  239. package/dist/types/components/NavigatePanel.d.ts +27 -0
  240. package/dist/types/components/NavigationGraph.d.ts +10 -0
  241. package/dist/types/components/StructureExplorer.d.ts +8 -0
  242. package/dist/types/components/StructureView.d.ts +22 -0
  243. package/dist/types/components/controls.d.ts +52 -0
  244. package/dist/types/components/graphLayout.d.ts +9 -0
  245. package/dist/types/editor/host.d.ts +9 -0
  246. package/dist/types/editor/index.d.ts +2 -0
  247. package/dist/types/i18n.d.ts +85 -0
  248. package/dist/types/import/analysisPreview.d.ts +27 -0
  249. package/dist/types/import/brief.d.ts +22 -0
  250. package/dist/types/import/checkpoint.d.ts +49 -0
  251. package/dist/types/import/detect.d.ts +34 -0
  252. package/dist/types/import/documentSvg.d.ts +2 -0
  253. package/dist/types/import/index.d.ts +10 -0
  254. package/dist/types/import/instructions.d.ts +7 -0
  255. package/dist/types/import/openings.d.ts +22 -0
  256. package/dist/types/import/planImport.d.ts +22 -0
  257. package/dist/types/import/register.d.ts +25 -0
  258. package/dist/types/import/runs.d.ts +31 -0
  259. package/dist/types/import/session.d.ts +33 -0
  260. package/dist/types/import/types.d.ts +56 -0
  261. package/dist/types/import/usage.d.ts +10 -0
  262. package/dist/types/import/walls.d.ts +37 -0
  263. package/dist/types/map/CabinMaterials.d.ts +18 -0
  264. package/dist/types/map/CabinMirror.d.ts +13 -0
  265. package/dist/types/map/Doors.d.ts +11 -0
  266. package/dist/types/map/ElevatorCabin.d.ts +20 -0
  267. package/dist/types/map/FixtureLights.d.ts +35 -0
  268. package/dist/types/map/MapCanvas.d.ts +87 -0
  269. package/dist/types/map/Passenger.d.ts +18 -0
  270. package/dist/types/map/SceneLayer.d.ts +306 -0
  271. package/dist/types/map/UndergroundContext.d.ts +18 -0
  272. package/dist/types/map/ambience.d.ts +29 -0
  273. package/dist/types/map/architecture.d.ts +8 -0
  274. package/dist/types/map/bathMusic.d.ts +14 -0
  275. package/dist/types/map/cameraAim.d.ts +20 -0
  276. package/dist/types/map/elevators.d.ts +16 -0
  277. package/dist/types/map/excavation.d.ts +9 -0
  278. package/dist/types/map/exteriors.d.ts +3 -0
  279. package/dist/types/map/features.d.ts +33 -0
  280. package/dist/types/map/fences.d.ts +4 -0
  281. package/dist/types/map/journey.d.ts +36 -0
  282. package/dist/types/map/levels.d.ts +4 -0
  283. package/dist/types/map/lighting.d.ts +103 -0
  284. package/dist/types/map/loadBasemap.d.ts +4 -0
  285. package/dist/types/map/materials.d.ts +38 -0
  286. package/dist/types/map/poolLighting.d.ts +11 -0
  287. package/dist/types/map/projection.d.ts +9 -0
  288. package/dist/types/map/rampGeometry.d.ts +8 -0
  289. package/dist/types/map/route.d.ts +59 -0
  290. package/dist/types/map/stairSurfaces.d.ts +29 -0
  291. package/dist/types/map/surfaces.d.ts +36 -0
  292. package/dist/types/map/textures.d.ts +8 -0
  293. package/dist/types/map/underground.d.ts +30 -0
  294. package/dist/types/map/walk.d.ts +232 -0
  295. package/dist/types/map/walkContext.d.ts +11 -0
  296. package/dist/types/map/walkDimensions.d.ts +5 -0
  297. package/dist/types/map/walkJourney.d.ts +33 -0
  298. package/dist/types/map/walkSurfaces.d.ts +27 -0
  299. package/dist/types/map/wallJoins.d.ts +6 -0
  300. package/dist/types/map/water.d.ts +12 -0
  301. package/dist/types/model/authoring.d.ts +38 -0
  302. package/dist/types/model/boundaries.d.ts +41 -0
  303. package/dist/types/model/coverage.d.ts +8 -0
  304. package/dist/types/model/document.d.ts +9 -0
  305. package/dist/types/model/doors.d.ts +19 -0
  306. package/dist/types/model/factory.d.ts +5 -0
  307. package/dist/types/model/geometry.d.ts +99 -0
  308. package/dist/types/model/history.d.ts +10 -0
  309. package/dist/types/model/host.d.ts +154 -0
  310. package/dist/types/model/imports.d.ts +7 -0
  311. package/dist/types/model/inference.d.ts +46 -0
  312. package/dist/types/model/live.d.ts +60 -0
  313. package/dist/types/model/materials.d.ts +28 -0
  314. package/dist/types/model/mutations.d.ts +178 -0
  315. package/dist/types/model/navigation.d.ts +70 -0
  316. package/dist/types/model/ontology.d.ts +43 -0
  317. package/dist/types/model/passages.d.ts +2 -0
  318. package/dist/types/model/portals.d.ts +9 -0
  319. package/dist/types/model/precision.d.ts +11 -0
  320. package/dist/types/model/project.d.ts +8 -0
  321. package/dist/types/model/ramps.d.ts +7 -0
  322. package/dist/types/model/roomNavigation.d.ts +15 -0
  323. package/dist/types/model/routeClearance.d.ts +11 -0
  324. package/dist/types/model/spaces.d.ts +46 -0
  325. package/dist/types/model/stairGeometry.d.ts +27 -0
  326. package/dist/types/model/stairTraversal.d.ts +8 -0
  327. package/dist/types/model/topology.d.ts +14 -0
  328. package/dist/types/model/types.d.ts +369 -0
  329. package/dist/types/model/validate.d.ts +38 -0
  330. package/dist/types/model/vertical.d.ts +145 -0
  331. package/dist/types/model/walls.d.ts +71 -0
  332. package/dist/types/planner/DrawingDock.d.ts +16 -0
  333. package/dist/types/planner/FloorControls.d.ts +17 -0
  334. package/dist/types/planner/MapSettings.d.ts +16 -0
  335. package/dist/types/planner/PlannerHelp.d.ts +3 -0
  336. package/dist/types/planner/ProjectSidebar.d.ts +32 -0
  337. package/dist/types/planner/floors.d.ts +3 -0
  338. package/dist/types/planner/types.d.ts +3 -0
  339. package/dist/types/planner/useDrawingTools.d.ts +52 -0
  340. package/dist/types/planner/useMapDisplay.d.ts +29 -0
  341. package/dist/types/planner/usePlannerKeyboard.d.ts +18 -0
  342. package/dist/types/planner/useReferenceAlignment.d.ts +30 -0
  343. package/dist/types/schema/index.d.ts +25 -0
  344. package/dist/types/theme/index.d.ts +55 -0
  345. package/package.json +58 -4
@@ -0,0 +1,275 @@
1
+ var z = Object.defineProperty;
2
+ var J = (o, t, n) => t in o ? z(o, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : o[t] = n;
3
+ var j = (o, t, n) => J(o, typeof t != "symbol" ? t + "" : t, n);
4
+ import { uid as B } from "./types.js";
5
+ import { distance as w, objectPosition as D, pointInRing as L } from "./geometry.js";
6
+ import { topology as G } from "./topology.js";
7
+ import { servedFloors as U, shaftKey as P } from "./vertical.js";
8
+ import { spaceAt as X, inSpace as Y } from "./spaces.js";
9
+ import { routeClearance as Z } from "./routeClearance.js";
10
+ const oo = 25, to = 1, eo = 5, io = 1.5, no = 0.25, R = (o) => o.navNodes ?? G(o).nodes, so = (o) => o.navEdges ?? G(o).edges, q = (o, t) => t === null ? null : o.floors.find((n) => n.id === t) ?? null, E = (o, t) => {
11
+ var n;
12
+ return ((n = q(o, t)) == null ? void 0 : n.elevation) ?? 0;
13
+ };
14
+ function H(o, t, n, s) {
15
+ o.navNodes ?? (o.navNodes = []);
16
+ const e = o.navNodes.find((f) => f.floorId === t && w(f.position, n) < no);
17
+ if (e)
18
+ return s && !e.objectId && (e.objectId = s), e;
19
+ const l = { id: B(), floorId: t, position: [n[0], n[1]] };
20
+ return s && (l.objectId = s), o.navNodes.push(l), l;
21
+ }
22
+ function K(o, t, n, s, e) {
23
+ o.navEdges ?? (o.navEdges = []);
24
+ const l = o.navEdges.find(
25
+ (i) => i.kind === t && (i.aId === n.id && i.bId === s.id || i.aId === s.id && i.bId === n.id)
26
+ );
27
+ if (l)
28
+ return e && !l.objectId && (l.objectId = e), l;
29
+ const f = { id: B(), kind: t, aId: n.id, bId: s.id };
30
+ return e && (f.objectId = e), o.navEdges.push(f), f;
31
+ }
32
+ function po(o, t, n) {
33
+ const s = n.map((e) => H(o, t, e));
34
+ for (let e = 1; e < s.length; e++) s[e - 1].id !== s[e].id && K(o, "walk", s[e - 1], s[e]);
35
+ return s;
36
+ }
37
+ function ko(o, t) {
38
+ const n = t.kind === "elevator" ? "elevator" : "stairs", s = t.stairModel === "escalator" ? t.travel ?? "up" : null, e = U(o, t), l = P(t), f = /* @__PURE__ */ new Map();
39
+ for (const r of o.objects)
40
+ r.kind === t.kind && P(r) === l && !f.has(r.floorId) && f.set(r.floorId, r);
41
+ const i = (r) => f.get(r) ?? t, c = e.map((r) => {
42
+ const u = i(r.id);
43
+ return H(o, r.id, D(o, u), u.id);
44
+ });
45
+ for (let r = 1; r < c.length; r++)
46
+ for (let u = n === "elevator" ? 0 : r - 1; u < r; u++) {
47
+ if (c[u].id === c[r].id) continue;
48
+ const [p, h] = s === "down" ? [c[r], c[u]] : [c[u], c[r]], k = K(o, n, p, h, i(c[r].floorId).id);
49
+ s && (k.directed = !0);
50
+ }
51
+ return c;
52
+ }
53
+ const vo = "@here";
54
+ function V(o, t, n, s) {
55
+ if (t.weight !== void 0) return t.weight;
56
+ const e = w(n.position, s.position), l = Math.abs(E(o, n.floorId) - E(o, s.floorId));
57
+ return t.kind === "elevator" ? oo + l * to : t.kind === "stairs" ? e + l * eo : e + (t.kind === "door" ? io : 0);
58
+ }
59
+ function y(o, t, n, s) {
60
+ const e = t.kind === "stairs" || t.kind === "elevator" ? Math.abs(E(o, n.floorId) - E(o, s.floorId)) : 0;
61
+ return w(n.position, s.position) + e;
62
+ }
63
+ function ro(o, t) {
64
+ const n = R(o).filter((i) => i.objectId === t.id);
65
+ if (n.length) return n.map((i) => ({ node: i, cost: 0 }));
66
+ const s = D(o, t), e = R(o).filter((i) => i.floorId === t.floorId), l = t.rings ? e.filter(
67
+ (i) => L(i.position, t.rings[0]) && !t.rings.slice(1).some((c) => L(i.position, c))
68
+ ) : [];
69
+ if (l.length) return l.map((i) => ({ node: i, cost: w(s, i.position) }));
70
+ const f = e.map((i) => ({ node: i, cost: w(s, i.position) })).sort((i, c) => i.cost - c.cost)[0];
71
+ return f ? [f] : [];
72
+ }
73
+ function W(o, t, n) {
74
+ if (typeof t == "string") {
75
+ const r = o.objects.find((u) => u.id === t);
76
+ return r ? { place: r, anchors: ro(o, r), extraNodes: [], extraEdges: [] } : null;
77
+ }
78
+ const s = { id: `@${n}`, floorId: t.floorId, position: t.position }, e = R(o).filter((r) => r.floorId === t.floorId), l = X(o, t.floorId, t.position), f = Z(o)(t.floorId);
79
+ let i = l ? e.filter((r) => Y(l, r.position) && f(t.position, r.position)) : [];
80
+ if (!l) {
81
+ const r = e.filter((u) => f(t.position, u.position)).sort((u, p) => w(u.position, t.position) - w(p.position, t.position))[0];
82
+ i = r ? [r] : [];
83
+ }
84
+ const c = i.map(
85
+ (r, u) => ({
86
+ id: `@${n}-${u}`,
87
+ kind: "walk",
88
+ aId: s.id,
89
+ bId: r.id,
90
+ weight: w(t.position, r.position)
91
+ })
92
+ );
93
+ return {
94
+ place: {
95
+ id: `@${n}`,
96
+ kind: "poi",
97
+ name: t.name ?? "Where you are",
98
+ floorId: t.floorId,
99
+ position: t.position
100
+ },
101
+ anchors: [{ node: s, cost: 0 }],
102
+ extraNodes: [s],
103
+ extraEdges: c
104
+ };
105
+ }
106
+ function wo(o, t, n, s = {}) {
107
+ var F;
108
+ const e = W(o, t, "from"), l = W(o, n, "to");
109
+ if (!e || !l) return null;
110
+ const f = e.place, i = l.place, c = e.anchors, r = l.anchors;
111
+ if (!c.length || !r.length) return null;
112
+ const u = [...R(o), ...e.extraNodes, ...l.extraNodes], p = new Map(u.map((d) => [d.id, d])), h = /* @__PURE__ */ new Map();
113
+ for (const d of [...so(o), ...e.extraEdges, ...l.extraEdges]) {
114
+ const a = p.get(d.aId), I = p.get(d.bId);
115
+ if (!a || !I) continue;
116
+ const v = d.kind === "stairs" && d.objectId ? o.objects.find((m) => m.id === d.objectId && m.stairModel === "escalator") : void 0, g = v != null && v.feedId ? (F = s.statuses) == null ? void 0 : F.get(v.feedId) : void 0;
117
+ if (v && (g != null && g.travel || (g == null ? void 0 : g.running) === !1)) {
118
+ const m = E(o, I.floorId) > E(o, a.floorId), x = (g.travel ?? v.travel ?? "up") === (m ? "up" : "down");
119
+ (g.running === !1 || x) && (h.get(a.id) ?? h.set(a.id, []).get(a.id)).push({ edge: d, other: I }), (g.running === !1 || !x) && (h.get(I.id) ?? h.set(I.id, []).get(I.id)).push({ edge: d, other: a });
120
+ continue;
121
+ }
122
+ (h.get(a.id) ?? h.set(a.id, []).get(a.id)).push({ edge: d, other: I }), d.directed || (h.get(I.id) ?? h.set(I.id, []).get(I.id)).push({ edge: d, other: a });
123
+ }
124
+ const k = new fo(), b = /* @__PURE__ */ new Map(), N = /* @__PURE__ */ new Map(), T = /* @__PURE__ */ new Set();
125
+ for (const d of c)
126
+ d.cost < (b.get(d.node.id) ?? 1 / 0) && (b.set(d.node.id, d.cost), k.push(d.node.id, d.cost));
127
+ const _ = new Map(r.map((d) => [d.node.id, d.cost]));
128
+ let A = 1 / 0;
129
+ for (; ; ) {
130
+ const d = k.pop();
131
+ if (!d || d.cost > A) break;
132
+ const { id: a, cost: I } = d;
133
+ if (T.has(a)) continue;
134
+ T.add(a), _.has(a) && (A = Math.min(A, I + _.get(a)));
135
+ const v = p.get(a);
136
+ for (const { edge: g, other: m } of h.get(a) ?? []) {
137
+ const x = I + V(o, g, v, m);
138
+ x < (b.get(m.id) ?? 1 / 0) && (b.set(m.id, x), N.set(m.id, { node: v, edge: g }), k.push(m.id, x));
139
+ }
140
+ }
141
+ const M = r.map((d) => ({ ...d, total: (b.get(d.node.id) ?? 1 / 0) + d.cost })).sort((d, a) => d.total - a.total)[0];
142
+ if (!M || !Number.isFinite(M.total)) return null;
143
+ const $ = [];
144
+ for (let d = M.node; ; ) {
145
+ const a = N.get(d.id);
146
+ if (!a) break;
147
+ $.unshift({ edge: a.edge, from: a.node, to: d, cost: V(o, a.edge, a.node, d) }), d = a.node;
148
+ }
149
+ const C = [$.length ? $[0].from : M.node, ...$.map((d) => d.to)], Q = $.reduce((d, a) => d + y(o, a.edge, a.from, a.to), 0);
150
+ return {
151
+ from: f,
152
+ to: i,
153
+ nodes: C,
154
+ legs: $,
155
+ steps: lo(o, f, i, C, $),
156
+ distance: Q,
157
+ cost: M.total
158
+ };
159
+ }
160
+ class fo {
161
+ constructor() {
162
+ j(this, "items", []);
163
+ j(this, "order", 0);
164
+ }
165
+ before(t, n) {
166
+ return t.cost < n.cost || t.cost === n.cost && t.order < n.order;
167
+ }
168
+ push(t, n) {
169
+ const s = { id: t, cost: n, order: this.order++ };
170
+ let e = this.items.length;
171
+ for (this.items.push(s); e > 0; ) {
172
+ const l = e - 1 >> 1;
173
+ if (!this.before(s, this.items[l])) break;
174
+ this.items[e] = this.items[l], e = l;
175
+ }
176
+ this.items[e] = s;
177
+ }
178
+ pop() {
179
+ const t = this.items[0], n = this.items.pop();
180
+ if (this.items.length && n) {
181
+ let s = 0;
182
+ for (; s * 2 + 1 < this.items.length; ) {
183
+ let e = s * 2 + 1;
184
+ if (e + 1 < this.items.length && this.before(this.items[e + 1], this.items[e]) && e++, !this.before(this.items[e], n)) break;
185
+ this.items[s] = this.items[e], s = e;
186
+ }
187
+ this.items[s] = n;
188
+ }
189
+ return t;
190
+ }
191
+ }
192
+ function O(o, t) {
193
+ const n = q(o, t);
194
+ return n ? n.code ? `${n.name} (level ${n.code})` : n.name : "outside";
195
+ }
196
+ const S = (o, t, n) => {
197
+ var s;
198
+ return ((s = o.objects.find((e) => e.id === t.edge.objectId)) == null ? void 0 : s.name) ?? n;
199
+ };
200
+ function lo(o, t, n, s, e) {
201
+ const l = [
202
+ {
203
+ kind: "depart",
204
+ text: `Start at ${t.name}${t.floorId !== null ? ` on ${O(o, t.floorId)}` : ""}`,
205
+ floorId: t.floorId,
206
+ nodeIds: s.length ? [s[0].id] : []
207
+ }
208
+ ];
209
+ for (let f = 0; f < e.length; f++) {
210
+ const i = e[f];
211
+ if (i.edge.kind === "walk") {
212
+ let c = y(o, i.edge, i.from, i.to);
213
+ const r = [i.from.id, i.to.id];
214
+ for (; f + 1 < e.length && e[f + 1].edge.kind === "walk"; )
215
+ f++, c += y(o, e[f].edge, e[f].from, e[f].to), r.push(e[f].to.id);
216
+ l.push({
217
+ kind: "walk",
218
+ text: `Walk ${Math.max(1, Math.round(c))} m ${e[f].to.floorId === null ? "outside" : `along ${O(o, i.from.floorId)}`}`,
219
+ floorId: i.from.floorId,
220
+ nodeIds: r,
221
+ distance: c
222
+ });
223
+ } else if (i.edge.kind === "door") {
224
+ const c = S(o, i, "the door"), r = i.to.floorId === null && i.from.floorId !== null ? `Exit through ${c}` : i.from.floorId === null && i.to.floorId !== null ? `Enter through ${c}` : `Go through ${c}`;
225
+ l.push({
226
+ kind: "door",
227
+ text: r,
228
+ floorId: i.to.floorId,
229
+ nodeIds: [i.from.id, i.to.id],
230
+ objectId: i.edge.objectId,
231
+ distance: y(o, i.edge, i.from, i.to)
232
+ });
233
+ } else {
234
+ const c = i.edge.kind, r = S(o, i, c === "elevator" ? "the lift" : "the stairs"), u = [i.from.id, i.to.id], p = i.from;
235
+ let h = i.to, k = i.edge.objectId;
236
+ for (; f + 1 < e.length && e[f + 1].edge.kind === c && S(o, e[f + 1], r) === r; )
237
+ f++, h = e[f].to, u.push(h.id), k = e[f].edge.objectId ?? k;
238
+ const b = E(o, h.floorId) - E(o, p.floorId), N = c === "stairs" && !/\b(up|down)\b/i.test(r) ? `${b > 0 ? "up " : "down "}` : "";
239
+ l.push({
240
+ kind: c,
241
+ text: `Take ${r} ${N}to ${O(o, h.floorId)}`,
242
+ floorId: h.floorId,
243
+ nodeIds: u,
244
+ objectId: k
245
+ });
246
+ }
247
+ }
248
+ return l.push({
249
+ kind: "arrive",
250
+ text: `Arrive at ${n.name}`,
251
+ floorId: n.floorId,
252
+ nodeIds: s.length ? [s[s.length - 1].id] : []
253
+ }), l;
254
+ }
255
+ export {
256
+ io as DOOR_COST,
257
+ oo as ELEVATOR_BASE,
258
+ to as ELEVATOR_PER_METRE,
259
+ vo as HERE,
260
+ no as NAV_WELD,
261
+ eo as STAIR_CLIMB_FACTOR,
262
+ K as addNavEdge,
263
+ H as addNavNode,
264
+ ko as chainVertical,
265
+ V as edgeCost,
266
+ y as edgeLength,
267
+ wo as findRoute,
268
+ O as floorPhrase,
269
+ so as navEdges,
270
+ R as navNodes,
271
+ po as navPath,
272
+ ro as routeAnchors,
273
+ lo as routeSteps
274
+ };
275
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","sources":["../../../../src/model/navigation.ts"],"sourcesContent":["// Indoor-navigation graph: authoring helpers, A-to-B routing (Dijkstra) and human step\n// instructions (graph validation lives in validate.ts). Pure model code — no React, no MapLibre; all geometry stays in the\n// local metric frame, and floor elevations are the real model values (presentation compression of\n// buried floors is a map concern, see src/map/underground.ts).\nimport type { Floor, NavEdge, NavEdgeKind, NavNode, Point, ProjectDocument, SiteObject } from './types';\nimport { uid } from './types';\nimport { distance, objectPosition, pointInRing } from './geometry';\nimport { topology } from './topology';\nimport { servedFloors, shaftKey } from './vertical';\nimport { inSpace, spaceAt } from './spaces';\nimport { routeClearance } from './routeClearance';\nimport type { StatusReading } from './live';\n\n// Cost model in metres-equivalent, exported so editors and play mode price edges identically: a\n// lift ride costs a flat call-and-wait plus a little per metre of rise, stairs cost their run plus\n// a climb penalty — so one storey favours the stairs and a tower ride favours the lift.\nexport const ELEVATOR_BASE = 25,\n ELEVATOR_PER_METRE = 1,\n STAIR_CLIMB_FACTOR = 5,\n DOOR_COST = 1.5;\n/** addNavNode welds onto any existing node within this range (mirrors joinAt's junction reuse). */\nexport const NAV_WELD = 0.25;\n\n// Authored graph wins where a document has one; otherwise the graph is derived from spaces and\n// portals. Routing, the journey player and the route overlay all read through these two accessors,\n// so they need no knowledge of which kind of document they are looking at.\nexport const navNodes = (p: ProjectDocument): NavNode[] => p.navNodes ?? topology(p).nodes;\nexport const navEdges = (p: ProjectDocument): NavEdge[] => p.navEdges ?? topology(p).edges;\nconst floorOf = (p: ProjectDocument, id: string | null): Floor | null =>\n id === null ? null : (p.floors.find(f => f.id === id) ?? null);\nconst elevation = (p: ProjectDocument, id: string | null) => floorOf(p, id)?.elevation ?? 0;\n// Graph validation moved to validate.ts — the validity layer stands on its own.\n\n// ——— Authoring helpers (used by the editor tool and the demo generators).\n/** Find-or-create a route node at a point on a floor, welding onto any node within NAV_WELD. */\nexport function addNavNode(p: ProjectDocument, floorId: string | null, position: Point, objectId?: string): NavNode {\n p.navNodes ??= [];\n const existing = p.navNodes.find(n => n.floorId === floorId && distance(n.position, position) < NAV_WELD);\n if (existing) {\n if (objectId && !existing.objectId) existing.objectId = objectId;\n return existing;\n }\n const node: NavNode = { id: uid(), floorId, position: [position[0], position[1]] };\n if (objectId) node.objectId = objectId;\n p.navNodes.push(node);\n return node;\n}\n/** Connect two nodes, reusing an existing edge of the same kind between the same pair. */\nexport function addNavEdge(p: ProjectDocument, kind: NavEdgeKind, a: NavNode, b: NavNode, objectId?: string): NavEdge {\n p.navEdges ??= [];\n const existing = p.navEdges.find(\n e => e.kind === kind && ((e.aId === a.id && e.bId === b.id) || (e.aId === b.id && e.bId === a.id)),\n );\n if (existing) {\n if (objectId && !existing.objectId) existing.objectId = objectId;\n return existing;\n }\n const edge: NavEdge = { id: uid(), kind, aId: a.id, bId: b.id };\n if (objectId) edge.objectId = objectId;\n p.navEdges.push(edge);\n return edge;\n}\n/** Draw a chain of walk edges through the given points, welding onto existing nodes en route. */\nexport function navPath(p: ProjectDocument, floorId: string | null, points: Point[]): NavNode[] {\n const out = points.map(pt => addNavNode(p, floorId, pt));\n for (let i = 1; i < out.length; i++) if (out[i - 1].id !== out[i].id) addNavEdge(p, 'walk', out[i - 1], out[i]);\n return out;\n}\n/** Thread a vertical connection through an elevator/stairs object: one node per served floor at the object's position. Stairs chain adjacent served floors (you pass every level); an elevator gets an edge per floor pair (a ride is direct, so ELEVATOR_BASE is paid once). Per-floor twin objects share a name (see the demo lifts); pass any one of them and each edge binds the twin on its upper floor. */\nexport function chainVertical(p: ProjectDocument, object: SiteObject): NavNode[] {\n const kind: NavEdgeKind = object.kind === 'elevator' ? 'elevator' : 'stairs';\n // An escalator carries you one way, so the edges it threads are one-way: a route may ride it in\n // the direction it runs and must walk round to come back. A stair has no direction to run in.\n const travel = object.stairModel === 'escalator' ? (object.travel ?? 'up') : null;\n const floors = servedFloors(p, object);\n const key = shaftKey(object);\n const twins = new Map<string | null, SiteObject>();\n for (const o of p.objects)\n if (o.kind === object.kind && shaftKey(o) === key && !twins.has(o.floorId)) twins.set(o.floorId, o);\n const twin = (floorId: string) => twins.get(floorId) ?? object;\n const out = floors.map(f => {\n const t = twin(f.id);\n return addNavNode(p, f.id, objectPosition(p, t), t.id);\n });\n for (let i = 1; i < out.length; i++)\n for (let j = kind === 'elevator' ? 0 : i - 1; j < i; j++) {\n if (out[j].id === out[i].id) continue;\n // Nodes come sorted upwards, so `out[j]` is the lower landing. An escalator running down is\n // the same pair the other way about.\n const [from, to] = travel === 'down' ? [out[i], out[j]] : [out[j], out[i]];\n const edge = addNavEdge(p, kind, from, to, twin(out[i].floorId!).id);\n if (travel) edge.directed = true;\n }\n return out;\n}\n\n// ——— Routing.\nexport interface RouteLeg {\n edge: NavEdge;\n from: NavNode;\n to: NavNode;\n cost: number;\n}\nexport interface RouteStep {\n kind: 'depart' | 'walk' | 'door' | 'stairs' | 'elevator' | 'arrive';\n text: string;\n floorId: string | null;\n nodeIds: string[];\n objectId?: string;\n distance?: number;\n}\n/** Either end of a route: an object in the document by id, or a point on a floor — where the\n * walker is standing, which is the start most people want and no object describes. */\nexport type RouteEnd = string | { name?: string; floorId: string | null; position: Point };\n/** What a route knows about its ends: enough to name them and place them, whether they came from\n * an object or a point. */\nexport type RoutePlace = Pick<SiteObject, 'id' | 'kind' | 'name' | 'floorId' | 'position'>;\n/** The id a host uses for \"where the walker is\" in a picker; findRoute() never sees it. */\nexport const HERE = '@here';\nexport interface Route {\n from: RoutePlace;\n to: RoutePlace;\n nodes: NavNode[];\n legs: RouteLeg[];\n steps: RouteStep[];\n distance: number;\n cost: number;\n}\n\n/** Metres-equivalent cost of traversing an edge; authored weight wins over the derived cost. */\nexport function edgeCost(p: ProjectDocument, edge: NavEdge, a: NavNode, b: NavNode): number {\n if (edge.weight !== undefined) return edge.weight;\n const run = distance(a.position, b.position),\n rise = Math.abs(elevation(p, a.floorId) - elevation(p, b.floorId));\n if (edge.kind === 'elevator') return ELEVATOR_BASE + rise * ELEVATOR_PER_METRE;\n if (edge.kind === 'stairs') return run + rise * STAIR_CLIMB_FACTOR;\n return run + (edge.kind === 'door' ? DOOR_COST : 0);\n}\n/** Walking metres of an edge (horizontal run, plus the rise on vertical edges) — for display, not costing. */\nexport function edgeLength(p: ProjectDocument, edge: NavEdge, a: NavNode, b: NavNode): number {\n const rise =\n edge.kind === 'stairs' || edge.kind === 'elevator'\n ? Math.abs(elevation(p, a.floorId) - elevation(p, b.floorId))\n : 0;\n return distance(a.position, b.position) + rise;\n}\n\n/** Candidate graph entry points for a room/door/poi: nodes bound to the object at cost 0, else nodes inside an area's outer ring, else the nearest node on the object's floor. Cost is the straight-line approach in metres. */\nexport function routeAnchors(p: ProjectDocument, object: SiteObject): { node: NavNode; cost: number }[] {\n const bound = navNodes(p).filter(n => n.objectId === object.id);\n if (bound.length) return bound.map(node => ({ node, cost: 0 }));\n const anchor = objectPosition(p, object);\n const sameFloor = navNodes(p).filter(n => n.floorId === object.floorId);\n const inside = object.rings\n ? sameFloor.filter(\n n => pointInRing(n.position, object.rings![0]) && !object.rings!.slice(1).some(h => pointInRing(n.position, h)),\n )\n : [];\n if (inside.length) return inside.map(node => ({ node, cost: distance(anchor, node.position) }));\n const nearest = sameFloor\n .map(node => ({ node, cost: distance(anchor, node.position) }))\n .sort((a, b) => a.cost - b.cost)[0];\n return nearest ? [nearest] : [];\n}\n\n/** A route's end resolved against the graph: the place it names, the nodes it can join the graph\n * at and what joining costs, and — for a point — the node and edges that stand in for it. A point\n * is not in the graph, so it is given a node of its own, joined by walk edges to the nodes inside\n * the space it stands in (or the nearest node on its floor, standing in a corridor nobody drew as\n * a space). The route then genuinely starts where the walker is, and the first leg is the walk\n * from there to the drawn network. */\nfunction routeEnd(\n p: ProjectDocument,\n end: RouteEnd,\n tag: string,\n): {\n place: RoutePlace;\n anchors: { node: NavNode; cost: number }[];\n extraNodes: NavNode[];\n extraEdges: NavEdge[];\n} | null {\n if (typeof end === 'string') {\n const object = p.objects.find(o => o.id === end);\n return object ? { place: object, anchors: routeAnchors(p, object), extraNodes: [], extraEdges: [] } : null;\n }\n const here: NavNode = { id: `@${tag}`, floorId: end.floorId, position: end.position };\n const sameFloor = navNodes(p).filter(n => n.floorId === end.floorId);\n const space = spaceAt(p, end.floorId, end.position);\n const clear = routeClearance(p)(end.floorId);\n let joins = space ? sameFloor.filter(n => inSpace(space, n.position) && clear(end.position, n.position)) : [];\n if (!space) {\n const nearest = sameFloor\n .filter(n => clear(end.position, n.position))\n .sort((a, b) => distance(a.position, end.position) - distance(b.position, end.position))[0];\n joins = nearest ? [nearest] : [];\n }\n const extraEdges = joins.map(\n (n, i): NavEdge => ({\n id: `@${tag}-${i}`,\n kind: 'walk',\n aId: here.id,\n bId: n.id,\n weight: distance(end.position, n.position),\n }),\n );\n return {\n place: {\n id: `@${tag}`,\n kind: 'poi',\n name: end.name ?? 'Where you are',\n floorId: end.floorId,\n position: end.position,\n },\n anchors: [{ node: here, cost: 0 }],\n extraNodes: [here],\n extraEdges,\n };\n}\n\n/** Shortest route between two ends — objects (rooms, doors, POIs, …) or points on a floor — over\n * the authored graph, with human step instructions. Returns null when either end has no anchor or\n * no path exists. */\nexport interface RouteOptions {\n statuses?: ReadonlyMap<string, StatusReading>;\n}\nexport function findRoute(\n p: ProjectDocument,\n fromEnd: RouteEnd,\n toEnd: RouteEnd,\n options: RouteOptions = {},\n): Route | null {\n const start = routeEnd(p, fromEnd, 'from'),\n end = routeEnd(p, toEnd, 'to');\n if (!start || !end) return null;\n const from = start.place,\n to = end.place;\n const starts = start.anchors,\n goals = end.anchors;\n if (!starts.length || !goals.length) return null;\n const nodes = [...navNodes(p), ...start.extraNodes, ...end.extraNodes],\n byId = new Map(nodes.map(n => [n.id, n]));\n const adjacency = new Map<string, { edge: NavEdge; other: NavNode }[]>();\n for (const e of [...navEdges(p), ...start.extraEdges, ...end.extraEdges]) {\n const a = byId.get(e.aId),\n b = byId.get(e.bId);\n if (!a || !b) continue;\n const machine =\n e.kind === 'stairs' && e.objectId\n ? p.objects.find(o => o.id === e.objectId && o.stairModel === 'escalator')\n : undefined;\n const reading = machine?.feedId ? options.statuses?.get(machine.feedId) : undefined;\n if (machine && (reading?.travel || reading?.running === false)) {\n const up = elevation(p, b.floorId) > elevation(p, a.floorId);\n const forward = (reading.travel ?? machine.travel ?? 'up') === (up ? 'up' : 'down');\n if (reading.running === false || forward)\n (adjacency.get(a.id) ?? adjacency.set(a.id, []).get(a.id)!).push({ edge: e, other: b });\n if (reading.running === false || !forward)\n (adjacency.get(b.id) ?? adjacency.set(b.id, []).get(b.id)!).push({ edge: e, other: a });\n continue;\n }\n (adjacency.get(a.id) ?? adjacency.set(a.id, []).get(a.id)!).push({ edge: e, other: b });\n // A directed edge is passable one way only — an escalator, or a door you cannot come back\n // through. Adding the return leg anyway is how a one-way route quietly becomes a round trip.\n if (!e.directed) (adjacency.get(b.id) ?? adjacency.set(b.id, []).get(b.id)!).push({ edge: e, other: a });\n }\n const queue = new RouteQueue();\n const dist = new Map<string, number>(),\n prev = new Map<string, { node: NavNode; edge: NavEdge }>(),\n done = new Set<string>();\n for (const s of starts)\n if (s.cost < (dist.get(s.node.id) ?? Infinity)) {\n dist.set(s.node.id, s.cost);\n queue.push(s.node.id, s.cost);\n }\n const goalCosts = new Map(goals.map(g => [g.node.id, g.cost]));\n let bestGoal = Infinity;\n for (;;) {\n // A visibility graph can have thousands of nodes. A heap and an admissible goal bound avoid\n // rescanning every reached node and searching the rest of the building after arrival is known.\n const entry = queue.pop();\n if (!entry || entry.cost > bestGoal) break;\n const { id: current, cost: best } = entry;\n if (done.has(current)) continue;\n done.add(current);\n if (goalCosts.has(current)) bestGoal = Math.min(bestGoal, best + goalCosts.get(current)!);\n const node = byId.get(current)!;\n for (const { edge, other } of adjacency.get(current) ?? []) {\n const next = best + edgeCost(p, edge, node, other);\n if (next < (dist.get(other.id) ?? Infinity)) {\n dist.set(other.id, next);\n prev.set(other.id, { node, edge });\n queue.push(other.id, next);\n }\n }\n }\n const goal = goals\n .map(g => ({ ...g, total: (dist.get(g.node.id) ?? Infinity) + g.cost }))\n .sort((a, b) => a.total - b.total)[0];\n if (!goal || !Number.isFinite(goal.total)) return null;\n const legs: RouteLeg[] = [];\n for (let at = goal.node; ; ) {\n const back = prev.get(at.id);\n if (!back) break;\n legs.unshift({ edge: back.edge, from: back.node, to: at, cost: edgeCost(p, back.edge, back.node, at) });\n at = back.node;\n }\n const path = [legs.length ? legs[0].from : goal.node, ...legs.map(l => l.to)];\n const walked = legs.reduce((sum, l) => sum + edgeLength(p, l.edge, l.from, l.to), 0);\n return {\n from,\n to,\n nodes: path,\n legs,\n steps: routeSteps(p, from, to, path, legs),\n distance: walked,\n cost: goal.total,\n };\n}\n\n/** Stable min-heap: equal-cost paths keep insertion order, so a regenerated plan routes identically. */\nclass RouteQueue {\n private items: { id: string; cost: number; order: number }[] = [];\n private order = 0;\n private before(a: (typeof this.items)[number], b: (typeof this.items)[number]) {\n return a.cost < b.cost || (a.cost === b.cost && a.order < b.order);\n }\n push(id: string, cost: number) {\n const item = { id, cost, order: this.order++ };\n let i = this.items.length;\n this.items.push(item);\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if (!this.before(item, this.items[parent])) break;\n this.items[i] = this.items[parent];\n i = parent;\n }\n this.items[i] = item;\n }\n pop() {\n const first = this.items[0],\n last = this.items.pop();\n if (this.items.length && last) {\n let i = 0;\n while (i * 2 + 1 < this.items.length) {\n let child = i * 2 + 1;\n if (child + 1 < this.items.length && this.before(this.items[child + 1], this.items[child])) child++;\n if (!this.before(this.items[child], last)) break;\n this.items[i] = this.items[child];\n i = child;\n }\n this.items[i] = last;\n }\n return first;\n }\n}\n\n// ——— Step instructions.\n/** Human name of a level: \"Menswear & denim (level 3)\" via Floor.code, \"outside\" for the site level. */\nexport function floorPhrase(p: ProjectDocument, floorId: string | null): string {\n const floor = floorOf(p, floorId);\n return !floor ? 'outside' : floor.code ? `${floor.name} (level ${floor.code})` : floor.name;\n}\nconst legName = (p: ProjectDocument, leg: RouteLeg, fallback: string) =>\n p.objects.find(o => o.id === leg.edge.objectId)?.name ?? fallback;\n/** Build the human step list: walking grouped per floor, vertical rides merged per lift/stair name, doors called out — \"Exit through Aleksanterinkatu entrance\", \"Take Lift A to Offices · buying & admin (level 8)\". */\nexport function routeSteps(\n p: ProjectDocument,\n from: RoutePlace,\n to: RoutePlace,\n path: NavNode[],\n legs: RouteLeg[],\n): RouteStep[] {\n const steps: RouteStep[] = [\n {\n kind: 'depart',\n text: `Start at ${from.name}${from.floorId !== null ? ` on ${floorPhrase(p, from.floorId)}` : ''}`,\n floorId: from.floorId,\n nodeIds: path.length ? [path[0].id] : [],\n },\n ];\n for (let i = 0; i < legs.length; i++) {\n const leg = legs[i];\n if (leg.edge.kind === 'walk') {\n // Fold consecutive walk legs on one floor into a single stride.\n let d = edgeLength(p, leg.edge, leg.from, leg.to);\n const nodeIds = [leg.from.id, leg.to.id];\n while (i + 1 < legs.length && legs[i + 1].edge.kind === 'walk') {\n i++;\n d += edgeLength(p, legs[i].edge, legs[i].from, legs[i].to);\n nodeIds.push(legs[i].to.id);\n }\n steps.push({\n kind: 'walk',\n text: `Walk ${Math.max(1, Math.round(d))} m ${legs[i].to.floorId === null ? 'outside' : `along ${floorPhrase(p, leg.from.floorId)}`}`,\n floorId: leg.from.floorId,\n nodeIds,\n distance: d,\n });\n } else if (leg.edge.kind === 'door') {\n const name = legName(p, leg, 'the door');\n const text =\n leg.to.floorId === null && leg.from.floorId !== null\n ? `Exit through ${name}`\n : leg.from.floorId === null && leg.to.floorId !== null\n ? `Enter through ${name}`\n : `Go through ${name}`;\n steps.push({\n kind: 'door',\n text,\n floorId: leg.to.floorId,\n nodeIds: [leg.from.id, leg.to.id],\n objectId: leg.edge.objectId,\n distance: edgeLength(p, leg.edge, leg.from, leg.to),\n });\n } else {\n // Merge a consecutive vertical ride on the same lift/stair name into one instruction.\n const kind = leg.edge.kind,\n name = legName(p, leg, kind === 'elevator' ? 'the lift' : 'the stairs');\n const nodeIds = [leg.from.id, leg.to.id];\n const start = leg.from;\n let end = leg.to;\n let objectId = leg.edge.objectId;\n while (i + 1 < legs.length && legs[i + 1].edge.kind === kind && legName(p, legs[i + 1], name) === name) {\n i++;\n end = legs[i].to;\n nodeIds.push(end.id);\n objectId = legs[i].edge.objectId ?? objectId;\n }\n const rise = elevation(p, end.floorId) - elevation(p, start.floorId);\n const direction = kind === 'stairs' && !/\\b(up|down)\\b/i.test(name) ? `${rise > 0 ? 'up ' : 'down '}` : '';\n steps.push({\n kind,\n text: `Take ${name} ${direction}to ${floorPhrase(p, end.floorId)}`,\n floorId: end.floorId,\n nodeIds,\n objectId,\n });\n }\n }\n steps.push({\n kind: 'arrive',\n text: `Arrive at ${to.name}`,\n floorId: to.floorId,\n nodeIds: path.length ? [path[path.length - 1].id] : [],\n });\n return steps;\n}\n"],"names":["ELEVATOR_BASE","ELEVATOR_PER_METRE","STAIR_CLIMB_FACTOR","DOOR_COST","NAV_WELD","navNodes","p","topology","navEdges","floorOf","id","f","elevation","_a","addNavNode","floorId","position","objectId","existing","n","distance","node","uid","addNavEdge","kind","a","b","e","edge","navPath","points","out","pt","i","chainVertical","object","travel","floors","servedFloors","key","shaftKey","twins","o","twin","t","objectPosition","j","from","to","HERE","edgeCost","run","rise","edgeLength","routeAnchors","bound","anchor","sameFloor","inside","pointInRing","h","nearest","routeEnd","end","tag","here","space","spaceAt","clear","routeClearance","joins","inSpace","extraEdges","findRoute","fromEnd","toEnd","options","start","starts","goals","nodes","byId","adjacency","machine","reading","up","forward","queue","RouteQueue","dist","prev","done","s","goalCosts","g","bestGoal","entry","current","best","other","next","goal","legs","at","back","path","l","walked","sum","routeSteps","__publicField","cost","item","parent","first","last","child","floorPhrase","floor","legName","leg","fallback","steps","d","nodeIds","name","text","direction"],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { uid as m, isSpace as h } from "./types.js";
2
+ import { effectivePortals as l } from "./portals.js";
3
+ function Z(n, i) {
4
+ const e = n.passage ?? "both";
5
+ return e === "none" ? null : n.b === i && e !== "b-to-a" ? "a-to-b" : n.a === i && e !== "a-to-b" ? "b-to-a" : null;
6
+ }
7
+ const u = (n, i, e = /* @__PURE__ */ new Set()) => {
8
+ var s;
9
+ if (e.has(i.id)) return /* @__PURE__ */ new Set();
10
+ e.add(i.id);
11
+ const o = new Set(i.spaceIds);
12
+ for (const t of i.childZoneIds ?? []) {
13
+ const d = (s = n.zones) == null ? void 0 : s.find((a) => a.id === t);
14
+ if (d) for (const a of u(n, d, e)) o.add(a);
15
+ }
16
+ return o;
17
+ }, w = (n, i) => [...u(n, i)];
18
+ function z(n, i) {
19
+ const e = u(n, i);
20
+ return l(n).filter((o) => e.has(o.a) !== e.has(o.b));
21
+ }
22
+ function g(n, i) {
23
+ const e = u(n, i);
24
+ return l(n).filter((o) => e.has(o.a) && e.has(o.b));
25
+ }
26
+ function G(n, i, e, o) {
27
+ const s = {
28
+ id: m(),
29
+ name: i,
30
+ spaceIds: [...new Set(e)].filter((t) => n.objects.some((d) => d.id === t && h(d.kind)))
31
+ };
32
+ return o && (s.purpose = o), (n.zones ?? (n.zones = [])).push(s), s;
33
+ }
34
+ function v(n, i) {
35
+ var e;
36
+ n.zones = (n.zones ?? []).filter((o) => o.id !== i);
37
+ for (const o of n.zones)
38
+ (e = o.childZoneIds) != null && e.includes(i) && (o.childZoneIds = o.childZoneIds.filter((s) => s !== i));
39
+ }
40
+ function P(n, i, e, o) {
41
+ var a;
42
+ const s = (a = n.zones) == null ? void 0 : a.find((r) => r.id === i);
43
+ if (!s) return;
44
+ const t = e.filter((r) => n.objects.some((f) => f.id === r && h(f.kind))), d = new Set(s.spaceIds);
45
+ for (const r of t)
46
+ o ? d.add(r) : d.delete(r);
47
+ s.spaceIds = [...d];
48
+ }
49
+ function k(n, i, e) {
50
+ var t, d;
51
+ const o = (t = n.zones) == null ? void 0 : t.find((a) => a.id === i);
52
+ if (!o || i === e || !((d = n.zones) != null && d.some((a) => a.id === e))) return !1;
53
+ const s = (a, r, f = /* @__PURE__ */ new Set()) => {
54
+ var I, b;
55
+ return a === r ? !0 : f.has(a) ? !1 : (f.add(a), (((b = (I = n.zones) == null ? void 0 : I.find((c) => c.id === a)) == null ? void 0 : b.childZoneIds) ?? []).some((c) => s(c, r, f)));
56
+ };
57
+ return s(e, i) ? !1 : (o.childZoneIds = [.../* @__PURE__ */ new Set([...o.childZoneIds ?? [], e])], !0);
58
+ }
59
+ function x(n, i, e) {
60
+ n.portals = l(n);
61
+ const o = {
62
+ id: m(),
63
+ name: i,
64
+ portalIds: [...new Set(e)].filter((s) => (n.portals ?? []).some((t) => t.id === s))
65
+ };
66
+ return (n.portalGroups ?? (n.portalGroups = [])).push(o), o;
67
+ }
68
+ function y(n, i) {
69
+ n.portalGroups = (n.portalGroups ?? []).filter((e) => e.id !== i);
70
+ }
71
+ function M(n, i, e, o) {
72
+ var a;
73
+ const s = (a = n.portalGroups) == null ? void 0 : a.find((r) => r.id === i);
74
+ if (!s) return;
75
+ n.portals = l(n);
76
+ const t = e.filter((r) => (n.portals ?? []).some((f) => f.id === r)), d = new Set(s.portalIds);
77
+ for (const r of t)
78
+ o ? d.add(r) : d.delete(r);
79
+ s.portalIds = [...d];
80
+ }
81
+ function O(n) {
82
+ const i = new Set(n.objects.map((s) => s.id)), e = new Set(n.objects.filter((s) => h(s.kind)).map((s) => s.id));
83
+ n.portals && (n.portals = n.portals.filter(
84
+ (s) => e.has(s.a) && e.has(s.b) && (!s.openingId || i.has(s.openingId))
85
+ ));
86
+ const o = new Set((n.portals ?? []).map((s) => s.id));
87
+ if (n.zones) {
88
+ const s = new Set(n.zones.map((t) => t.id));
89
+ for (const t of n.zones)
90
+ t.spaceIds = t.spaceIds.filter((d) => e.has(d)), t.childZoneIds && (t.childZoneIds = t.childZoneIds.filter((d) => s.has(d)));
91
+ }
92
+ if (n.portalGroups)
93
+ for (const s of n.portalGroups) s.portalIds = s.portalIds.filter((t) => o.has(t));
94
+ }
95
+ export {
96
+ x as addPortalGroup,
97
+ G as addZone,
98
+ g as captive,
99
+ Z as entryInto,
100
+ k as nestZone,
101
+ z as perimeter,
102
+ O as pruneOntology,
103
+ y as removePortalGroup,
104
+ v as removeZone,
105
+ M as setPortalGroupMembers,
106
+ P as setZoneMembers,
107
+ w as zoneSpaces
108
+ };
109
+ //# sourceMappingURL=ontology.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ontology.js","sources":["../../../../src/model/ontology.ts"],"sourcesContent":["// The described layer: zones, portals and portal groups — what groups with what, what connects to\n// what, in which direction, and how far a crossing can be trusted. Everything here is semantics over\n// ids; no geometry is read. Reading the plan itself (inferring portals, dividing spaces) lives in\n// inference.ts, and turning this description into a routing graph lives in topology.ts.\nimport { isSpace, uid, type Portal, type PortalGroup, type ProjectDocument, type Zone } from './types';\nimport { effectivePortals } from './portals';\n\n/** Which way you must cross `portal` to arrive in `spaceId`, or null if you cannot get there that way.\n * Direction is asked, never stored: a door's entry side is relative to the area you mean. */\nexport function entryInto(portal: Portal, spaceId: string): 'a-to-b' | 'b-to-a' | null {\n const passage = portal.passage ?? 'both';\n if (passage === 'none') return null;\n if (portal.b === spaceId && passage !== 'b-to-a') return 'a-to-b';\n if (portal.a === spaceId && passage !== 'a-to-b') return 'b-to-a';\n return null;\n}\n\nconst memberSet = (project: ProjectDocument, zone: Zone, seen = new Set<string>()): Set<string> => {\n if (seen.has(zone.id)) return new Set(); // zones nest as a DAG; a cycle is a bug, not a hang\n seen.add(zone.id);\n const out = new Set(zone.spaceIds);\n for (const childId of zone.childZoneIds ?? []) {\n const child = project.zones?.find(z => z.id === childId);\n if (child) for (const id of memberSet(project, child, seen)) out.add(id);\n }\n return out;\n};\n/** Every space in `zone`, including those reached through nested zones. */\nexport const zoneSpaces = (project: ProjectDocument, zone: Zone): string[] => [...memberSet(project, zone)];\n\n/** Portals on the boundary of `zone` — one side in, one side out. These are the ways in, and the\n * thing software without a floor plan to ask makes you enumerate by hand. Derived, so it cannot go\n * stale when the building is redrawn. */\nexport function perimeter(project: ProjectDocument, zone: Zone): Portal[] {\n const inside = memberSet(project, zone);\n return effectivePortals(project).filter(p => inside.has(p.a) !== inside.has(p.b));\n}\n/** Portals wholly inside `zone`. Not decorative: anything tracking where somebody ended up needs to\n * know that crossing one of these does not change which zone you are in. */\nexport function captive(project: ProjectDocument, zone: Zone): Portal[] {\n const inside = memberSet(project, zone);\n return effectivePortals(project).filter(p => inside.has(p.a) && inside.has(p.b));\n}\n\n// ——— Authoring. Zones are the part a person actually writes: portals are read off the plan, but\n// what counts as \"Finance\" or \"the secure floor\" is a decision nobody can derive.\n\n/** Create a zone over the given spaces. Ids that are not spaces are dropped rather than rejected —\n * a selection usually contains a door or a fixture alongside the rooms, and losing the whole gesture\n * over that would be tiresome. */\nexport function addZone(project: ProjectDocument, name: string, spaceIds: string[], purpose?: string): Zone {\n const zone: Zone = {\n id: uid(),\n name,\n spaceIds: [...new Set(spaceIds)].filter(id => project.objects.some(o => o.id === id && isSpace(o.kind))),\n };\n if (purpose) zone.purpose = purpose;\n (project.zones ??= []).push(zone);\n return zone;\n}\n/** Remove a zone, and any nesting reference to it. A dangling childZoneIds entry fails validation. */\nexport function removeZone(project: ProjectDocument, zoneId: string) {\n project.zones = (project.zones ?? []).filter(z => z.id !== zoneId);\n for (const z of project.zones)\n if (z.childZoneIds?.includes(zoneId)) z.childZoneIds = z.childZoneIds.filter(id => id !== zoneId);\n}\n/** Add or remove spaces on a zone. Membership is a set: adding twice is not an error, and neither is\n * removing something that was never there. */\nexport function setZoneMembers(project: ProjectDocument, zoneId: string, spaceIds: string[], member: boolean) {\n const zone = project.zones?.find(z => z.id === zoneId);\n if (!zone) return;\n const eligible = spaceIds.filter(id => project.objects.some(o => o.id === id && isSpace(o.kind)));\n const next = new Set(zone.spaceIds);\n for (const id of eligible)\n if (member) next.add(id);\n else next.delete(id);\n zone.spaceIds = [...next];\n}\n/** Nest one zone inside another, refusing anything that would make a cycle. Zones form a DAG, and a\n * cycle is the one shape the validator will not accept. */\nexport function nestZone(project: ProjectDocument, parentId: string, childId: string): boolean {\n const parent = project.zones?.find(z => z.id === parentId);\n if (!parent || parentId === childId || !project.zones?.some(z => z.id === childId)) return false;\n const reaches = (from: string, target: string, seen = new Set<string>()): boolean => {\n if (from === target) return true;\n if (seen.has(from)) return false;\n seen.add(from);\n return (project.zones?.find(z => z.id === from)?.childZoneIds ?? []).some(id => reaches(id, target, seen));\n };\n if (reaches(childId, parentId)) return false; // the child already contains us\n parent.childZoneIds = [...new Set([...(parent.childZoneIds ?? []), childId])];\n return true;\n}\n\n/** Create a portal group over the given portals. The escape hatch for sets that are not a zone\n * boundary — \"all loading-bay shutters\", \"every door on the night circuit\". Where a set *is* a zone\n * boundary, derive it with `perimeter` instead; a derived list cannot go stale. Ids that are not\n * portals are dropped rather than rejected, mirroring addZone. */\nexport function addPortalGroup(project: ProjectDocument, name: string, portalIds: string[]): PortalGroup {\n project.portals = effectivePortals(project);\n const group: PortalGroup = {\n id: uid(),\n name,\n portalIds: [...new Set(portalIds)].filter(id => (project.portals ?? []).some(p => p.id === id)),\n };\n (project.portalGroups ??= []).push(group);\n return group;\n}\n/** Remove a portal group. Nothing references groups, so nothing needs cleaning up after one. */\nexport function removePortalGroup(project: ProjectDocument, groupId: string) {\n project.portalGroups = (project.portalGroups ?? []).filter(g => g.id !== groupId);\n}\n/** Add or remove portals on a group. Membership is a set, exactly as setZoneMembers. */\nexport function setPortalGroupMembers(project: ProjectDocument, groupId: string, portalIds: string[], member: boolean) {\n const group = project.portalGroups?.find(g => g.id === groupId);\n if (!group) return;\n project.portals = effectivePortals(project);\n const eligible = portalIds.filter(id => (project.portals ?? []).some(p => p.id === id));\n const next = new Set(group.portalIds);\n for (const id of eligible)\n if (member) next.add(id);\n else next.delete(id);\n group.portalIds = [...next];\n}\n\n/** Drop ontology references to things that no longer exist.\n *\n * Every reference here is validated, so a delete that skips this step produces an invalid document\n * and gets refused — from the outside that looks like the delete silently doing nothing, which is\n * the worst way for a bug to present. Call it after removing objects, from every path that can.\n *\n * A portal whose opening is gone goes with it: the door was the way through, and keeping a portal\n * that no longer has one would quietly turn a closed doorway into an open boundary. */\nexport function pruneOntology(project: ProjectDocument) {\n const alive = new Set(project.objects.map(o => o.id));\n const spaceIds = new Set(project.objects.filter(o => isSpace(o.kind)).map(o => o.id));\n if (project.portals)\n project.portals = project.portals.filter(\n p => spaceIds.has(p.a) && spaceIds.has(p.b) && (!p.openingId || alive.has(p.openingId)),\n );\n const portalIds = new Set((project.portals ?? []).map(p => p.id));\n if (project.zones) {\n const zoneIds = new Set(project.zones.map(z => z.id));\n for (const zone of project.zones) {\n zone.spaceIds = zone.spaceIds.filter(id => spaceIds.has(id));\n if (zone.childZoneIds) zone.childZoneIds = zone.childZoneIds.filter(id => zoneIds.has(id));\n }\n }\n if (project.portalGroups)\n for (const group of project.portalGroups) group.portalIds = group.portalIds.filter(id => portalIds.has(id));\n}\n"],"names":["entryInto","portal","spaceId","passage","memberSet","project","zone","seen","out","childId","child","_a","z","id","zoneSpaces","perimeter","inside","effectivePortals","p","captive","addZone","name","spaceIds","purpose","uid","o","isSpace","removeZone","zoneId","setZoneMembers","member","eligible","next","nestZone","parentId","parent","_b","reaches","from","target","addPortalGroup","portalIds","group","removePortalGroup","groupId","g","setPortalGroupMembers","pruneOntology","alive","zoneIds"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ const t = /* @__PURE__ */ new Set(["door", "gate", "turnstile"]);
2
+ export {
3
+ t as TRAVERSABLE
4
+ };
5
+ //# sourceMappingURL=passages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passages.js","sources":["../../../../src/model/passages.ts"],"sourcesContent":["/** Openings a person can cross. Windows are openings, but never walkable portals. */\nexport const TRAVERSABLE = new Set<string>(['door', 'gate', 'turnstile']);\n"],"names":["TRAVERSABLE"],"mappings":"AACO,MAAMA,IAAc,oBAAI,IAAY,CAAC,QAAQ,QAAQ,WAAW,CAAC;"}
@@ -0,0 +1,42 @@
1
+ import { isSpace as u } from "./types.js";
2
+ const a = (r, i) => JSON.stringify([r, i].sort());
3
+ function h(r) {
4
+ var c;
5
+ const i = new Map((r.virtualBoundaries ?? []).map((t) => [t.id, t]));
6
+ if (!i.size) return [];
7
+ const d = /* @__PURE__ */ new Map();
8
+ for (const t of r.objects)
9
+ if (!(!u(t.kind) || ((c = t.geometry) == null ? void 0 : c.mode) !== "boundaries"))
10
+ for (const o of t.geometry.loops)
11
+ for (const n of o) {
12
+ const e = i.get(n.edgeId);
13
+ if (!e || e.floorId !== t.floorId) continue;
14
+ const s = d.get(e.id) ?? [];
15
+ s.some((l) => l.id === t.id) || s.push({ id: t.id, reversed: !!n.reversed }), d.set(e.id, s);
16
+ }
17
+ const f = /* @__PURE__ */ new Map();
18
+ for (const t of d.values())
19
+ for (let o = 0; o < t.length; o++)
20
+ for (let n = o + 1; n < t.length; n++) {
21
+ if (t[o].reversed === t[n].reversed) continue;
22
+ const [e, s] = [t[o].id, t[n].id].sort();
23
+ f.set(a(e, s), { id: `open:${e}:${s}`, a: e, b: s, attests: "none" });
24
+ }
25
+ return [...f.values()];
26
+ }
27
+ function m(r) {
28
+ const i = h(r), d = new Set(i.map((e) => a(e.a, e.b))), f = new Set(r.objects.filter((e) => {
29
+ var s;
30
+ return ((s = e.geometry) == null ? void 0 : s.mode) === "boundaries";
31
+ }).map((e) => e.id)), c = (r.portals ?? []).filter(
32
+ (e) => !e.id.startsWith("open:") || !f.has(e.a) || !f.has(e.b) || d.has(a(e.a, e.b))
33
+ ), t = new Set(
34
+ c.filter((e) => !e.id.startsWith("open:") && !e.id.startsWith("inferred:")).map((e) => a(e.a, e.b))
35
+ ), o = c.filter((e) => !e.id.startsWith("open:") || !t.has(a(e.a, e.b))), n = new Set(o.map((e) => a(e.a, e.b)));
36
+ return [...o, ...i.filter((e) => !n.has(a(e.a, e.b)))];
37
+ }
38
+ export {
39
+ m as effectivePortals,
40
+ h as sharedBoundaryPortals
41
+ };
42
+ //# sourceMappingURL=portals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portals.js","sources":["../../../../src/model/portals.ts"],"sourcesContent":["import { isSpace, type Portal, type ProjectDocument } from './types';\n\nconst pairKey = (a: string, b: string) => JSON.stringify([a, b].sort());\n\n/** A shared virtual edge explicitly declares an unwalled connection. Read its two sides directly;\n * sampling inset room outlines loses narrow openings near the ends of adjoining walls. */\nexport function sharedBoundaryPortals(project: ProjectDocument): Portal[] {\n const edges = new Map((project.virtualBoundaries ?? []).map(edge => [edge.id, edge]));\n if (!edges.size) return [];\n const sides = new Map<string, { id: string; reversed: boolean }[]>();\n for (const space of project.objects) {\n if (!isSpace(space.kind) || space.geometry?.mode !== 'boundaries') continue;\n for (const loop of space.geometry.loops)\n for (const use of loop) {\n const edge = edges.get(use.edgeId);\n if (!edge || edge.floorId !== space.floorId) continue;\n const owners = sides.get(edge.id) ?? [];\n if (!owners.some(owner => owner.id === space.id)) owners.push({ id: space.id, reversed: !!use.reversed });\n sides.set(edge.id, owners);\n }\n }\n const out = new Map<string, Portal>();\n for (const owners of sides.values())\n for (let i = 0; i < owners.length; i++)\n for (let j = i + 1; j < owners.length; j++) {\n if (owners[i].reversed === owners[j].reversed) continue;\n const [a, b] = [owners[i].id, owners[j].id].sort();\n out.set(pairKey(a, b), { id: `open:${a}:${b}`, a, b, attests: 'none' });\n }\n return [...out.values()];\n}\n\n/** The readable portal layer, including explicit shared openings in older saved plans. A stored\n * portal takes precedence, including sealed and one-way passage. No document mutation is needed.\n * Once both spaces use shared boundaries, an inferred open connection also disappears when its\n * virtual edge is replaced by a wall. Legacy independent outlines retain their inferred portals. */\nexport function effectivePortals(project: ProjectDocument): Portal[] {\n const shared = sharedBoundaryPortals(project);\n const pairs = new Set(shared.map(p => pairKey(p.a, p.b)));\n const bound = new Set(project.objects.filter(o => o.geometry?.mode === 'boundaries').map(o => o.id));\n const stored = (project.portals ?? []).filter(\n p => !p.id.startsWith('open:') || !bound.has(p.a) || !bound.has(p.b) || pairs.has(pairKey(p.a, p.b)),\n );\n const authoredPairs = new Set(\n stored.filter(p => !p.id.startsWith('open:') && !p.id.startsWith('inferred:')).map(p => pairKey(p.a, p.b)),\n );\n const retained = stored.filter(p => !p.id.startsWith('open:') || !authoredPairs.has(pairKey(p.a, p.b)));\n const held = new Set(retained.map(p => pairKey(p.a, p.b)));\n return [...retained, ...shared.filter(p => !held.has(pairKey(p.a, p.b)))];\n}\n"],"names":["pairKey","a","b","sharedBoundaryPortals","project","edges","edge","sides","space","isSpace","_a","loop","use","owners","owner","out","i","j","effectivePortals","shared","pairs","p","bound","o","stored","authoredPairs","retained","held"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ const E = 1e-6, _ = 1e-3, o = 0.01, t = 1e-3, A = 1, c = 1e-8, n = 1e4;
2
+ export {
3
+ n as CLIP_SCALE,
4
+ E as GEOMETRY_EPS,
5
+ _ as JOIN_EPS,
6
+ c as MIN_FACE_AREA,
7
+ t as MIN_RING_EDGE,
8
+ A as MIN_SPACE_AREA,
9
+ o as MIN_WALL_LENGTH
10
+ };
11
+ //# sourceMappingURL=precision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precision.js","sources":["../../../../src/model/precision.ts"],"sourcesContent":["/** Numerical tolerances are independent of the user's positioning grid. All values are metres. */\nexport const GEOMETRY_EPS = 1e-6;\nexport const JOIN_EPS = 0.001;\nexport const MIN_WALL_LENGTH = 0.01;\nexport const MIN_RING_EDGE = 0.001;\n/** Usable floor area, after subtracting holes and physical walls, in square metres. */\nexport const MIN_SPACE_AREA = 1;\n/** Topological faces and holes may be smaller than a labelled space. Square metres. */\nexport const MIN_FACE_AREA = 1e-8;\n/** Only derived polygon-clipping inputs use this precision; junctions retain their coordinates. */\nexport const CLIP_SCALE = 1e4;\n"],"names":["GEOMETRY_EPS","JOIN_EPS","MIN_WALL_LENGTH","MIN_RING_EDGE","MIN_SPACE_AREA","MIN_FACE_AREA","CLIP_SCALE"],"mappings":"AACO,MAAMA,IAAe,MACfC,IAAW,MACXC,IAAkB,MAClBC,IAAgB,MAEhBC,IAAiB,GAEjBC,IAAgB,MAEhBC,IAAa;"}
@@ -0,0 +1,31 @@
1
+ import { rotate as e, add as f } from "./geometry.js";
2
+ function m(n) {
3
+ const o = structuredClone(n);
4
+ return o.id = crypto.randomUUID(), o.name = `${n.name} copy`, o.updatedAt = (/* @__PURE__ */ new Date()).toISOString(), o.objects.forEach((i) => {
5
+ delete i.feedId;
6
+ }), o;
7
+ }
8
+ function p(n, o) {
9
+ const i = n.rotation, t = o.position ?? n.position;
10
+ if (n.rings) {
11
+ const r = (o.width ?? n.width) / n.width, l = (o.depth ?? n.depth) / n.depth;
12
+ n.rings = n.rings.map(
13
+ (a) => a.map((d) => {
14
+ const s = e([d[0] - n.position[0], d[1] - n.position[1]], -i);
15
+ return f(t, e([s[0] * r, s[1] * l], o.rotation ?? i));
16
+ })
17
+ );
18
+ }
19
+ Object.assign(n, o);
20
+ }
21
+ function g(n) {
22
+ var i, t;
23
+ const o = n.initialFloorId;
24
+ return o === null ? null : o !== void 0 && n.floors.some((r) => r.id === o) ? o : ((i = n.floors.find((r) => r.elevation === 0)) == null ? void 0 : i.id) ?? ((t = n.floors[0]) == null ? void 0 : t.id) ?? null;
25
+ }
26
+ export {
27
+ m as copyProject,
28
+ g as openingFloorId,
29
+ p as transformObject
30
+ };
31
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sources":["../../../../src/model/project.ts"],"sourcesContent":["import type { ProjectDocument, SiteObject } from './types';\nimport { add, rotate } from './geometry';\n\nexport function copyProject(project: ProjectDocument): ProjectDocument {\n const copy = structuredClone(project);\n copy.id = crypto.randomUUID();\n copy.name = `${project.name} copy`;\n copy.updatedAt = new Date().toISOString();\n copy.objects.forEach(object => {\n delete object.feedId;\n });\n return copy;\n}\n\n/** Apply dimensions in the object's own axes, then rotate and translate its polygon. */\nexport function transformObject(object: SiteObject, patch: Partial<SiteObject>) {\n const oldRotation = object.rotation;\n const target = patch.position ?? object.position;\n if (object.rings) {\n const sx = (patch.width ?? object.width) / object.width;\n const sy = (patch.depth ?? object.depth) / object.depth;\n object.rings = object.rings.map(ring =>\n ring.map(point => {\n const local = rotate([point[0] - object.position[0], point[1] - object.position[1]], -oldRotation);\n return add(target, rotate([local[0] * sx, local[1] * sy], patch.rotation ?? oldRotation));\n }),\n );\n }\n Object.assign(object, patch);\n}\n\n/** The floor a viewer should open on: the document's own `initialFloorId` when it names a floor that\n * still exists, otherwise the ground floor, otherwise the lowest one. An explicit `null` is a real\n * answer — it means the outdoor site — so it is distinguished from the field being absent. */\nexport function openingFloorId(project: ProjectDocument): string | null {\n const preferred = project.initialFloorId;\n if (preferred === null) return null;\n if (preferred !== undefined && project.floors.some(f => f.id === preferred)) return preferred;\n return project.floors.find(f => f.elevation === 0)?.id ?? project.floors[0]?.id ?? null;\n}\n"],"names":["copyProject","project","copy","object","transformObject","patch","oldRotation","target","sx","sy","ring","point","local","rotate","add","openingFloorId","preferred","f","_a","_b"],"mappings":";AAGO,SAASA,EAAYC,GAA2C;AACrE,QAAMC,IAAO,gBAAgBD,CAAO;AACpC,SAAAC,EAAK,KAAK,OAAO,WAAA,GACjBA,EAAK,OAAO,GAAGD,EAAQ,IAAI,SAC3BC,EAAK,aAAY,oBAAI,KAAA,GAAO,YAAA,GAC5BA,EAAK,QAAQ,QAAQ,CAAAC,MAAU;AAC7B,WAAOA,EAAO;AAAA,EAChB,CAAC,GACMD;AACT;AAGO,SAASE,EAAgBD,GAAoBE,GAA4B;AAC9E,QAAMC,IAAcH,EAAO,UACrBI,IAASF,EAAM,YAAYF,EAAO;AACxC,MAAIA,EAAO,OAAO;AAChB,UAAMK,KAAMH,EAAM,SAASF,EAAO,SAASA,EAAO,OAC5CM,KAAMJ,EAAM,SAASF,EAAO,SAASA,EAAO;AAClD,IAAAA,EAAO,QAAQA,EAAO,MAAM;AAAA,MAAI,CAAAO,MAC9BA,EAAK,IAAI,CAAAC,MAAS;AAChB,cAAMC,IAAQC,EAAO,CAACF,EAAM,CAAC,IAAIR,EAAO,SAAS,CAAC,GAAGQ,EAAM,CAAC,IAAIR,EAAO,SAAS,CAAC,CAAC,GAAG,CAACG,CAAW;AACjG,eAAOQ,EAAIP,GAAQM,EAAO,CAACD,EAAM,CAAC,IAAIJ,GAAII,EAAM,CAAC,IAAIH,CAAE,GAAGJ,EAAM,YAAYC,CAAW,CAAC;AAAA,MAC1F,CAAC;AAAA,IAAA;AAAA,EAEL;AACA,SAAO,OAAOH,GAAQE,CAAK;AAC7B;AAKO,SAASU,EAAed,GAAyC;;AACtE,QAAMe,IAAYf,EAAQ;AAC1B,SAAIe,MAAc,OAAa,OAC3BA,MAAc,UAAaf,EAAQ,OAAO,KAAK,OAAKgB,EAAE,OAAOD,CAAS,IAAUA,MAC7EE,IAAAjB,EAAQ,OAAO,KAAK,CAAAgB,MAAKA,EAAE,cAAc,CAAC,MAA1C,gBAAAC,EAA6C,SAAMC,IAAAlB,EAAQ,OAAO,CAAC,MAAhB,gBAAAkB,EAAmB,OAAM;AACrF;"}
@@ -0,0 +1,10 @@
1
+ const d = (l, o) => !!l.slope && [l.slope.low, l.slope.high].some((s) => Math.abs(s - o) < 0.01);
2
+ function p(l, o) {
3
+ const s = l.floors.find((i) => i.id === o.floorId), e = (i) => l.floors.find((n) => n.buildingId === (s == null ? void 0 : s.buildingId) && Math.abs(n.elevation - i) < 0.01);
4
+ return { low: o.slope && e(o.slope.low), high: o.slope && e(o.slope.high) };
5
+ }
6
+ export {
7
+ p as rampFloors,
8
+ d as rampJoins
9
+ };
10
+ //# sourceMappingURL=ramps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ramps.js","sources":["../../../../src/model/ramps.ts"],"sourcesContent":["import type { ProjectDocument, SiteObject } from './types';\n\nexport const rampJoins = (o: SiteObject, elevation: number) =>\n !!o.slope && [o.slope.low, o.slope.high].some(end => Math.abs(end - elevation) < 0.01);\n\n/** Only the end levels in the ramp's building are connected, never a storey it passes through. */\nexport function rampFloors(project: ProjectDocument, o: SiteObject) {\n const own = project.floors.find(f => f.id === o.floorId);\n const end = (height: number) =>\n project.floors.find(f => f.buildingId === own?.buildingId && Math.abs(f.elevation - height) < 0.01);\n return { low: o.slope && end(o.slope.low), high: o.slope && end(o.slope.high) };\n}\n"],"names":["rampJoins","o","elevation","end","rampFloors","project","own","f","height"],"mappings":"AAEO,MAAMA,IAAY,CAACC,GAAeC,MACvC,CAAC,CAACD,EAAE,SAAS,CAACA,EAAE,MAAM,KAAKA,EAAE,MAAM,IAAI,EAAE,KAAK,CAAAE,MAAO,KAAK,IAAIA,IAAMD,CAAS,IAAI,IAAI;AAGhF,SAASE,EAAWC,GAA0B,GAAe;AAClE,QAAMC,IAAMD,EAAQ,OAAO,KAAK,OAAKE,EAAE,OAAO,EAAE,OAAO,GACjDJ,IAAM,CAACK,MACXH,EAAQ,OAAO,KAAK,CAAAE,MAAKA,EAAE,gBAAeD,KAAA,gBAAAA,EAAK,eAAc,KAAK,IAAIC,EAAE,YAAYC,CAAM,IAAI,IAAI;AACpG,SAAO,EAAE,KAAK,EAAE,SAASL,EAAI,EAAE,MAAM,GAAG,GAAG,MAAM,EAAE,SAASA,EAAI,EAAE,MAAM,IAAI,EAAA;AAC9E;"}