@pascal-app/core 0.7.0 → 0.9.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 (318) hide show
  1. package/dist/events/bus.d.ts +47 -18
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  5. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  6. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  19. package/dist/index.d.ts +22 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +19 -2
  22. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  23. package/dist/lib/polygon-relations.d.ts +10 -0
  24. package/dist/lib/polygon-relations.d.ts.map +1 -0
  25. package/dist/lib/polygon-relations.js +73 -0
  26. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  27. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  28. package/dist/lib/space-detection-pause.test.js +42 -0
  29. package/dist/lib/space-detection.d.ts +30 -1
  30. package/dist/lib/space-detection.d.ts.map +1 -1
  31. package/dist/lib/space-detection.js +222 -41
  32. package/dist/lib/space-detection.test.d.ts +2 -0
  33. package/dist/lib/space-detection.test.d.ts.map +1 -0
  34. package/dist/lib/space-detection.test.js +77 -0
  35. package/dist/material-library.d.ts +1 -1
  36. package/dist/material-library.d.ts.map +1 -1
  37. package/dist/material-library.js +1957 -141
  38. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  39. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  40. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  41. package/dist/registry/handles.d.ts +321 -0
  42. package/dist/registry/handles.d.ts.map +1 -0
  43. package/dist/registry/handles.js +24 -0
  44. package/dist/registry/index.d.ts +7 -0
  45. package/dist/registry/index.d.ts.map +1 -0
  46. package/dist/registry/index.js +4 -0
  47. package/dist/registry/registry.d.ts +93 -0
  48. package/dist/registry/registry.d.ts.map +1 -0
  49. package/dist/registry/registry.js +201 -0
  50. package/dist/registry/registry.test.d.ts +2 -0
  51. package/dist/registry/registry.test.d.ts.map +1 -0
  52. package/dist/registry/registry.test.js +183 -0
  53. package/dist/registry/relations-resolver.d.ts +42 -0
  54. package/dist/registry/relations-resolver.d.ts.map +1 -0
  55. package/dist/registry/relations-resolver.js +77 -0
  56. package/dist/registry/relations-resolver.test.d.ts +2 -0
  57. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  58. package/dist/registry/relations-resolver.test.js +183 -0
  59. package/dist/registry/scene-api.d.ts +44 -0
  60. package/dist/registry/scene-api.d.ts.map +1 -0
  61. package/dist/registry/scene-api.js +111 -0
  62. package/dist/registry/scene-api.test.d.ts +2 -0
  63. package/dist/registry/scene-api.test.d.ts.map +1 -0
  64. package/dist/registry/scene-api.test.js +183 -0
  65. package/dist/registry/subtree.d.ts +67 -0
  66. package/dist/registry/subtree.d.ts.map +1 -0
  67. package/dist/registry/subtree.js +119 -0
  68. package/dist/registry/subtree.test.d.ts +2 -0
  69. package/dist/registry/subtree.test.d.ts.map +1 -0
  70. package/dist/registry/subtree.test.js +133 -0
  71. package/dist/registry/types.d.ts +1420 -0
  72. package/dist/registry/types.d.ts.map +1 -0
  73. package/dist/registry/types.js +1 -0
  74. package/dist/schema/asset-url.test.js +0 -4
  75. package/dist/schema/index.d.ts +17 -2
  76. package/dist/schema/index.d.ts.map +1 -1
  77. package/dist/schema/index.js +17 -3
  78. package/dist/schema/material.d.ts +12 -2
  79. package/dist/schema/material.d.ts.map +1 -1
  80. package/dist/schema/material.js +10 -0
  81. package/dist/schema/nodes/box-vent.d.ts +74 -0
  82. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  83. package/dist/schema/nodes/box-vent.js +63 -0
  84. package/dist/schema/nodes/building.d.ts +1 -1
  85. package/dist/schema/nodes/building.d.ts.map +1 -1
  86. package/dist/schema/nodes/building.js +3 -2
  87. package/dist/schema/nodes/ceiling.d.ts +3 -1
  88. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  89. package/dist/schema/nodes/ceiling.js +1 -1
  90. package/dist/schema/nodes/chimney.d.ts +158 -0
  91. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  92. package/dist/schema/nodes/chimney.js +67 -0
  93. package/dist/schema/nodes/column.d.ts +598 -6
  94. package/dist/schema/nodes/column.d.ts.map +1 -1
  95. package/dist/schema/nodes/column.js +586 -6
  96. package/dist/schema/nodes/cupola.d.ts +66 -0
  97. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  98. package/dist/schema/nodes/cupola.js +28 -0
  99. package/dist/schema/nodes/door.d.ts +1 -1
  100. package/dist/schema/nodes/dormer.d.ts +228 -0
  101. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  102. package/dist/schema/nodes/dormer.js +119 -0
  103. package/dist/schema/nodes/downspout.d.ts +75 -0
  104. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  105. package/dist/schema/nodes/downspout.js +63 -0
  106. package/dist/schema/nodes/elevator.d.ts +105 -0
  107. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  108. package/dist/schema/nodes/elevator.js +54 -0
  109. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  110. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  111. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  112. package/dist/schema/nodes/fence.d.ts +3 -2
  113. package/dist/schema/nodes/fence.d.ts.map +1 -1
  114. package/dist/schema/nodes/fence.js +4 -2
  115. package/dist/schema/nodes/gutter.d.ts +84 -0
  116. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  117. package/dist/schema/nodes/gutter.js +76 -0
  118. package/dist/schema/nodes/item.d.ts +16 -0
  119. package/dist/schema/nodes/item.d.ts.map +1 -1
  120. package/dist/schema/nodes/item.js +21 -0
  121. package/dist/schema/nodes/level.d.ts +1 -1
  122. package/dist/schema/nodes/level.d.ts.map +1 -1
  123. package/dist/schema/nodes/level.js +2 -0
  124. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  125. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  126. package/dist/schema/nodes/ridge-vent.js +30 -0
  127. package/dist/schema/nodes/roof-segment.d.ts +198 -4
  128. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  129. package/dist/schema/nodes/roof-segment.js +254 -3
  130. package/dist/schema/nodes/roof.d.ts +6 -6
  131. package/dist/schema/nodes/roof.d.ts.map +1 -1
  132. package/dist/schema/nodes/roof.js +9 -21
  133. package/dist/schema/nodes/shelf.d.ts +105 -0
  134. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  135. package/dist/schema/nodes/shelf.js +84 -0
  136. package/dist/schema/nodes/site.d.ts +1 -116
  137. package/dist/schema/nodes/site.d.ts.map +1 -1
  138. package/dist/schema/nodes/site.js +2 -6
  139. package/dist/schema/nodes/skylight.d.ts +279 -0
  140. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  141. package/dist/schema/nodes/skylight.js +152 -0
  142. package/dist/schema/nodes/slab.d.ts +3 -1
  143. package/dist/schema/nodes/slab.d.ts.map +1 -1
  144. package/dist/schema/nodes/slab.js +1 -1
  145. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  146. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  147. package/dist/schema/nodes/solar-panel.js +47 -0
  148. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  149. package/dist/schema/nodes/stair.d.ts +6 -6
  150. package/dist/schema/nodes/stair.d.ts.map +1 -1
  151. package/dist/schema/nodes/stair.js +9 -7
  152. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  153. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  154. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  155. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  156. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  157. package/dist/schema/nodes/turbine-vent.js +41 -0
  158. package/dist/schema/nodes/wall.d.ts +3 -3
  159. package/dist/schema/nodes/window.d.ts +1 -1
  160. package/dist/schema/types.d.ts +1448 -236
  161. package/dist/schema/types.d.ts.map +1 -1
  162. package/dist/schema/types.js +26 -0
  163. package/dist/services/alignment-anchors.d.ts +100 -0
  164. package/dist/services/alignment-anchors.d.ts.map +1 -0
  165. package/dist/services/alignment-anchors.js +283 -0
  166. package/dist/services/alignment-anchors.test.d.ts +2 -0
  167. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  168. package/dist/services/alignment-anchors.test.js +383 -0
  169. package/dist/services/alignment.d.ts +126 -0
  170. package/dist/services/alignment.d.ts.map +1 -0
  171. package/dist/services/alignment.js +169 -0
  172. package/dist/services/alignment.test.d.ts +2 -0
  173. package/dist/services/alignment.test.d.ts.map +1 -0
  174. package/dist/services/alignment.test.js +92 -0
  175. package/dist/services/drag-session.d.ts +47 -0
  176. package/dist/services/drag-session.d.ts.map +1 -0
  177. package/dist/services/drag-session.js +100 -0
  178. package/dist/services/drag-session.test.d.ts +2 -0
  179. package/dist/services/drag-session.test.d.ts.map +1 -0
  180. package/dist/services/drag-session.test.js +219 -0
  181. package/dist/services/hosting.d.ts +80 -0
  182. package/dist/services/hosting.d.ts.map +1 -0
  183. package/dist/services/hosting.js +122 -0
  184. package/dist/services/hosting.test.d.ts +2 -0
  185. package/dist/services/hosting.test.d.ts.map +1 -0
  186. package/dist/services/hosting.test.js +199 -0
  187. package/dist/services/index.d.ts +7 -0
  188. package/dist/services/index.d.ts.map +1 -0
  189. package/dist/services/index.js +6 -0
  190. package/dist/services/movement.d.ts +48 -0
  191. package/dist/services/movement.d.ts.map +1 -0
  192. package/dist/services/movement.js +65 -0
  193. package/dist/services/movement.test.d.ts +2 -0
  194. package/dist/services/movement.test.d.ts.map +1 -0
  195. package/dist/services/movement.test.js +133 -0
  196. package/dist/services/single-undo-dance.test.d.ts +2 -0
  197. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  198. package/dist/services/single-undo-dance.test.js +210 -0
  199. package/dist/services/snap.d.ts +72 -0
  200. package/dist/services/snap.d.ts.map +1 -0
  201. package/dist/services/snap.js +120 -0
  202. package/dist/services/snap.test.d.ts +2 -0
  203. package/dist/services/snap.test.d.ts.map +1 -0
  204. package/dist/services/snap.test.js +95 -0
  205. package/dist/solar-panel-presets.d.ts +17 -0
  206. package/dist/solar-panel-presets.d.ts.map +1 -0
  207. package/dist/solar-panel-presets.js +43 -0
  208. package/dist/store/actions/node-actions.d.ts +14 -2
  209. package/dist/store/actions/node-actions.d.ts.map +1 -1
  210. package/dist/store/actions/node-actions.js +499 -20
  211. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  212. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  213. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  214. package/dist/store/actions/reparent.test.d.ts +2 -0
  215. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  216. package/dist/store/actions/reparent.test.js +212 -0
  217. package/dist/store/use-interactive.d.ts +39 -0
  218. package/dist/store/use-interactive.d.ts.map +1 -1
  219. package/dist/store/use-interactive.js +75 -0
  220. package/dist/store/use-live-node-overrides.d.ts +22 -0
  221. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  222. package/dist/store/use-live-node-overrides.js +59 -0
  223. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  224. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  225. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  226. package/dist/store/use-scene.d.ts +11 -0
  227. package/dist/store/use-scene.d.ts.map +1 -1
  228. package/dist/store/use-scene.js +207 -11
  229. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  230. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  231. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  232. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  233. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  234. package/dist/systems/elevator/elevator-geometry.js +50 -0
  235. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  236. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  237. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  238. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  239. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  240. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  241. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  242. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  243. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  244. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  245. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  246. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  247. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  248. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  249. package/dist/systems/elevator/elevator-runtime.js +225 -0
  250. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  251. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  252. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  253. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  254. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  255. package/dist/systems/elevator/elevator-service.js +111 -0
  256. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  257. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  258. package/dist/systems/stair/stair-footprint.js +168 -0
  259. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  260. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  261. package/dist/systems/stair/stair-opening-preview.js +93 -0
  262. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  263. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  264. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  265. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  266. package/dist/systems/stair/stair-opening-sync.js +183 -185
  267. package/dist/systems/stair/stair-opening-sync.test.js +385 -4
  268. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  269. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  270. package/dist/systems/stair/stair-opening-system.js +104 -0
  271. package/dist/systems/wall/wall-curve.d.ts +1 -1
  272. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  273. package/dist/systems/wall/wall-curve.js +1 -1
  274. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  275. package/dist/systems/wall/wall-mitering.js +2 -6
  276. package/dist/systems/wall/wall-move.d.ts +48 -0
  277. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  278. package/dist/systems/wall/wall-move.js +193 -0
  279. package/dist/utils/clone-scene-graph.d.ts +6 -4
  280. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  281. package/dist/utils/clone-scene-graph.js +6 -4
  282. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  283. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  284. package/dist/utils/clone-scene-graph.test.js +59 -0
  285. package/dist/validation/validate-build-json.d.ts +43 -0
  286. package/dist/validation/validate-build-json.d.ts.map +1 -0
  287. package/dist/validation/validate-build-json.js +227 -0
  288. package/package.json +11 -3
  289. package/dist/materials.d.ts +0 -10
  290. package/dist/materials.d.ts.map +0 -1
  291. package/dist/materials.js +0 -22
  292. package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
  293. package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
  294. package/dist/systems/ceiling/ceiling-system.js +0 -92
  295. package/dist/systems/door/door-system.d.ts +0 -2
  296. package/dist/systems/door/door-system.d.ts.map +0 -1
  297. package/dist/systems/door/door-system.js +0 -195
  298. package/dist/systems/fence/fence-system.d.ts +0 -2
  299. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  300. package/dist/systems/fence/fence-system.js +0 -187
  301. package/dist/systems/item/item-system.d.ts +0 -2
  302. package/dist/systems/item/item-system.d.ts.map +0 -1
  303. package/dist/systems/item/item-system.js +0 -48
  304. package/dist/systems/roof/roof-system.d.ts +0 -16
  305. package/dist/systems/roof/roof-system.d.ts.map +0 -1
  306. package/dist/systems/roof/roof-system.js +0 -797
  307. package/dist/systems/slab/slab-system.d.ts +0 -8
  308. package/dist/systems/slab/slab-system.d.ts.map +0 -1
  309. package/dist/systems/slab/slab-system.js +0 -214
  310. package/dist/systems/stair/stair-system.d.ts +0 -2
  311. package/dist/systems/stair/stair-system.d.ts.map +0 -1
  312. package/dist/systems/stair/stair-system.js +0 -776
  313. package/dist/systems/wall/wall-system.d.ts +0 -12
  314. package/dist/systems/wall/wall-system.d.ts.map +0 -1
  315. package/dist/systems/wall/wall-system.js +0 -455
  316. package/dist/systems/window/window-system.d.ts +0 -2
  317. package/dist/systems/window/window-system.d.ts.map +0 -1
  318. package/dist/systems/window/window-system.js +0 -131
@@ -0,0 +1,218 @@
1
+ import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
2
+ import { resolveElevatorServiceLevels } from './elevator-service';
3
+ const ELEVATOR_OPENING_PADDING = 0.08;
4
+ const DEFAULT_ELEVATOR_SHAFT_WALL_THICKNESS = 0.09;
5
+ function pointsEqual(a, b, tolerance = 1e-5) {
6
+ const dx = a[0] - b[0];
7
+ const dz = a[1] - b[1];
8
+ return dx * dx + dz * dz <= tolerance * tolerance;
9
+ }
10
+ function polygonsEqual(left, right) {
11
+ if (left.length !== right.length)
12
+ return false;
13
+ return left.every((polygon, polygonIndex) => {
14
+ const other = right[polygonIndex];
15
+ if (!(other && polygon.length === other.length))
16
+ return false;
17
+ return polygon.every((point, pointIndex) => {
18
+ const otherPoint = other[pointIndex];
19
+ if (!otherPoint)
20
+ return false;
21
+ return pointsEqual(point, otherPoint);
22
+ });
23
+ });
24
+ }
25
+ function metadataEqual(left, right) {
26
+ if (left.length !== right.length)
27
+ return false;
28
+ return left.every((entry, index) => entry.source === right[index]?.source &&
29
+ (entry.elevatorId ?? null) === (right[index]?.elevatorId ?? null) &&
30
+ (entry.stairId ?? null) === (right[index]?.stairId ?? null));
31
+ }
32
+ function normalizeExistingMetadata(holes, metadata) {
33
+ return holes.map((_, index) => metadata?.[index] ?? { source: 'manual' });
34
+ }
35
+ function rotateXZ(x, z, angle) {
36
+ const cos = Math.cos(angle);
37
+ const sin = Math.sin(angle);
38
+ return [x * cos + z * sin, -x * sin + z * cos];
39
+ }
40
+ function pointOnSegment(point, a, b, tolerance = 1e-6) {
41
+ const cross = (point[1] - a[1]) * (b[0] - a[0]) - (point[0] - a[0]) * (b[1] - a[1]);
42
+ if (Math.abs(cross) > tolerance)
43
+ return false;
44
+ const dot = (point[0] - a[0]) * (b[0] - a[0]) + (point[1] - a[1]) * (b[1] - a[1]);
45
+ if (dot < -tolerance)
46
+ return false;
47
+ const lenSq = (b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2;
48
+ return dot <= lenSq + tolerance;
49
+ }
50
+ function pointInPolygon(point, polygon) {
51
+ if (polygon.length < 3)
52
+ return false;
53
+ let inside = false;
54
+ const [x, z] = point;
55
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
56
+ const a = polygon[i];
57
+ const b = polygon[j];
58
+ if (pointOnSegment(point, a, b))
59
+ return true;
60
+ const intersects = a[1] > z !== b[1] > z && x < ((b[0] - a[0]) * (z - a[1])) / (b[1] - a[1]) + a[0];
61
+ if (intersects)
62
+ inside = !inside;
63
+ }
64
+ return inside;
65
+ }
66
+ function polygonContainsPolygon(outer, inner) {
67
+ return inner.every((point) => pointInPolygon(point, outer));
68
+ }
69
+ function isCoveredByExistingHole(existingHoles, autoHole) {
70
+ return existingHoles.some((existingHole) => polygonContainsPolygon(existingHole, autoHole));
71
+ }
72
+ function getServedLevelRange(elevator, nodes) {
73
+ const servedLevels = resolveElevatorServiceLevels(elevator, nodes);
74
+ if (servedLevels.length < 2)
75
+ return null;
76
+ const levelNumbers = servedLevels.map((level) => level.level);
77
+ return {
78
+ max: Math.max(...levelNumbers),
79
+ min: Math.min(...levelNumbers),
80
+ sortedServedLevels: servedLevels,
81
+ };
82
+ }
83
+ function getLevelNumber(levelId, nodes) {
84
+ if (!levelId)
85
+ return undefined;
86
+ const node = nodes[levelId];
87
+ return node?.type === 'level' ? node.level : undefined;
88
+ }
89
+ function shouldApplyElevatorToSlab(elevator, slabLevelId, nodes) {
90
+ const range = getServedLevelRange(elevator, nodes);
91
+ if (!range)
92
+ return false;
93
+ const slabLevel = getLevelNumber(slabLevelId, nodes);
94
+ if (slabLevel !== undefined) {
95
+ return slabLevel > range.min && slabLevel <= range.max;
96
+ }
97
+ const servedIndex = range.sortedServedLevels.findIndex((level) => level.id === slabLevelId);
98
+ return servedIndex > 0;
99
+ }
100
+ function shouldApplyElevatorToCeiling(elevator, ceilingLevelId, nodes) {
101
+ const range = getServedLevelRange(elevator, nodes);
102
+ if (!range)
103
+ return false;
104
+ const ceilingLevel = getLevelNumber(ceilingLevelId, nodes);
105
+ if (ceilingLevel !== undefined) {
106
+ return ceilingLevel >= range.min && ceilingLevel < range.max;
107
+ }
108
+ const servedIndex = range.sortedServedLevels.findIndex((level) => level.id === ceilingLevelId);
109
+ return servedIndex >= 0 && servedIndex < range.sortedServedLevels.length - 1;
110
+ }
111
+ function getElevatorOpeningPolygon(elevator) {
112
+ const wallThickness = Math.max(elevator.shaftWallThickness ?? DEFAULT_ELEVATOR_SHAFT_WALL_THICKNESS, 0.04);
113
+ const shaftWidth = Math.max(elevator.shaftWidth ?? elevator.width, elevator.width, 0.8);
114
+ const shaftDepth = Math.max(elevator.shaftDepth ?? elevator.depth, elevator.depth, 0.8);
115
+ const halfWidth = shaftWidth / 2 + wallThickness + ELEVATOR_OPENING_PADDING;
116
+ const halfDepth = shaftDepth / 2 + wallThickness + ELEVATOR_OPENING_PADDING;
117
+ const corners = [
118
+ [-halfWidth, -halfDepth],
119
+ [halfWidth, -halfDepth],
120
+ [halfWidth, halfDepth],
121
+ [-halfWidth, halfDepth],
122
+ ];
123
+ return corners.map(([x, z]) => {
124
+ const [rotatedX, rotatedZ] = rotateXZ(x, z, elevator.rotation ?? 0);
125
+ return [elevator.position[0] + rotatedX, elevator.position[2] + rotatedZ];
126
+ });
127
+ }
128
+ export function syncAutoElevatorOpenings(nodes) {
129
+ const elevators = Object.values(nodes).filter((node) => node.type === 'elevator' && node.visible !== false);
130
+ const slabs = Object.values(nodes).filter((node) => node.type === 'slab');
131
+ const ceilings = Object.values(nodes).filter((node) => node.type === 'ceiling');
132
+ const updates = [];
133
+ for (const slab of slabs) {
134
+ const slabLevelId = resolveLevelId(slab, nodes);
135
+ const existingHoles = slab.holes ?? [];
136
+ const existingMetadata = normalizeExistingMetadata(existingHoles, slab.holeMetadata);
137
+ const preservedHoles = existingHoles
138
+ .map((polygon, index) => ({ metadata: existingMetadata[index], polygon }))
139
+ .filter((entry) => entry.metadata.source !== 'elevator');
140
+ const manualHoles = preservedHoles.filter((entry) => entry.metadata.source !== 'stair');
141
+ const stairHoles = preservedHoles.filter((entry) => entry.metadata.source === 'stair');
142
+ const preservedHolePolygons = preservedHoles.map((entry) => entry.polygon);
143
+ const elevatorHoles = elevators
144
+ .filter((elevator) => shouldApplyElevatorToSlab(elevator, slabLevelId, nodes))
145
+ .map((elevator) => ({
146
+ polygon: getElevatorOpeningPolygon(elevator),
147
+ metadata: {
148
+ elevatorId: elevator.id,
149
+ source: 'elevator',
150
+ },
151
+ }))
152
+ .filter((hole) => polygonContainsPolygon(slab.polygon, hole.polygon))
153
+ .filter((hole) => !isCoveredByExistingHole(preservedHolePolygons, hole.polygon));
154
+ const nextHoles = [
155
+ ...manualHoles.map((hole) => hole.polygon),
156
+ ...elevatorHoles.map((hole) => hole.polygon),
157
+ ...stairHoles.map((hole) => hole.polygon),
158
+ ];
159
+ const nextMetadata = [
160
+ ...manualHoles.map((hole) => ({ ...hole.metadata })),
161
+ ...elevatorHoles.map((hole) => hole.metadata),
162
+ ...stairHoles.map((hole) => ({ ...hole.metadata })),
163
+ ];
164
+ if (!polygonsEqual(existingHoles, nextHoles) ||
165
+ !metadataEqual(existingMetadata, nextMetadata)) {
166
+ updates.push({
167
+ id: slab.id,
168
+ data: {
169
+ holes: nextHoles,
170
+ holeMetadata: nextMetadata,
171
+ },
172
+ });
173
+ }
174
+ }
175
+ for (const ceiling of ceilings) {
176
+ const ceilingLevelId = resolveLevelId(ceiling, nodes);
177
+ const existingHoles = ceiling.holes ?? [];
178
+ const existingMetadata = normalizeExistingMetadata(existingHoles, ceiling.holeMetadata);
179
+ const preservedHoles = existingHoles
180
+ .map((polygon, index) => ({ metadata: existingMetadata[index], polygon }))
181
+ .filter((entry) => entry.metadata.source !== 'elevator');
182
+ const manualHoles = preservedHoles.filter((entry) => entry.metadata.source !== 'stair');
183
+ const stairHoles = preservedHoles.filter((entry) => entry.metadata.source === 'stair');
184
+ const preservedHolePolygons = preservedHoles.map((entry) => entry.polygon);
185
+ const elevatorHoles = elevators
186
+ .filter((elevator) => shouldApplyElevatorToCeiling(elevator, ceilingLevelId, nodes))
187
+ .map((elevator) => ({
188
+ polygon: getElevatorOpeningPolygon(elevator),
189
+ metadata: {
190
+ elevatorId: elevator.id,
191
+ source: 'elevator',
192
+ },
193
+ }))
194
+ .filter((hole) => polygonContainsPolygon(ceiling.polygon, hole.polygon))
195
+ .filter((hole) => !isCoveredByExistingHole(preservedHolePolygons, hole.polygon));
196
+ const nextHoles = [
197
+ ...manualHoles.map((hole) => hole.polygon),
198
+ ...elevatorHoles.map((hole) => hole.polygon),
199
+ ...stairHoles.map((hole) => hole.polygon),
200
+ ];
201
+ const nextMetadata = [
202
+ ...manualHoles.map((hole) => ({ ...hole.metadata })),
203
+ ...elevatorHoles.map((hole) => hole.metadata),
204
+ ...stairHoles.map((hole) => ({ ...hole.metadata })),
205
+ ];
206
+ if (!polygonsEqual(existingHoles, nextHoles) ||
207
+ !metadataEqual(existingMetadata, nextMetadata)) {
208
+ updates.push({
209
+ id: ceiling.id,
210
+ data: {
211
+ holes: nextHoles,
212
+ holeMetadata: nextMetadata,
213
+ },
214
+ });
215
+ }
216
+ }
217
+ return updates;
218
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=elevator-opening-sync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevator-opening-sync.test.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-opening-sync.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,146 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { BuildingNode, CeilingNode, ElevatorNode, LevelNode, SlabNode } from '../../schema';
3
+ import { syncAutoElevatorOpenings } from './elevator-opening-sync';
4
+ describe('syncAutoElevatorOpenings', () => {
5
+ test('does not add elevator holes when a manual surface hole already covers them', () => {
6
+ const building = BuildingNode.parse({ name: 'Building' });
7
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
8
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
9
+ const elevator = ElevatorNode.parse({
10
+ name: 'Elevator',
11
+ parentId: building.id,
12
+ position: [2, 0, 1.5],
13
+ width: 1.6,
14
+ depth: 1.6,
15
+ });
16
+ const buildingWithChildren = {
17
+ ...building,
18
+ children: [ground.id, upper.id, elevator.id],
19
+ };
20
+ const manualOpening = [
21
+ [1, 0.5],
22
+ [3, 0.5],
23
+ [3, 2.5],
24
+ [1, 2.5],
25
+ ];
26
+ const sourceCeiling = CeilingNode.parse({
27
+ name: 'Source Ceiling',
28
+ parentId: ground.id,
29
+ polygon: [
30
+ [0, 0],
31
+ [4, 0],
32
+ [4, 3],
33
+ [0, 3],
34
+ ],
35
+ holes: [manualOpening],
36
+ holeMetadata: [{ source: 'manual' }],
37
+ });
38
+ const upperSlab = SlabNode.parse({
39
+ name: 'Upper Slab',
40
+ parentId: upper.id,
41
+ polygon: [
42
+ [0, 0],
43
+ [4, 0],
44
+ [4, 3],
45
+ [0, 3],
46
+ ],
47
+ holes: [manualOpening],
48
+ holeMetadata: [{ source: 'manual' }],
49
+ });
50
+ const nodes = Object.fromEntries([buildingWithChildren, ground, upper, elevator, sourceCeiling, upperSlab].map((node) => [
51
+ node.id,
52
+ node,
53
+ ]));
54
+ const updates = syncAutoElevatorOpenings(nodes);
55
+ expect(updates.find((update) => update.id === upperSlab.id)).toBeUndefined();
56
+ expect(updates.find((update) => update.id === sourceCeiling.id)).toBeUndefined();
57
+ });
58
+ test('adds elevator holes when an existing manual hole is too small', () => {
59
+ const building = BuildingNode.parse({ name: 'Building' });
60
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
61
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
62
+ const elevator = ElevatorNode.parse({
63
+ name: 'Elevator',
64
+ parentId: building.id,
65
+ position: [2, 0, 1.5],
66
+ width: 1.6,
67
+ depth: 1.6,
68
+ });
69
+ const buildingWithChildren = {
70
+ ...building,
71
+ children: [ground.id, upper.id, elevator.id],
72
+ };
73
+ const smallManualOpening = [
74
+ [1.7, 1.2],
75
+ [2.3, 1.2],
76
+ [2.3, 1.8],
77
+ [1.7, 1.8],
78
+ ];
79
+ const upperSlab = SlabNode.parse({
80
+ name: 'Upper Slab',
81
+ parentId: upper.id,
82
+ polygon: [
83
+ [0, 0],
84
+ [4, 0],
85
+ [4, 3],
86
+ [0, 3],
87
+ ],
88
+ holes: [smallManualOpening],
89
+ holeMetadata: [{ source: 'manual' }],
90
+ });
91
+ const nodes = Object.fromEntries([buildingWithChildren, ground, upper, elevator, upperSlab].map((node) => [node.id, node]));
92
+ const updates = syncAutoElevatorOpenings(nodes);
93
+ const slabUpdate = updates.find((update) => update.id === upperSlab.id);
94
+ expect(slabUpdate?.data.holes).toHaveLength(2);
95
+ expect(slabUpdate?.data.holes?.[0]).toEqual(smallManualOpening);
96
+ expect(slabUpdate?.data.holeMetadata).toEqual([
97
+ { source: 'manual' },
98
+ { source: 'elevator', elevatorId: elevator.id },
99
+ ]);
100
+ });
101
+ test('removes stale auto elevator holes when a manual hole overlaps the elevator opening', () => {
102
+ const building = BuildingNode.parse({ name: 'Building' });
103
+ const ground = LevelNode.parse({ name: 'Ground', level: 0, parentId: building.id });
104
+ const upper = LevelNode.parse({ name: 'Upper', level: 1, parentId: building.id });
105
+ const elevator = ElevatorNode.parse({
106
+ name: 'Elevator',
107
+ parentId: building.id,
108
+ position: [2, 0, 1.5],
109
+ width: 1.6,
110
+ depth: 1.6,
111
+ });
112
+ const buildingWithChildren = {
113
+ ...building,
114
+ children: [ground.id, upper.id, elevator.id],
115
+ };
116
+ const manualOpening = [
117
+ [1, 0.5],
118
+ [3, 0.5],
119
+ [3, 2.5],
120
+ [1, 2.5],
121
+ ];
122
+ const staleAutoOpening = [
123
+ [1.12, 0.62],
124
+ [2.88, 0.62],
125
+ [2.88, 2.38],
126
+ [1.12, 2.38],
127
+ ];
128
+ const upperSlab = SlabNode.parse({
129
+ name: 'Upper Slab',
130
+ parentId: upper.id,
131
+ polygon: [
132
+ [0, 0],
133
+ [4, 0],
134
+ [4, 3],
135
+ [0, 3],
136
+ ],
137
+ holes: [manualOpening, staleAutoOpening],
138
+ holeMetadata: [{ source: 'manual' }, { source: 'elevator', elevatorId: elevator.id }],
139
+ });
140
+ const nodes = Object.fromEntries([buildingWithChildren, ground, upper, elevator, upperSlab].map((node) => [node.id, node]));
141
+ const updates = syncAutoElevatorOpenings(nodes);
142
+ const slabUpdate = updates.find((update) => update.id === upperSlab.id);
143
+ expect(slabUpdate?.data.holes).toEqual([manualOpening]);
144
+ expect(slabUpdate?.data.holeMetadata).toEqual([{ source: 'manual' }]);
145
+ });
146
+ });
@@ -0,0 +1,2 @@
1
+ export declare const ElevatorOpeningSystem: () => null;
2
+ //# sourceMappingURL=elevator-opening-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevator-opening-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-opening-system.tsx"],"names":[],"mappings":"AAkCA,eAAO,MAAM,qBAAqB,YAuBjC,CAAA"}
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import useScene from '../../store/use-scene';
4
+ import { syncAutoElevatorOpenings } from './elevator-opening-sync';
5
+ function isOpeningRelevantNode(node) {
6
+ return (node?.type === 'building' ||
7
+ node?.type === 'ceiling' ||
8
+ node?.type === 'elevator' ||
9
+ node?.type === 'level' ||
10
+ node?.type === 'slab');
11
+ }
12
+ function hasOpeningRelevantNodeChange(nextNodes, prevNodes) {
13
+ if (nextNodes === prevNodes)
14
+ return false;
15
+ const ids = new Set([...Object.keys(nextNodes), ...Object.keys(prevNodes)]);
16
+ for (const id of ids) {
17
+ const nextNode = nextNodes[id];
18
+ const prevNode = prevNodes[id];
19
+ if (nextNode === prevNode)
20
+ continue;
21
+ if (isOpeningRelevantNode(nextNode) || isOpeningRelevantNode(prevNode))
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ export const ElevatorOpeningSystem = () => {
27
+ const syncingAutoOpeningsRef = useRef(false);
28
+ useEffect(() => {
29
+ const applyUpdates = (updates) => {
30
+ if (updates.length === 0)
31
+ return;
32
+ syncingAutoOpeningsRef.current = true;
33
+ useScene.getState().updateNodes(updates);
34
+ queueMicrotask(() => {
35
+ syncingAutoOpeningsRef.current = false;
36
+ });
37
+ };
38
+ applyUpdates(syncAutoElevatorOpenings(useScene.getState().nodes));
39
+ return useScene.subscribe((state, prevState) => {
40
+ if (syncingAutoOpeningsRef.current)
41
+ return;
42
+ if (!hasOpeningRelevantNodeChange(state.nodes, prevState.nodes))
43
+ return;
44
+ applyUpdates(syncAutoElevatorOpenings(state.nodes));
45
+ });
46
+ }, []);
47
+ return null;
48
+ };
@@ -0,0 +1,2 @@
1
+ export declare function ElevatorRuntimeSystem(): null;
2
+ //# sourceMappingURL=elevator-runtime-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevator-runtime-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-runtime-system.tsx"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,SAMpC"}
@@ -0,0 +1,8 @@
1
+ import { useFrame } from '@react-three/fiber';
2
+ import { stepElevatorRuntimes } from './elevator-runtime';
3
+ export function ElevatorRuntimeSystem() {
4
+ useFrame(({ clock }, delta) => {
5
+ stepElevatorRuntimes(clock.getElapsedTime() * 1000, delta);
6
+ }, 2);
7
+ return null;
8
+ }
@@ -0,0 +1,18 @@
1
+ import type { AnyNodeId, ElevatorNode } from '../../schema';
2
+ import { type ElevatorInteractiveState } from '../../store/use-interactive';
3
+ import { type ElevatorLevelEntry } from './elevator-service';
4
+ export declare function createElevatorInteractiveState(levelId: AnyNodeId, carY: number): ElevatorInteractiveState;
5
+ export declare function queueElevatorRequest(state: ElevatorInteractiveState, levelId: AnyNodeId): ElevatorInteractiveState;
6
+ export declare function openElevatorDoorState(state: ElevatorInteractiveState): ElevatorInteractiveState;
7
+ export declare function requestElevatorLevel(elevatorId: AnyNodeId, levelId: AnyNodeId): void;
8
+ export declare function openElevatorDoor(elevatorId: AnyNodeId): void;
9
+ export declare function stepElevatorRuntimeState({ defaultEntry, delta, elevator, entries, now, state, }: {
10
+ defaultEntry: ElevatorLevelEntry;
11
+ delta: number;
12
+ elevator: ElevatorNode;
13
+ entries: ElevatorLevelEntry[];
14
+ now: number;
15
+ state: ElevatorInteractiveState;
16
+ }): ElevatorInteractiveState;
17
+ export declare function stepElevatorRuntimes(now: number, delta: number): void;
18
+ //# sourceMappingURL=elevator-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevator-runtime.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,KAAK,wBAAwB,EAAkB,MAAM,6BAA6B,CAAA;AAE3F,OAAO,EAAE,KAAK,kBAAkB,EAAyB,MAAM,oBAAoB,CAAA;AAUnF,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,MAAM,GACX,wBAAwB,CAW1B;AA8BD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,wBAAwB,EAC/B,OAAO,EAAE,SAAS,GACjB,wBAAwB,CAW1B;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,wBAAwB,GAAG,wBAAwB,CAQ/F;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,QAS7E;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,QASrD;AAED,wBAAgB,wBAAwB,CAAC,EACvC,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,OAAO,EACP,GAAG,EACH,KAAK,GACN,EAAE;IACD,YAAY,EAAE,kBAAkB,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,wBAAwB,CAAA;CAChC,GAAG,wBAAwB,CAmH3B;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAsC9D"}