@kerros/viewer 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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -2
  3. package/dist/adapters/basemap.js +11 -0
  4. package/dist/adapters/basemap.js.map +1 -0
  5. package/dist/adapters/persistence.js +121 -0
  6. package/dist/adapters/persistence.js.map +1 -0
  7. package/dist/adapters/status.js +18 -0
  8. package/dist/adapters/status.js.map +1 -0
  9. package/dist/components/ElevatorPanel.js +120 -0
  10. package/dist/components/ElevatorPanel.js.map +1 -0
  11. package/dist/components/Icons.js +57 -0
  12. package/dist/components/Icons.js.map +1 -0
  13. package/dist/components/Inspector.js +1184 -0
  14. package/dist/components/Inspector.js.map +1 -0
  15. package/dist/components/NavigatePanel.js +301 -0
  16. package/dist/components/NavigatePanel.js.map +1 -0
  17. package/dist/components/NavigationGraph.js +234 -0
  18. package/dist/components/NavigationGraph.js.map +1 -0
  19. package/dist/components/StructureExplorer.js +510 -0
  20. package/dist/components/StructureExplorer.js.map +1 -0
  21. package/dist/components/StructureView.js +286 -0
  22. package/dist/components/StructureView.js.map +1 -0
  23. package/dist/components/controls.js +170 -0
  24. package/dist/components/controls.js.map +1 -0
  25. package/dist/components/graphLayout.js +27 -0
  26. package/dist/components/graphLayout.js.map +1 -0
  27. package/dist/host.js +169 -0
  28. package/dist/host.js.map +1 -0
  29. package/dist/i18n.js +88 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/index.js +171 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/map/CabinMaterials.js +81 -0
  34. package/dist/map/CabinMaterials.js.map +1 -0
  35. package/dist/map/CabinMirror.js +43 -0
  36. package/dist/map/CabinMirror.js.map +1 -0
  37. package/dist/map/Doors.js +80 -0
  38. package/dist/map/Doors.js.map +1 -0
  39. package/dist/map/ElevatorCabin.js +93 -0
  40. package/dist/map/ElevatorCabin.js.map +1 -0
  41. package/dist/map/FixtureLights.js +117 -0
  42. package/dist/map/FixtureLights.js.map +1 -0
  43. package/dist/map/MapCanvas.js +2161 -0
  44. package/dist/map/MapCanvas.js.map +1 -0
  45. package/dist/map/Passenger.js +69 -0
  46. package/dist/map/Passenger.js.map +1 -0
  47. package/dist/map/SceneLayer.js +1534 -0
  48. package/dist/map/SceneLayer.js.map +1 -0
  49. package/dist/map/UndergroundContext.js +153 -0
  50. package/dist/map/UndergroundContext.js.map +1 -0
  51. package/dist/map/ambience.js +194 -0
  52. package/dist/map/ambience.js.map +1 -0
  53. package/dist/map/architecture.js +305 -0
  54. package/dist/map/architecture.js.map +1 -0
  55. package/dist/map/bathMusic.js +92 -0
  56. package/dist/map/bathMusic.js.map +1 -0
  57. package/dist/map/cameraAim.js +33 -0
  58. package/dist/map/cameraAim.js.map +1 -0
  59. package/dist/map/elevators.js +56 -0
  60. package/dist/map/elevators.js.map +1 -0
  61. package/dist/map/excavation.js +52 -0
  62. package/dist/map/excavation.js.map +1 -0
  63. package/dist/map/exteriors.js +25 -0
  64. package/dist/map/exteriors.js.map +1 -0
  65. package/dist/map/features.js +218 -0
  66. package/dist/map/features.js.map +1 -0
  67. package/dist/map/fences.js +29 -0
  68. package/dist/map/fences.js.map +1 -0
  69. package/dist/map/journey.js +112 -0
  70. package/dist/map/journey.js.map +1 -0
  71. package/dist/map/levels.js +6 -0
  72. package/dist/map/levels.js.map +1 -0
  73. package/dist/map/lighting.js +142 -0
  74. package/dist/map/lighting.js.map +1 -0
  75. package/dist/map/loadBasemap.js +26 -0
  76. package/dist/map/loadBasemap.js.map +1 -0
  77. package/dist/map/materials.js +183 -0
  78. package/dist/map/materials.js.map +1 -0
  79. package/dist/map/poolLighting.js +39 -0
  80. package/dist/map/poolLighting.js.map +1 -0
  81. package/dist/map/projection.js +24 -0
  82. package/dist/map/projection.js.map +1 -0
  83. package/dist/map/rampGeometry.js +38 -0
  84. package/dist/map/rampGeometry.js.map +1 -0
  85. package/dist/map/route.js +107 -0
  86. package/dist/map/route.js.map +1 -0
  87. package/dist/map/stairSurfaces.js +115 -0
  88. package/dist/map/stairSurfaces.js.map +1 -0
  89. package/dist/map/surfaces.js +136 -0
  90. package/dist/map/surfaces.js.map +1 -0
  91. package/dist/map/textures.js +95 -0
  92. package/dist/map/textures.js.map +1 -0
  93. package/dist/map/underground.js +34 -0
  94. package/dist/map/underground.js.map +1 -0
  95. package/dist/map/walk.js +433 -0
  96. package/dist/map/walk.js.map +1 -0
  97. package/dist/map/walkContext.js +46 -0
  98. package/dist/map/walkContext.js.map +1 -0
  99. package/dist/map/walkDimensions.js +7 -0
  100. package/dist/map/walkDimensions.js.map +1 -0
  101. package/dist/map/walkJourney.js +85 -0
  102. package/dist/map/walkJourney.js.map +1 -0
  103. package/dist/map/walkSurfaces.js +106 -0
  104. package/dist/map/walkSurfaces.js.map +1 -0
  105. package/dist/map/wallJoins.js +70 -0
  106. package/dist/map/wallJoins.js.map +1 -0
  107. package/dist/map/water.js +274 -0
  108. package/dist/map/water.js.map +1 -0
  109. package/dist/model/authoring.js +80 -0
  110. package/dist/model/authoring.js.map +1 -0
  111. package/dist/model/boundaries.js +548 -0
  112. package/dist/model/boundaries.js.map +1 -0
  113. package/dist/model/coverage.js +30 -0
  114. package/dist/model/coverage.js.map +1 -0
  115. package/dist/model/document.js +52 -0
  116. package/dist/model/document.js.map +1 -0
  117. package/dist/model/doors.js +47 -0
  118. package/dist/model/doors.js.map +1 -0
  119. package/dist/model/factory.js +56 -0
  120. package/dist/model/factory.js.map +1 -0
  121. package/dist/model/geometry.js +394 -0
  122. package/dist/model/geometry.js.map +1 -0
  123. package/dist/model/inference.js +182 -0
  124. package/dist/model/inference.js.map +1 -0
  125. package/dist/model/materials.js +11 -0
  126. package/dist/model/materials.js.map +1 -0
  127. package/dist/model/mutations.js +157 -0
  128. package/dist/model/mutations.js.map +1 -0
  129. package/dist/model/navigation.js +275 -0
  130. package/dist/model/navigation.js.map +1 -0
  131. package/dist/model/ontology.js +109 -0
  132. package/dist/model/ontology.js.map +1 -0
  133. package/dist/model/passages.js +5 -0
  134. package/dist/model/passages.js.map +1 -0
  135. package/dist/model/portals.js +42 -0
  136. package/dist/model/portals.js.map +1 -0
  137. package/dist/model/precision.js +11 -0
  138. package/dist/model/precision.js.map +1 -0
  139. package/dist/model/project.js +17 -0
  140. package/dist/model/project.js.map +1 -0
  141. package/dist/model/ramps.js +10 -0
  142. package/dist/model/ramps.js.map +1 -0
  143. package/dist/model/roomNavigation.js +87 -0
  144. package/dist/model/roomNavigation.js.map +1 -0
  145. package/dist/model/routeClearance.js +72 -0
  146. package/dist/model/routeClearance.js.map +1 -0
  147. package/dist/model/spaces.js +104 -0
  148. package/dist/model/spaces.js.map +1 -0
  149. package/dist/model/stairGeometry.js +40 -0
  150. package/dist/model/stairGeometry.js.map +1 -0
  151. package/dist/model/stairTraversal.js +48 -0
  152. package/dist/model/stairTraversal.js.map +1 -0
  153. package/dist/model/topology.js +63 -0
  154. package/dist/model/topology.js.map +1 -0
  155. package/dist/model/types.js +35 -0
  156. package/dist/model/types.js.map +1 -0
  157. package/dist/model/validate.js +240 -0
  158. package/dist/model/validate.js.map +1 -0
  159. package/dist/model/vertical.js +129 -0
  160. package/dist/model/vertical.js.map +1 -0
  161. package/dist/model/walls.js +71 -0
  162. package/dist/model/walls.js.map +1 -0
  163. package/dist/styles.css +4931 -0
  164. package/dist/theme/index.js +34 -0
  165. package/dist/theme/index.js.map +1 -0
  166. package/dist/types/adapters/basemap.d.ts +2 -0
  167. package/dist/types/adapters/persistence.d.ts +29 -0
  168. package/dist/types/adapters/status.d.ts +4 -0
  169. package/dist/types/components/ElevatorPanel.d.ts +18 -0
  170. package/dist/types/components/Icons.d.ts +8 -0
  171. package/dist/types/components/Inspector.d.ts +38 -0
  172. package/dist/types/components/NavigatePanel.d.ts +27 -0
  173. package/dist/types/components/NavigationGraph.d.ts +10 -0
  174. package/dist/types/components/StructureExplorer.d.ts +8 -0
  175. package/dist/types/components/StructureView.d.ts +22 -0
  176. package/dist/types/components/controls.d.ts +52 -0
  177. package/dist/types/components/graphLayout.d.ts +9 -0
  178. package/dist/types/i18n.d.ts +85 -0
  179. package/dist/types/import/analysisPreview.d.ts +27 -0
  180. package/dist/types/import/brief.d.ts +22 -0
  181. package/dist/types/import/checkpoint.d.ts +49 -0
  182. package/dist/types/import/instructions.d.ts +7 -0
  183. package/dist/types/import/usage.d.ts +10 -0
  184. package/dist/types/map/CabinMaterials.d.ts +18 -0
  185. package/dist/types/map/CabinMirror.d.ts +13 -0
  186. package/dist/types/map/Doors.d.ts +11 -0
  187. package/dist/types/map/ElevatorCabin.d.ts +20 -0
  188. package/dist/types/map/FixtureLights.d.ts +35 -0
  189. package/dist/types/map/MapCanvas.d.ts +87 -0
  190. package/dist/types/map/Passenger.d.ts +18 -0
  191. package/dist/types/map/SceneLayer.d.ts +306 -0
  192. package/dist/types/map/UndergroundContext.d.ts +18 -0
  193. package/dist/types/map/ambience.d.ts +29 -0
  194. package/dist/types/map/architecture.d.ts +8 -0
  195. package/dist/types/map/bathMusic.d.ts +14 -0
  196. package/dist/types/map/cameraAim.d.ts +20 -0
  197. package/dist/types/map/elevators.d.ts +16 -0
  198. package/dist/types/map/excavation.d.ts +9 -0
  199. package/dist/types/map/exteriors.d.ts +3 -0
  200. package/dist/types/map/features.d.ts +33 -0
  201. package/dist/types/map/fences.d.ts +4 -0
  202. package/dist/types/map/journey.d.ts +36 -0
  203. package/dist/types/map/levels.d.ts +4 -0
  204. package/dist/types/map/lighting.d.ts +103 -0
  205. package/dist/types/map/loadBasemap.d.ts +4 -0
  206. package/dist/types/map/materials.d.ts +38 -0
  207. package/dist/types/map/poolLighting.d.ts +11 -0
  208. package/dist/types/map/projection.d.ts +9 -0
  209. package/dist/types/map/rampGeometry.d.ts +8 -0
  210. package/dist/types/map/route.d.ts +59 -0
  211. package/dist/types/map/stairSurfaces.d.ts +29 -0
  212. package/dist/types/map/surfaces.d.ts +36 -0
  213. package/dist/types/map/textures.d.ts +8 -0
  214. package/dist/types/map/underground.d.ts +30 -0
  215. package/dist/types/map/walk.d.ts +232 -0
  216. package/dist/types/map/walkContext.d.ts +11 -0
  217. package/dist/types/map/walkDimensions.d.ts +5 -0
  218. package/dist/types/map/walkJourney.d.ts +33 -0
  219. package/dist/types/map/walkSurfaces.d.ts +27 -0
  220. package/dist/types/map/wallJoins.d.ts +6 -0
  221. package/dist/types/map/water.d.ts +12 -0
  222. package/dist/types/model/authoring.d.ts +38 -0
  223. package/dist/types/model/boundaries.d.ts +41 -0
  224. package/dist/types/model/coverage.d.ts +8 -0
  225. package/dist/types/model/document.d.ts +9 -0
  226. package/dist/types/model/doors.d.ts +19 -0
  227. package/dist/types/model/factory.d.ts +5 -0
  228. package/dist/types/model/geometry.d.ts +99 -0
  229. package/dist/types/model/host.d.ts +154 -0
  230. package/dist/types/model/inference.d.ts +46 -0
  231. package/dist/types/model/live.d.ts +60 -0
  232. package/dist/types/model/materials.d.ts +28 -0
  233. package/dist/types/model/mutations.d.ts +178 -0
  234. package/dist/types/model/navigation.d.ts +70 -0
  235. package/dist/types/model/ontology.d.ts +43 -0
  236. package/dist/types/model/passages.d.ts +2 -0
  237. package/dist/types/model/portals.d.ts +9 -0
  238. package/dist/types/model/precision.d.ts +11 -0
  239. package/dist/types/model/project.d.ts +8 -0
  240. package/dist/types/model/ramps.d.ts +7 -0
  241. package/dist/types/model/roomNavigation.d.ts +15 -0
  242. package/dist/types/model/routeClearance.d.ts +11 -0
  243. package/dist/types/model/spaces.d.ts +46 -0
  244. package/dist/types/model/stairGeometry.d.ts +27 -0
  245. package/dist/types/model/stairTraversal.d.ts +8 -0
  246. package/dist/types/model/topology.d.ts +14 -0
  247. package/dist/types/model/types.d.ts +369 -0
  248. package/dist/types/model/validate.d.ts +38 -0
  249. package/dist/types/model/vertical.d.ts +145 -0
  250. package/dist/types/model/walls.d.ts +71 -0
  251. package/dist/types/schema/index.d.ts +25 -0
  252. package/dist/types/theme/index.d.ts +55 -0
  253. package/dist/types/viewer/FloorViewer.d.ts +76 -0
  254. package/dist/types/viewer/ViewerWorkspace.d.ts +2 -0
  255. package/dist/types/viewer/host.d.ts +11 -0
  256. package/dist/types/viewer/index.d.ts +2 -0
  257. package/dist/types/viewer/panels.d.ts +12 -0
  258. package/dist/viewer/FloorViewer.js +104 -0
  259. package/dist/viewer/FloorViewer.js.map +1 -0
  260. package/dist/viewer/ViewerWorkspace.js +370 -0
  261. package/dist/viewer/ViewerWorkspace.js.map +1 -0
  262. package/dist/viewer/panels.js +35 -0
  263. package/dist/viewer/panels.js.map +1 -0
  264. package/package.json +58 -4
@@ -0,0 +1,1534 @@
1
+ var Be = Object.defineProperty;
2
+ var Pe = (E, i, e) => i in E ? Be(E, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : E[i] = e;
3
+ var b = (E, i, e) => Pe(E, typeof i != "symbol" ? i + "" : i, e);
4
+ import { makeDoor as Ge } from "./Doors.js";
5
+ import { fenceGeometry as Fe } from "./fences.js";
6
+ import * as v from "three";
7
+ import Tt from "polygon-clipping";
8
+ import { MercatorCoordinate as ae } from "maplibre-gl";
9
+ import { legStepIndices as Ve, routeTrackImage as We, ROUTE_COLOR as le, TRACK_PIXELS as he, TRACK_TILE as ce } from "./route.js";
10
+ import { toLngLat as de, objectPosition as fe, objectRotation as $e, add as ge, rotate as ut, openRing as zt, barrierEnds as pe, distance as xt, pointInRing as Rt, closeRing as wt, rectangle as at, objectArea as ue, ringArea as De } from "../model/geometry.js";
11
+ import { floorOutline as It, mainAxis as _e } from "../model/walls.js";
12
+ import { wallPieces as vt, objectRings as St, COLORS as yt } from "./features.js";
13
+ import { shaftVoids as Gt, flights as He, stairModel as Ue, flightsAt as Ne, flightRun as Ze, servedFloors as Xe, isVertical as Et, reaches as qe } from "../model/vertical.js";
14
+ import { makeFixture as Ye, makeRoof as Ke } from "./architecture.js";
15
+ import { MaterialLibrary as Je } from "./materials.js";
16
+ import { EXTERIOR_PRESETS as me } from "../model/materials.js";
17
+ import { exteriorWalls as Qe } from "./exteriors.js";
18
+ import { mergeGeometries as je } from "three/addons/utils/BufferGeometryUtils.js";
19
+ import { SurfaceBatch as ti, metricUVs as Ot, splitWallFace as ei, OUTSIDE as ii, hitEntity as ve } from "./surfaces.js";
20
+ import { excavationRings as si } from "./excavation.js";
21
+ import { floorIndex as Mt, undergroundView as ni, MAX_CAGE_LEVELS as oi } from "./underground.js";
22
+ import { UndergroundCage as ri, undergroundPit as ai } from "./UndergroundContext.js";
23
+ import { walkNearPlane as li, includeSceneDepth as hi, syncLightingCamera as ci } from "./projection.js";
24
+ import { FIXED as ye, ambient as we, sunlight as be } from "./lighting.js";
25
+ import { FixtureLights as di } from "./FixtureLights.js";
26
+ import { EYE as fi } from "./walkDimensions.js";
27
+ import { rampJoins as gi } from "../model/ramps.js";
28
+ import { stairGeometry as pi, spiralGeometry as ui } from "../model/stairGeometry.js";
29
+ import { tallSpaceContext as mi } from "./walkSurfaces.js";
30
+ import { walkVoidContext as vi, walkConnectionContext as yi } from "./walkContext.js";
31
+ import { rampSections as wi, rampVoids as ke } from "./rampGeometry.js";
32
+ import { makePool as bi, makeWaterSlide as xi, applyPoolCaustics as Mi } from "./water.js";
33
+ import { cabinFloorVoids as Si, cabinDimensions as Ii, elevatorWalls as Ci, elevatorDoorWidth as xe } from "./elevators.js";
34
+ import { CabinMaterials as Li } from "./CabinMaterials.js";
35
+ import { REFLECTION_BODY as Ti, CabinMirror as ki } from "./CabinMirror.js";
36
+ import { makeElevatorCabin as Ai, makeCabinDoor as zi } from "./ElevatorCabin.js";
37
+ import { Passenger as Ri } from "./Passenger.js";
38
+ import { LIFT as q, SLAB as $ } from "./levels.js";
39
+ const Ei = 7e-3, Me = (E) => Ei * (E > 2e3 ? 0 : E > 200 ? 1 : E > 20 ? 2 : 3), Oi = 0.85, Se = 0.58, Ie = 0.05, Ct = 0.06, Bi = 0.35, Pi = 0.62, Gi = 0.3, Fi = 0.055, Vi = 0.17, Wi = (E, i, e) => E ? 0 : i - e, $i = (E, i, e) => {
40
+ var n;
41
+ return i ? e - (((n = E.active) == null ? void 0 : n.elevation) ?? 0) : E.elevation(e);
42
+ }, Lt = (E, i) => Math.max(E + i + q, E + q + $ + fi + Bi);
43
+ function Ce(E, i) {
44
+ const e = E.floors.find((o) => o.id === i);
45
+ if (!e) return;
46
+ const s = (e.mezzanine ? E.floors.filter(
47
+ (o) => !o.mezzanine && o.buildingId === e.buildingId && o.elevation < e.elevation && o.elevation + o.height > e.elevation
48
+ ).sort((o, l) => l.elevation - o.elevation)[0] : void 0) ?? e;
49
+ return { floor: s, soffit: Lt(0, s.elevation + s.height - e.elevation) };
50
+ }
51
+ const Bt = (E) => E.map((i) => [Math.round(i[0] * 1e4) / 1e4, Math.round(i[1] * 1e4) / 1e4]);
52
+ function Le(E, i, e, n) {
53
+ var m;
54
+ const s = Mt(E), l = (s.objects.get(i) ?? []).filter((h) => {
55
+ var c;
56
+ return h.kind === "zone" && !h.parentId && !h.slope && ((c = h.rings) == null ? void 0 : c.length);
57
+ }), r = l.length ? l.map((h) => h.rings.map((c) => Bt(wt(c)))) : It(E, i).map((h) => [Bt(wt(h))]);
58
+ if (!r.length) return [];
59
+ try {
60
+ let h = Tt.union(r[0], ...r.slice(1));
61
+ const c = [...Gt(E, i, e, n), ...ke(E, i)];
62
+ return c.length && (h = Tt.difference(
63
+ h,
64
+ ...c.map((I) => [Bt(I)])
65
+ )), h;
66
+ } catch {
67
+ const h = (m = s.outlines.get(i)) == null ? void 0 : m.rings;
68
+ return h ? [h.map((c) => wt(c))] : [];
69
+ }
70
+ }
71
+ const Di = 42, Pt = "route:track", Q = (E) => E ? q + $ : Ct, _i = (E, i) => {
72
+ const e = (n, s) => {
73
+ let o = 1 / 0, l = -1 / 0;
74
+ for (const f of n)
75
+ f[s] < o && (o = f[s]), f[s] > l && (l = f[s]);
76
+ return [o, l];
77
+ };
78
+ for (const n of [0, 1]) {
79
+ const [s, o] = e(E, n), [l, f] = e(i, n);
80
+ if (o < l || f < s) return !1;
81
+ }
82
+ return !0;
83
+ }, Hi = 0.4, Ui = 0.2, Te = 0.02;
84
+ function Ni(E, i) {
85
+ const e = Math.max(2, Math.min(40, Math.round(i / Hi))), n = i / e, s = Math.max(Ui, E / e);
86
+ return Array.from({ length: e }, (o, l) => ({
87
+ at: n * (l + 0.5),
88
+ // The band hangs a riser below the slope line: the foot step's top is level with the lower comb
89
+ // plate, which is where a step really does emerge, and the head of the run stops a riser short
90
+ // of the upper one. That last riser is where the band's travel goes — a cycle puts every step
91
+ // where its neighbour was, so the step that climbs past the last visible one has to finish
92
+ // inside the head housing rather than above a landing that is now flush with the floor.
93
+ base: E * l / e - s,
94
+ going: n,
95
+ face: s
96
+ }));
97
+ }
98
+ class Ts {
99
+ constructor() {
100
+ b(this, "id", "kerros-3d");
101
+ b(this, "type", "custom");
102
+ b(this, "renderingMode", "3d");
103
+ b(this, "map");
104
+ b(this, "renderer");
105
+ b(this, "scene", new v.Scene());
106
+ b(this, "camera", (() => {
107
+ const i = new v.Camera();
108
+ return i.layers.enableAll(), i.layers.disable(Ti), i;
109
+ })());
110
+ b(this, "worldToClip", new v.Matrix4());
111
+ b(this, "clipToWorld", new v.Matrix4());
112
+ b(this, "transform", new v.Matrix4());
113
+ b(this, "center");
114
+ b(this, "unit", 1);
115
+ b(this, "project");
116
+ b(this, "stack", !1);
117
+ b(this, "walking", !1);
118
+ /** The walked floor has lamps of its own. Then the walk hands the interior over to them and
119
+ * turns the general ceiling light down; a floor without any keeps the general light, or it is
120
+ * a dark floor with a bright lid. */
121
+ b(this, "lampLit", !1);
122
+ b(this, "poolLitOnly", !1);
123
+ b(this, "poolOverview", !1);
124
+ /** Per-storey ghost strength for the things a stack is FOR — the floor plates that carry each
125
+ * storey's plan — on the storeys ABOVE the one in focus. Shared out across however many of them
126
+ * the stack layers up between the eye and the focus. */
127
+ b(this, "ghostAlpha", 0.17);
128
+ /** And for the storeys UNDER the one in focus, which are read through the active plate as well as
129
+ * through everything above it and need the strength to survive both. Keeping the two apart is
130
+ * also what lets the active plate be drawn between them — see the render-order pass. */
131
+ b(this, "underAlpha", 0.27);
132
+ b(this, "activeFloor", null);
133
+ b(this, "hovered", null);
134
+ b(this, "rebase", 0);
135
+ /** See sceneGround: what an authored elevation has to be shifted by to join the scene. */
136
+ b(this, "ground", 0);
137
+ b(this, "growth", 1);
138
+ b(this, "growthStart", 0);
139
+ b(this, "materials", new Je());
140
+ b(this, "cabinMaterials");
141
+ b(this, "passenger");
142
+ b(this, "fixtureLights");
143
+ b(this, "waterTime", { value: 0 });
144
+ b(this, "hasWater", !1);
145
+ b(this, "batch", new ti());
146
+ b(this, "highlight", new v.Group());
147
+ b(this, "selected", null);
148
+ /** Where the sun stands over this project right now, and whether that counts as evening. */
149
+ b(this, "sunState", ye.day);
150
+ b(this, "statuses", null);
151
+ b(this, "excavation", !1);
152
+ /** Only the part of the feed the scene is built from — where the cars are and whether doors stand
153
+ * open. A tone changing from normal to alarm recolours a marker and must not rebuild the scene. */
154
+ b(this, "liftSignature", "");
155
+ /** Per-floor stairwell cut-outs; cleared with the scene. */
156
+ b(this, "voidCache", /* @__PURE__ */ new Map());
157
+ /** Moving parts, rebuilt with the scene each update — see `rig`. */
158
+ b(this, "rigs", []);
159
+ /** Where each moving part has got to, carried ACROSS rebuilds. The geometry is disposed with the
160
+ * scene like everything else; this is not, or every lift car would jump back to the bottom of its
161
+ * shaft on any edit, and every open door would slam. */
162
+ b(this, "rigState", /* @__PURE__ */ new Map());
163
+ /** Whether the last animation frame moved anything that casts a shadow. */
164
+ b(this, "shadowsMoved", !1);
165
+ b(this, "lastFrame", 0);
166
+ b(this, "revision", 0);
167
+ b(this, "depthScale", 1);
168
+ b(this, "buried", !1);
169
+ b(this, "presentedElevation", (i) => i);
170
+ b(this, "cage", new ri());
171
+ b(this, "sun");
172
+ /** The two lights that are not the sun, kept so the scene can be re-lit without being rebuilt. */
173
+ b(this, "skyLight");
174
+ b(this, "roomLight");
175
+ b(this, "route", null);
176
+ b(this, "routeStep", null);
177
+ b(this, "routeGroup", null);
178
+ b(this, "routeTile", null);
179
+ b(this, "mapStyle", {});
180
+ b(this, "xyCache", /* @__PURE__ */ new Map());
181
+ b(this, "markerCache", /* @__PURE__ */ new Map());
182
+ // Whether each marker is hidden behind geometry. Screen position is cheap and recomputed every
183
+ // frame; this is a raycast against the whole model and must NOT be. It survives camera movement and
184
+ // is only re-derived once the camera settles (or the scene is rebuilt).
185
+ b(this, "occlusionCache", /* @__PURE__ */ new Map());
186
+ b(this, "previousProjection", new v.Matrix4());
187
+ b(this, "previousScale", 0);
188
+ b(this, "previousSize", "");
189
+ b(this, "pickables", []);
190
+ b(this, "clipBounds", new v.Box3());
191
+ b(this, "clipBoundsDirty", !0);
192
+ /** A tiny procedural sky/ground gradient, prefiltered for image-based lighting. Without an
193
+ * environment every MeshStandardMaterial is lit by two lights alone and reads flat; with one, every
194
+ * surface picks up sky above and bounced ground below, which is most of what makes 3D look solid.
195
+ * Built once per lighting mood and cached — PMREM generation is not cheap. */
196
+ b(this, "envCache", /* @__PURE__ */ new Map());
197
+ /** Ask for a repaint at the loop rate rather than at once, coalescing the requests so a dozen
198
+ * escalators still only cost one frame between them. */
199
+ b(this, "loopTimer");
200
+ }
201
+ cabinFinishes() {
202
+ return this.cabinMaterials ?? (this.cabinMaterials = new Li(this.renderer));
203
+ }
204
+ wave() {
205
+ var i, e;
206
+ (i = this.passenger) == null || i.wave(performance.now() / 1e3), (e = this.map) == null || e.triggerRepaint();
207
+ }
208
+ setMapStyle(i) {
209
+ this.mapStyle = i ?? {};
210
+ }
211
+ onAdd(i, e) {
212
+ this.map = i, this.renderer = new v.WebGLRenderer({
213
+ canvas: i.getCanvas(),
214
+ context: e,
215
+ antialias: !0
216
+ }), this.renderer.autoClear = !1, this.renderer.shadowMap.enabled = !0, this.renderer.shadowMap.type = v.PCFSoftShadowMap, this.renderer.shadowMap.autoUpdate = !1, this.renderer.toneMapping = v.NeutralToneMapping, this.renderer.toneMappingExposure = 0.9;
217
+ }
218
+ environmentMap(i) {
219
+ const e = i ? "evening" : "day", n = this.envCache.get(e);
220
+ if (n || !this.renderer) return (n == null ? void 0 : n.texture) ?? null;
221
+ const s = document.createElement("canvas");
222
+ s.width = 512, s.height = 256;
223
+ const o = s.getContext("2d"), l = o.createLinearGradient(0, 0, 0, 256);
224
+ i ? (l.addColorStop(0, "#22293f"), l.addColorStop(0.42, "#5d5570"), l.addColorStop(0.52, "#c08a5f"), l.addColorStop(1, "#241f1c")) : (l.addColorStop(0, "#719dcc"), l.addColorStop(0.35, "#c0d5e5"), l.addColorStop(0.49, "#f6eedf"), l.addColorStop(0.54, "#b3ada0"), l.addColorStop(1, "#605f55")), o.fillStyle = l, o.fillRect(0, 0, 512, 256);
225
+ for (const [h, c, I] of [
226
+ [110, 83, 65],
227
+ [370, 58, 44]
228
+ ]) {
229
+ const w = o.createRadialGradient(h, c, 0, h, c, I);
230
+ w.addColorStop(0, i ? "rgba(249,188,139,.42)" : "rgba(255,252,239,.8)"), w.addColorStop(1, "rgba(255,255,255,0)"), o.fillStyle = w, o.fillRect(h - I, c - I, I * 2, I * 2);
231
+ }
232
+ const f = new v.CanvasTexture(s);
233
+ f.mapping = v.EquirectangularReflectionMapping, f.colorSpace = v.SRGBColorSpace;
234
+ const r = new v.PMREMGenerator(this.renderer), m = r.fromEquirectangular(f);
235
+ return f.dispose(), r.dispose(), this.renderer.resetState(), this.envCache.set(e, m), m.texture;
236
+ }
237
+ /** Move the existing lights to where a new sun puts them, without touching the geometry. Valid
238
+ * only while the dusk treatment is unchanged — see the caller. */
239
+ relight(i, e, n) {
240
+ var l, f, r, m, h, c;
241
+ const s = we(
242
+ n,
243
+ this.poolOverview ? { kelvin: 6500, level: 1 } : (l = i.floors.find((I) => I.id === e)) == null ? void 0 : l.light
244
+ );
245
+ this.scene.environmentIntensity = this.poolLitOnly ? 0 : s.environment, (f = this.skyLight) == null || f.color.set(s.sky), (r = this.skyLight) == null || r.groundColor.set(s.ground), this.skyLight && (this.skyLight.intensity = this.poolLitOnly ? 0 : s.hemisphere), (m = this.roomLight) == null || m.color.set(s.interior), (h = this.roomLight) == null || h.groundColor.set(s.interiorBounce), this.roomLight && (this.roomLight.intensity = s.interiorLevel * (this.walking && this.lampLit ? 0.45 : 1));
246
+ const o = be(n);
247
+ this.sun && (this.sun.color.set(o.color), this.sun.intensity = this.poolLitOnly ? 0 : o.intensity, this.sun.position.set(...o.position), this.fitShadowCamera()), this.refreshShadows(), (c = this.map) == null || c.triggerRepaint();
248
+ }
249
+ /** Redraw every shadow map on the next frame: the sun's, and each lamp's. Shadows are drawn on
250
+ * demand, and each light says for itself whether it is owed a redraw — the sun's map is one
251
+ * 2048² pass over the whole model and a lamp's is six, so a lamp swapping under a walker must
252
+ * not cost the sun's as well. This is the "everything changed" case. */
253
+ refreshShadows() {
254
+ var i;
255
+ this.renderer && (this.sun && (this.sun.shadow.needsUpdate = !0), (i = this.fixtureLights) == null || i.invalidate(), this.renderer.shadowMap.needsUpdate = !0);
256
+ }
257
+ /** Point the sun at the model and shrink its shadow frustum to fit. A fixed +/-140 m box spends
258
+ * most of a 2048 map on empty ground: a small building got a handful of texels and its shadows
259
+ * came out as soft mush. Fitting to the real extent keeps texel density high whatever the scale. */
260
+ fitShadowCamera() {
261
+ const i = this.sun;
262
+ if (!i) return;
263
+ const e = new v.Box3();
264
+ if (this.scene.traverse((m) => {
265
+ m instanceof v.Mesh && m.castShadow && e.expandByObject(m);
266
+ }), e.isEmpty()) return;
267
+ const n = e.getCenter(new v.Vector3()), s = e.getSize(new v.Vector3());
268
+ i.target.position.copy(n), i.target.updateMatrixWorld();
269
+ const o = Math.max(s.x, s.y, s.z) || 1, l = i.position.clone().normalize();
270
+ i.position.copy(n).addScaledVector(l, o * 1.6 + 40), i.updateMatrixWorld();
271
+ const f = i.shadow.camera;
272
+ i.shadow.updateMatrices(i);
273
+ const r = new v.Box3();
274
+ for (const m of [e.min.x, e.max.x])
275
+ for (const h of [e.min.y, e.max.y])
276
+ for (const c of [e.min.z, e.max.z]) {
277
+ const I = new v.Vector3(m, h, c);
278
+ r.expandByPoint(I.clone().applyMatrix4(f.matrixWorldInverse)), !this.buried && c > 0 && (I.addScaledVector(l, -c / l.z), r.expandByPoint(I.applyMatrix4(f.matrixWorldInverse)));
279
+ }
280
+ Object.assign(f, {
281
+ left: r.min.x - 3,
282
+ right: r.max.x + 3,
283
+ top: r.max.y + 3,
284
+ bottom: r.min.y - 3,
285
+ near: Math.max(0.5, -r.max.z - 10),
286
+ far: -r.min.z + 10
287
+ }), f.updateProjectionMatrix();
288
+ }
289
+ xy(i) {
290
+ const e = `${i[0]},${i[1]}`, n = this.xyCache.get(e);
291
+ if (n) return n;
292
+ const s = ae.fromLngLat(de(i, this.project.origin)), o = [(s.x - this.center.x) / this.unit, -(s.y - this.center.y) / this.unit];
293
+ return this.xyCache.set(e, o), o;
294
+ }
295
+ /** Several extrusions welded into one moving part. An escalator's step band is twenty boxes that
296
+ * travel together, and twenty meshes per flight — times a bank of four, times the flights in
297
+ * view — is a draw call apiece for something that moves as one thing. Merged, it is one. */
298
+ parts(i, e, n = !0, s) {
299
+ const o = i.map(({ rings: m, base: h, height: c }) => {
300
+ const I = new v.Shape(m[0].map((d) => new v.Vector2(...this.xy(d))));
301
+ I.holes = m.slice(1).map((d) => new v.Path(d.map((S) => new v.Vector2(...this.xy(S)))));
302
+ const w = new v.ExtrudeGeometry(I, { depth: c, bevelEnabled: !1, steps: 1 });
303
+ w.translate(0, 0, h), Ot(w);
304
+ const y = w.getAttribute("position");
305
+ if (s) {
306
+ w.computeBoundingBox();
307
+ const d = w.boundingBox.getCenter(new v.Vector3()), S = w.getAttribute("uv"), p = w.getAttribute("normal"), [u, x] = s.across;
308
+ for (let a = 0; a < y.count; a++) {
309
+ const L = y.getX(a) - d.x, F = y.getY(a) - d.y, D = Math.abs(p.getZ(a)) > 0.5 ? (-L * x + F * u) / s.going + 0.5 : 0.2 + 0.6 * (y.getZ(a) - w.boundingBox.min.z) / Math.max(1e-3, c);
310
+ S.setXY(a, (L * u + F * x) / s.width + 0.5, D);
311
+ }
312
+ }
313
+ for (let d = 0; d < y.count; d++) y.setZ(d, this.present(y.getZ(d)));
314
+ return w.computeVertexNormals(), w.index ? w.toNonIndexed() : w;
315
+ }), l = o.length === 1 ? o[0] : je(o), f = new v.Mesh(l, s ? this.materials.escalator() : this.materials.solid(e));
316
+ f.castShadow = n, f.receiveShadow = !0;
317
+ const r = new v.Group();
318
+ return r.add(f), r;
319
+ }
320
+ /** Register a moving part. Called while the scene is being built; the geometry is fresh each time
321
+ * and the position it has reached is not, so a car mid-ride keeps riding across an edit.
322
+ *
323
+ * `settled` seeds the value the first time a part is ever seen, so opening a document does not
324
+ * send every lift travelling up from the basement to wherever it actually is. */
325
+ rig(i, e, n, s, o, l = !1) {
326
+ this.rigState.has(i) || this.rigState.set(i, l ? 0 : n), o(e, this.rigState.get(i)), this.scene.add(e), this.rigs.push({ id: i, group: e, target: n, speed: s, apply: o, loop: l });
327
+ }
328
+ /** Advance every moving part toward its target. Returns true while anything is still moving, which
329
+ * is what keeps asking the map to repaint — MapLibre draws on demand, so without that a ride would
330
+ * advance one frame per mouse move. */
331
+ animate(i) {
332
+ let e = !1;
333
+ this.shadowsMoved = !1;
334
+ for (const n of this.rigs) {
335
+ const s = this.rigState.get(n.id) ?? n.target;
336
+ if (n.loop) {
337
+ const f = (s + n.speed * i) % n.target;
338
+ this.rigState.set(n.id, f), n.apply(n.group, f), e = !0;
339
+ continue;
340
+ }
341
+ if (Math.abs(s - n.target) < 1e-4) {
342
+ s !== n.target && this.rigState.set(n.id, n.target);
343
+ continue;
344
+ }
345
+ const o = n.speed * i, l = Math.abs(n.target - s) <= o ? n.target : s + Math.sign(n.target - s) * o;
346
+ this.rigState.set(n.id, l), n.apply(n.group, l), e = !0, this.shadowsMoved = !0;
347
+ }
348
+ return e;
349
+ }
350
+ paintSoon() {
351
+ this.loopTimer || (this.loopTimer = setTimeout(() => {
352
+ var i;
353
+ this.loopTimer = void 0, (i = this.map) == null || i.triggerRepaint();
354
+ }, Di));
355
+ }
356
+ /** One vertex, mapped by the view's depth transform — see sceneElevation. Geometry is built at
357
+ * the elevations the document authored, so this is the last thing that touches a z before it
358
+ * reaches the buffer. It ran in the stacked view only, which left a deep cutaway drawing its
359
+ * plate at the compressed depth and everything around it at the real one. */
360
+ present(i) {
361
+ return this.buried && !this.walking ? this.presentedElevation(i) : i;
362
+ }
363
+ surface(i, e, n, s, o, l, f = !1, r = !1, m, h = !1, c, I) {
364
+ if (!i.length) return;
365
+ const w = new v.Shape(i[0].map((p) => new v.Vector2(...this.xy(p))));
366
+ w.holes = i.slice(1).map((p) => new v.Path(p.map((u) => new v.Vector2(...this.xy(u)))));
367
+ const y = n > 5e-3 ? new v.ExtrudeGeometry(w, {
368
+ depth: n,
369
+ bevelEnabled: !1,
370
+ steps: r && !f ? Math.min(24, Math.max(1, Math.ceil(n / 0.35))) : 1
371
+ }) : new v.ShapeGeometry(w);
372
+ if (y.translate(0, 0, e), Ot(y, I), r && !f) {
373
+ const p = y.getAttribute("position"), u = new Float32Array(p.count * 3);
374
+ for (let x = 0; x < p.count; x++) {
375
+ const a = Math.max(0, Math.min(1, (p.getZ(x) - e) / Oi)), L = Se + (1 - Se) * a ** 0.55;
376
+ u[x * 3] = u[x * 3 + 1] = u[x * 3 + 2] = L;
377
+ }
378
+ y.setAttribute("color", new v.BufferAttribute(u, 3));
379
+ }
380
+ if (this.buried && !this.walking && this.depthScale < 1) {
381
+ const p = y.getAttribute("position");
382
+ for (let u = 0; u < p.count; u++) p.setZ(u, this.present(p.getZ(u)));
383
+ y.computeVertexNormals();
384
+ }
385
+ const d = m ?? (f ? this.materials.ghost(s, typeof f == "number" ? f : this.ghostAlpha) : l ? this.materials.get(l, s) : this.materials.solid(s)), S = c && ei(y, c.map((p) => this.xy(p)));
386
+ if (S) {
387
+ const p = this.materials.get("plaster", "#eceae5");
388
+ this.batch.add(S, r && !f ? this.materials.shaded(p) : p, o, h);
389
+ }
390
+ this.batch.add(y, r && !f ? this.materials.shaded(d) : d, o, h);
391
+ }
392
+ /** A sloped area (garage ramp, loading incline): the plate's own footprint, but each vertex lifted
393
+ * to its elevation along the slope axis, then given thickness along -Z so the deck reads solid
394
+ * from below. Depth compression is applied per-vertex, exactly as for flat surfaces. */
395
+ slopedSurface(i, e, n, s, o, l = !1) {
396
+ for (const f of wi(i.rings, e)) {
397
+ const r = new v.Shape(f[0].map((u) => new v.Vector2(...this.xy(u))));
398
+ r.holes = f.slice(1).map((u) => new v.Path(u.map((x) => new v.Vector2(...this.xy(x)))));
399
+ const m = new v.ExtrudeGeometry(r, { depth: s, bevelEnabled: !1 });
400
+ m.translate(0, 0, -s), Ot(m);
401
+ const [h, c] = this.xy(e.axis[0]), [I, w] = this.xy(e.axis[1]), y = I - h, d = w - c, S = y * y + d * d, p = m.getAttribute("position");
402
+ for (let u = 0; u < p.count; u++) {
403
+ const x = S ? Math.max(0, Math.min(1, ((p.getX(u) - h) * y + (p.getY(u) - c) * d) / S)) : 0;
404
+ p.setZ(u, this.present(p.getZ(u) + e.high + (e.low - e.high) * x + n));
405
+ }
406
+ m.computeVertexNormals(), this.batch.add(
407
+ m,
408
+ l ? this.materials.ghost(o, typeof l == "number" ? l : this.ghostAlpha) : this.materials.solid(o),
409
+ i.id
410
+ );
411
+ }
412
+ }
413
+ /** The façade envelope for a set of levels: each level's slab, its exterior walls with their
414
+ * authored finish, and the glazing punched into them. Interior partitions are deliberately left
415
+ * out — from outside they are hidden work, and they used to produce seams along the façade. */
416
+ envelope(i, e, n, s) {
417
+ for (const o of i.objects) {
418
+ const l = e.get(o.floorId ?? "");
419
+ l && (o.kind === "zone" && o.rings && this.surface(o.rings, l.elevation + q, $, "#c8c5bd", o.id), o.kind === "window" && o.barrierId && n.has(o.barrierId) && this.opening(i, o, l.elevation));
420
+ }
421
+ for (const o of vt(i, null, !0))
422
+ if (e.has(o.floorId ?? "") && n.has(o.id)) {
423
+ const l = s.get(o.id);
424
+ this.surface([o.ring], o.base + Q(o.floorId), o.height - o.base, l.color, o.id, l.material);
425
+ }
426
+ }
427
+ opening(i, e, n) {
428
+ const s = fe(i, e), o = $e(i, e);
429
+ if (e.kind === "window") {
430
+ const l = i.barriers.find((d) => d.id === e.barrierId), f = Math.min(e.height, ((l == null ? void 0 : l.height) ?? e.height + 0.85) - 0.85);
431
+ if (f <= 0) return;
432
+ const r = n + Q(e.floorId) + 0.85, m = Math.min(0.08, f / 3, e.width / 3), h = Math.max(0.16, ((l == null ? void 0 : l.thickness) ?? 0.2) + 0.035), c = this.materials.metal("#b4b9ba"), I = this.materials.glass(this.sunState.evening), w = (d, S, p, u, x, a = h) => {
433
+ const L = ut([d, 0], o);
434
+ this.surface(
435
+ [at([s[0] + L[0], s[1] + L[1]], S, a, o)],
436
+ p,
437
+ u,
438
+ "#ffffff",
439
+ e.id,
440
+ void 0,
441
+ !1,
442
+ !1,
443
+ x
444
+ );
445
+ };
446
+ w(0, e.width + 0.08, r - 0.04, 0.065, this.materials.get("stone", "#b9b6ac"), h + 0.12), w(0, e.width, r, m, c), w(0, e.width, r + f - m, m, c);
447
+ for (const d of [-1, 1]) w(d * (e.width - m) / 2, m, r + m, f - m * 2, c);
448
+ w(0, e.width - m * 2, r + m, f - m * 2, I, 0.018);
449
+ const y = Math.max(1, Math.ceil(e.width / 1.45));
450
+ for (let d = 1; d < y; d++)
451
+ w(-e.width / 2 + d * e.width / y, 0.045, r + m, f - m * 2, c);
452
+ } else e.kind === "stairs" ? this.shaft(i, e, n, s, o) : e.kind === "elevator" ? this.lift(i, e, n, s, o) : ["door", "gate"].includes(e.kind) ? this.leaf(i, e, n, s, o) : this.surface(
453
+ St({ ...e, position: s, rotation: o }),
454
+ n + Q(e.floorId),
455
+ Math.min(e.height, 3),
456
+ e.color ?? yt[e.kind] ?? "#bfcac7",
457
+ e.id
458
+ );
459
+ }
460
+ /** A door as a leaf in a frame, standing open when the feed says it is.
461
+ *
462
+ * In 3D a door had been a slab filling its whole opening — a door you could never walk through and
463
+ * that looked identical whether it was open, closed or unmonitored. The plan has drawn the swing
464
+ * for a long time; this is the same statement in three dimensions, hinged the same way, so the two
465
+ * views agree about which side it opens from.
466
+ *
467
+ * Unknown status means shut. The plan can draw the architectural swing symbol for "a door is here
468
+ * and this is how it opens" because a symbol is allowed to be about the door rather than about its
469
+ * state; a solid leaf in space is not, so it stays in the frame until something says otherwise. */
470
+ leaf(i, e, n, s, o) {
471
+ var S;
472
+ const l = i.barriers.find((p) => p.id === e.barrierId), f = (l == null ? void 0 : l.thickness) ?? 0.2, r = n + Q(e.floorId), m = Math.min(e.height, ((l == null ? void 0 : l.height) ?? e.height) - 0.02), h = Ge(e, f, m, this.materials), c = this.xy(s), I = this.xy(ge(s, ut([1, 0], o))), w = Math.atan2(I[1] - c[1], I[0] - c[0]), y = (p) => {
473
+ p.position.set(c[0], c[1], this.present(r)), p.scale.z = this.buried && !this.walking ? this.depthScale : 1, p.rotation.z = w;
474
+ };
475
+ y(h.frame);
476
+ for (const p of this.batch.addObject(h.frame, e.id)) this.scene.add(p);
477
+ h.frame.traverse((p) => {
478
+ p instanceof v.Mesh && p.geometry.dispose();
479
+ });
480
+ const d = (S = this.statuses) == null ? void 0 : S.get(e.feedId ?? "");
481
+ h.panels.forEach(({ group: p, setOpen: u }, x) => {
482
+ y(p), this.rig(
483
+ `${e.id}:leaf:${e.doorType ?? "hinged"}:${x}`,
484
+ p,
485
+ d != null && d.open ? 1 : 0,
486
+ 1.4,
487
+ (a, L) => u(L)
488
+ );
489
+ });
490
+ }
491
+ /** The inward-facing edge of an exterior wall.
492
+ *
493
+ * Which face is the inside is not a property of the wall — it is a question about the building
494
+ * around it — so it is answered the only way it can be: step off the wall each way and see which
495
+ * side lands within the floor's footprint. A wall with no footprint to be inside of (an outbuilding
496
+ * wall, a fence read as exterior) has no interior face. */
497
+ interiorFace(i, e, n) {
498
+ const s = It(i, e.floorId ?? n);
499
+ if (!s.length) return null;
500
+ const o = zt(e.ring);
501
+ if (o.length < 3) return null;
502
+ const l = i.barriers.find((I) => I.id === e.id);
503
+ if (!l || l.thickness < 0.03) return null;
504
+ const [f, r] = pe(i, l), m = xt(f, r), h = (r[0] - f[0]) / m, c = (r[1] - f[1]) / m;
505
+ for (let I = 0; I < o.length; I++) {
506
+ const w = o[I], y = o[(I + 1) % o.length], d = xt(w, y);
507
+ if (d < 0.05 || Math.abs((y[0] - w[0]) * c - (y[1] - w[1]) * h) > 1e-6 * d) continue;
508
+ const S = (y[1] - w[1]) / d, p = -(y[0] - w[0]) / d, u = [(w[0] + y[0]) / 2, (w[1] + y[1]) / 2], x = [u[0] + S * 0.3, u[1] + p * 0.3];
509
+ if (s.some((a) => Rt(x, a)))
510
+ return [w, y];
511
+ }
512
+ return null;
513
+ }
514
+ /** An area's rings with the shafts that climb through its level, and the ramps that drive through
515
+ * it, cut out of them.
516
+ *
517
+ * `through` says which plate is being cut: a floor is open where something arrives up through it,
518
+ * a ceiling where something sets off up through it. They are not the same holes — the bottom of a
519
+ * run departs without arriving and the top arrives without departing — and on a criss-cross
520
+ * escalator bank they are at opposite ends of the same box.
521
+ *
522
+ * `wells` is whether a stairwell may be punched at all — a hole onto a storey nobody is drawing
523
+ * is worse than no hole. A ramp is not subject to it: what is under a ramp's hole is the ramp.
524
+ *
525
+ * Cheap when there is nothing to cut, which is the usual case: a floor with no shaft through it
526
+ * gets its own rings back untouched, and one with a shaft pays a clip only for the areas the
527
+ * shaft actually lands in. */
528
+ withVoids(i, e, n, s, o = "floor", l = !0) {
529
+ const f = `${n}|${o}|${l}`;
530
+ let r = this.voidCache.get(f);
531
+ if (r || (r = l ? Gt(i, n, s, { through: o, lowest: this.buried || this.walking ? -1 / 0 : -0.01 }) : [], r = [...r, ...ke(i, n, o)], o === "floor" && (r = [
532
+ ...r,
533
+ ...i.objects.filter((h) => {
534
+ var c;
535
+ return h.floorId === n && h.water && ((c = h.rings) == null ? void 0 : c.length);
536
+ }).map((h) => h.rings[0]),
537
+ // In POV the car supplies its own floor, including at the lowest landing. Stairwell
538
+ // cuts are disabled when no lower storey is drawn; leaving the room finish here put
539
+ // carpet or bath tiles above the car's floor and made the cabin look like a pool.
540
+ ...this.walking ? Si(i, n, s) : []
541
+ ]), this.voidCache.set(f, r)), !r.length || !e.length) return e.length ? [e] : [];
542
+ const m = r.filter((h) => _i(h, e[0]));
543
+ if (!m.length) return e.length ? [e] : [];
544
+ try {
545
+ return Tt.difference(
546
+ [e.map((c) => wt(c))],
547
+ ...m.map((c) => [c])
548
+ );
549
+ } catch {
550
+ return e.length ? [e] : [];
551
+ }
552
+ }
553
+ /** A stair, spiral or escalator, drawn as the climb it actually makes.
554
+ *
555
+ * The flights come from the levels the document says the shaft serves, not from whatever storey
556
+ * happens to sit above it, and each is drawn at the elevation of the level it leaves — so the
557
+ * flight arriving at the level you are standing on is there, coming up through the floor, instead
558
+ * of being filed invisibly under the storey below. */
559
+ shaft(i, e, n, s, o) {
560
+ var I;
561
+ const l = e.color ?? yt[e.kind] ?? "#bfcac7", f = ((I = i.floors.find((w) => w.id === e.floorId)) == null ? void 0 : I.elevation) ?? 0, r = He(i, e), m = Ue(i, e), h = this.stack ? r : (() => {
562
+ const { up: w, down: y } = Ne(i, e, this.activeFloor);
563
+ return [y, w].filter((d) => !!d);
564
+ })();
565
+ if (!h.length) {
566
+ this.surface(St({ ...e, position: s, rotation: o }), n + Q(e.floorId), 0.18, l, e.id);
567
+ return;
568
+ }
569
+ const c = this.buried || this.walking ? h : h.filter((w) => w.from.elevation > -0.01);
570
+ if (c.length)
571
+ for (const w of c) {
572
+ const y = n + (w.from.elevation - f) + Q(e.floorId);
573
+ if (m === "spiral") {
574
+ this.spiral(e, s, y, w.rise, l);
575
+ continue;
576
+ }
577
+ const d = r.indexOf(w), S = d + 1 < r.length && c.includes(r[d + 1]);
578
+ this.flight(e, Ze(i, e, w.rise, m, d), y, w.rise, l, m, !S);
579
+ }
580
+ }
581
+ /** A straight climb: treads for a stair, a ribbed deck between flat landing plates for an
582
+ * escalator. Both run along the object's own depth axis, which is where the plan put them. */
583
+ flight(i, e, n, s, o, l, f = !0) {
584
+ var S;
585
+ const { at: r, half: m, pad: h, incline: c, topT: I, footT: w, rotation: y } = e;
586
+ if (l === "escalator") {
587
+ const p = Ni(s, c), u = p[0].going, x = p[0].face, a = s / p.length, L = Math.max(0.24, a + 0.06), F = m - w;
588
+ this.surface(
589
+ [at(r(w + F / 2), i.width, F, y)],
590
+ n - (a + x + 0.08),
591
+ a + x + 0.08 + Te,
592
+ o,
593
+ i.id
594
+ ), f && this.surface(
595
+ [at(r(I - h / 2), i.width, h, y)],
596
+ n + s - L,
597
+ L + Te,
598
+ o,
599
+ i.id
600
+ );
601
+ const D = { ...i, rings: [at(r((I + w) / 2), i.width, c, y)] };
602
+ this.slopedSurface(D, { axis: [r(I), r(w)], high: n + s, low: n }, -x, 0.3, "#9aa5a1");
603
+ const nt = (N) => ({
604
+ rings: [at(r(w - N.at), i.width - 0.12, u * 1.02, y)],
605
+ base: n + N.base,
606
+ height: x
607
+ }), U = (S = this.statuses) == null ? void 0 : S.get(i.feedId ?? ""), _ = ((U == null ? void 0 : U.travel) ?? i.travel ?? "up") === "down" ? -1 : 1, et = (U == null ? void 0 : U.running) ?? !0, ot = this.xy(r(0)), A = this.xy(ge(r(0), ut([1, 0], y))), P = {
608
+ across: [A[0] - ot[0], A[1] - ot[1]],
609
+ width: i.width - 0.12,
610
+ going: u * 1.02
611
+ };
612
+ if (this.stack || !et) {
613
+ const N = this.parts(p.map(nt), o, !1, P);
614
+ N.userData.entityId = i.id, this.scene.add(N);
615
+ } else {
616
+ const N = _ > 0 ? { at: -u / 2, base: -a - x } : { at: c + u / 2, base: s - x }, st = this.parts([N, ...p].map(nt), o, !1, P), H = this.xy(r(0)), J = this.xy(r(-u * _)), rt = this.present(n + a * _) - this.present(n);
617
+ this.rig(
618
+ `${i.id}:steps:${Math.round(n * 100)}`,
619
+ st,
620
+ 1,
621
+ // Half a metre a second along the incline is an ordinary escalator — a step a second.
622
+ 0.5 / Math.hypot(u, a),
623
+ (O, Y) => O.position.set((J[0] - H[0]) * Y, (J[1] - H[1]) * Y, rt * Y),
624
+ !0
625
+ );
626
+ }
627
+ for (const N of [-1, 1]) {
628
+ const st = [N * (i.width / 2 - 0.06), 0], H = ut(st, y), J = r((I + w) / 2), rt = {
629
+ ...i,
630
+ rings: [at([J[0] + H[0], J[1] + H[1]], 0.1, c, y)]
631
+ };
632
+ this.slopedSurface(
633
+ rt,
634
+ {
635
+ axis: [
636
+ [r(I)[0] + H[0], r(I)[1] + H[1]],
637
+ [r(w)[0] + H[0], r(w)[1] + H[1]]
638
+ ],
639
+ high: n + s + 0.95,
640
+ low: n + 0.95
641
+ },
642
+ 0,
643
+ 0.08,
644
+ "#8f9a96"
645
+ );
646
+ }
647
+ return;
648
+ }
649
+ const d = pi(i, e, s, l);
650
+ for (const p of d.landings)
651
+ this.surface([p.ring], n + p.height - 0.06, 0.1, o, i.id, i.material);
652
+ for (const p of d.lanes) {
653
+ const u = p.head[0] - p.foot[0], x = p.head[1] - p.foot[1], a = Math.hypot(u, x) / p.steps, L = Math.atan2(x, u) * 180 / Math.PI - 90;
654
+ for (let F = 0; F < p.steps; F++) {
655
+ const D = (F + 0.5) / p.steps;
656
+ this.surface(
657
+ [at([p.foot[0] + u * D, p.foot[1] + x * D], p.width, a * 1.02, L)],
658
+ n + p.base + p.rise * F / p.steps,
659
+ p.rise / p.steps + 0.04,
660
+ o,
661
+ i.id,
662
+ i.material
663
+ );
664
+ }
665
+ }
666
+ }
667
+ /** A lift: the shaft as tall as the levels it serves, its doors on the level you are standing on,
668
+ * and the car where the feed says it is.
669
+ *
670
+ * It used to be a 1.2 m box — shorter than the doors it was supposed to contain — because the
671
+ * generic object path capped elevators there to stop them blocking the view. A lift is not
672
+ * furniture: it is a hole through the building, and drawing it as one is the only way the plan
673
+ * shows that floors 2 and 7 are on the same shaft. Above the level in focus it turns to ghost, so
674
+ * it reads as continuing without becoming a column in front of everything else. */
675
+ lift(i, e, n, s, o) {
676
+ var et, ot;
677
+ const l = e.color ?? yt[e.kind] ?? "#bfcac7", f = ((et = i.floors.find((A) => A.id === e.floorId)) == null ? void 0 : et.elevation) ?? 0, r = Xe(i, e), m = n + Q(e.floorId), h = (A) => n + (A.elevation - f) + Q(e.floorId), c = i.floors.find((A) => A.id === this.activeFloor);
678
+ if (r.length < 2) {
679
+ this.surface(St({ ...e, position: s, rotation: o }), m, Math.min(e.height, 2.4), l, e.id);
680
+ return;
681
+ }
682
+ const w = (this.buried ? r : r.filter((A) => A.elevation > -0.01))[0] ?? r[0], y = Math.max(h(r[0]), this.buried ? -1 / 0 : n - f + Q(e.floorId)), d = h(r[r.length - 1]) + (r[r.length - 1].height ?? e.height), S = c ? Math.min(d, n + (c.elevation - f) + Q(e.floorId) + (c.height ?? 3)) : d, p = (A, P, N) => {
683
+ if (!(P - A < 0.05))
684
+ for (const [st, H] of [
685
+ ["front", 0],
686
+ ["right", 90],
687
+ ["back", 180],
688
+ ["left", 270]
689
+ ]) {
690
+ const J = H % 180 ? e.depth : e.width, rt = H % 180 ? e.width : e.depth, O = xe(e, H), Y = o + H, it = (tt, Z, V, dt) => {
691
+ if (dt <= V) return;
692
+ const gt = ut([tt, rt / 2 - 0.045], Y);
693
+ this.surface(
694
+ [at([s[0] + gt[0], s[1] + gt[1]], Z, 0.09, Y)],
695
+ V,
696
+ dt - V,
697
+ N ? l : "#b4bab7",
698
+ e.id,
699
+ void 0,
700
+ N
701
+ );
702
+ };
703
+ if (!(e.doorSides ?? ["front"]).includes(st)) {
704
+ it(0, J, A, P);
705
+ continue;
706
+ }
707
+ for (const tt of [-1, 1]) it(tt * (J + O) / 4, (J - O) / 2, A, P);
708
+ let j = A;
709
+ for (const tt of r) {
710
+ const Z = h(tt), V = Math.min(2.3, tt.height - 0.35);
711
+ Z + V <= A || Z >= P || (it(0, O, j, Math.min(P, Z)), j = Math.max(j, Z + V));
712
+ }
713
+ it(0, O, j, P);
714
+ }
715
+ };
716
+ p(y, S, !1), this.walking || p(S, d, Math.max(this.ghostAlpha, Vi));
717
+ const u = (ot = this.statuses) == null ? void 0 : ot.get(e.feedId ?? ""), x = (u == null ? void 0 : u.targetFloorId) ?? (u == null ? void 0 : u.carFloorId) ?? null, a = r.find((A) => A.id === x && (this.buried || A.elevation > -0.01)) ?? w, L = Math.min(2.5, a.height - 0.35), F = {
718
+ ...e,
719
+ position: s,
720
+ rotation: o,
721
+ ...Ii(e)
722
+ }, D = at(s, F.width, F.depth, o), nt = this.walking && (c == null ? void 0 : c.id) === a.id, U = nt ? Ai(F, L, this.cabinFinishes(), (c == null ? void 0 : c.code) ?? (c == null ? void 0 : c.name) ?? "", !!(u != null && u.moving)) : this.parts(
723
+ [
724
+ { rings: [D], base: 0, height: 0.1 },
725
+ { rings: [D], base: L, height: 0.08 },
726
+ ...Ci(F, !0).map((A) => ({ rings: [A], base: 0.1, height: L - 0.1 }))
727
+ ],
728
+ "#dfe6e3"
729
+ );
730
+ if (nt) {
731
+ const A = this.xy(s), P = this.xy([
732
+ s[0] + Math.cos(o * Math.PI / 180),
733
+ s[1] + Math.sin(o * Math.PI / 180)
734
+ ]);
735
+ U.position.set(A[0], A[1], 0), U.rotation.z = Math.atan2(P[1] - A[1], P[0] - A[0]);
736
+ }
737
+ u != null && u.carFloorId && !u.targetFloorId && this.rigState.set(`${e.id}:car`, h(a));
738
+ const _ = u != null && u.carTravelSeconds && u.carTravelSeconds > 0 ? Math.max(
739
+ 1.5,
740
+ Math.abs(h(a) - (this.rigState.get(`${e.id}:car`) ?? h(a))) / u.carTravelSeconds
741
+ ) : 1.5;
742
+ if (this.rig(`${e.id}:car`, U, h(a), _, (A, P) => {
743
+ A.position.z = this.present(P) - this.present(0), A.visible = !this.walking || !!(u != null && u.moving) || !!(u != null && u.targetFloorId) || (c == null ? void 0 : c.id) === a.id;
744
+ }), c && r.some((A) => A.id === c.id)) {
745
+ const A = Math.min(2.3, (c.height ?? 3) - 0.35), P = Math.abs((this.rigState.get(`${e.id}:car`) ?? h(a)) - h(c)) < 0.25, N = !!(u != null && u.open) && P;
746
+ for (const st of e.doorSides ?? ["front"]) {
747
+ const H = { front: 0, right: 90, back: 180, left: 270 }[st], J = o + H, rt = (H % 180 === 0 ? e.depth : e.width) / 2 - 0.13, O = ut([0, rt], J), Y = [s[0] + O[0], s[1] + O[1]], it = this.xy(Y), j = this.xy([Y[0] + Math.cos(J * Math.PI / 180), Y[1] + Math.sin(J * Math.PI / 180)]);
748
+ for (const tt of [-1, 1]) {
749
+ const Z = zi(xe(e, H), A, tt, this.cabinFinishes());
750
+ Z.group.position.set(it[0], it[1], this.present(h(c) + 0.015)), Z.group.rotation.z = Math.atan2(j[1] - it[1], j[0] - it[0]), Z.group.scale.z = (this.present(h(c) + A) - this.present(h(c))) / A, this.rig(`${e.id}:door:${st}:${tt}`, Z.group, N ? 1 : 0, 2.5, (V, dt) => Z.update(dt));
751
+ }
752
+ }
753
+ }
754
+ }
755
+ /** A spiral: wedge treads winding around a central pole.
756
+ *
757
+ * Its exits are wherever it is served — a spiral passing three levels with a landing on each is
758
+ * one object serving three floors, which the document could already say and nothing drew. */
759
+ spiral(i, e, n, s, o) {
760
+ const l = ui(i, e, s);
761
+ for (const f of l.wedges)
762
+ this.surface([wt(f.ring)], n + f.height - 0.07, 0.07, o, i.id);
763
+ this.surface([at(e, l.inner * 2, l.inner * 2, 0)], n, s, "#9aa3a0", i.id);
764
+ }
765
+ update(i, e, n, s, o = ye.day, l = null, f = !1, r = !1) {
766
+ var Wt, $t, Dt, _t, Ht, Ut, Nt, Zt, Xt, qt, Yt, Kt, Jt, Qt, jt, te, ee, ie, se, ne;
767
+ const m = o.evening, h = l ? [...l.values()].filter(
768
+ (t) => t.carFloorId !== void 0 || t.targetFloorId !== void 0 || t.open !== void 0 || t.running !== void 0 || t.travel
769
+ ).map(
770
+ (t) => `${t.feedId}:${t.carFloorId ?? ""}:${t.targetFloorId ?? ""}:${t.carTravelSeconds ?? ""}:${t.open ?? ""}:${t.moving ?? ""}:${t.running ?? ""}:${t.travel ?? ""}`
771
+ ).sort().join("|") : "";
772
+ if (r && this.activeFloor !== e)
773
+ for (const t of this.rigState.keys()) (t.endsWith(":car") || t.includes(":door:")) && this.rigState.delete(t);
774
+ this.statuses = l;
775
+ const c = this.project === i && this.stack === n && this.walking === r && this.activeFloor === e && this.liftSignature === h && this.excavation === f && this.scene.children.length > 0;
776
+ if (c && this.sunState === o) {
777
+ this.selected !== s && (this.selected = s, this.refreshHighlight());
778
+ return;
779
+ }
780
+ if (c && this.sunState.evening === o.evening) {
781
+ this.sunState = o, this.relight(i, e, o), this.selected !== s && (this.selected = s, this.refreshHighlight());
782
+ return;
783
+ }
784
+ this.project !== i && this.xyCache.clear(), this.voidCache.clear(), this.disposeScene(), clearTimeout(this.loopTimer), this.loopTimer = void 0, this.rigs = [], this.project = i, this.stack = n, this.walking = r, this.lampLit = !!r && !!e && i.objects.some((t) => t.floorId === e && t.kind === "light" && !!t.light);
785
+ const I = (($t = (Wt = i.floors.find((t) => t.id === e)) == null ? void 0 : Wt.light) == null ? void 0 : $t.level) === 0 && i.objects.some((t) => t.floorId === e && !!t.water);
786
+ this.poolLitOnly = r && I, this.poolOverview = !r && I, this.activeFloor = e, this.selected = s, this.sunState = o, this.liftSignature = h, this.excavation = f, this.revision++, this.batch.chunk = r ? 20 : 0;
787
+ const w = Qe(i), y = Mt(i), d = y.floors, S = new Map(i.buildings.map((t) => [t.id, t])), p = new Map(i.barriers.map((t) => [t.id, t])), u = new Map(
788
+ i.barriers.map((t) => {
789
+ var C, B;
790
+ const g = (B = S.get(((C = d.get(t.floorId ?? "")) == null ? void 0 : C.buildingId) ?? "")) == null ? void 0 : B.exteriorPreset;
791
+ return [
792
+ t.id,
793
+ w.has(t.id) ? g ? me[g] : t.material ? { material: t.material, color: t.color ?? "#c8c3b7" } : me.limestone : {
794
+ material: t.material ?? (t.kind === "wall" ? "plaster" : void 0),
795
+ // An interior wall is plastered, and plaster is near-white. COLORS.wall is the grey
796
+ // a wall is DRAWN in — a stroke on a plan has to read against a pale floor, so it is
797
+ // mid-grey by necessity — and standing that grey up as a surface, shaded and with
798
+ // contact shadow at its foot, made every partition read as bare concrete. A fence
799
+ // keeps its drawn colour: it has no plaster and is not a room's wall.
800
+ color: t.color ?? (t.kind === "wall" ? "#eef0f3" : yt[t.kind])
801
+ }
802
+ ];
803
+ })
804
+ ), x = ni(i, e, n), a = x.active, L = x.buried;
805
+ this.buried = L, this.depthScale = x.depthScale, this.presentedElevation = x.elevation, this.rebase = n || r ? 0 : (a == null ? void 0 : a.elevation) ?? 0;
806
+ const F = Wi(n, this.rebase, (a == null ? void 0 : a.elevation) ?? 0);
807
+ this.ground = F;
808
+ const D = L && n && x.compressed && !!a, nt = new Set(
809
+ D ? x.levels.filter((t) => t.elevation > a.elevation).map((t) => t.id) : []
810
+ ), U = new Set(
811
+ n && a ? x.levels.filter((t) => t.buildingId === a.buildingId && t.elevation < a.elevation).map((t) => t.id) : []
812
+ );
813
+ this.center = ae.fromLngLat(de([0, 0], i.origin)), this.unit = this.center.meterInMercatorCoordinateUnits(), this.transform.makeTranslation(this.center.x, this.center.y, 0).scale(new v.Vector3(this.unit, -this.unit, this.unit));
814
+ const _ = we(
815
+ o,
816
+ this.poolOverview ? { kelvin: 6500, level: 1 } : (Dt = i.floors.find((t) => t.id === e)) == null ? void 0 : Dt.light
817
+ );
818
+ this.renderer && (this.renderer.toneMappingExposure = r ? 0.68 : 0.9), this.scene.environment = this.environmentMap(m), this.scene.environmentRotation.set(Math.PI / 2, 0, 0), this.scene.environmentIntensity = this.poolLitOnly ? 0 : _.environment;
819
+ const et = new v.HemisphereLight(_.sky, _.ground, this.poolLitOnly ? 0 : _.hemisphere);
820
+ et.position.set(0, 0, 1), et.layers.enableAll(), this.scene.add(et), this.skyLight = et;
821
+ const ot = new v.HemisphereLight(
822
+ _.interior,
823
+ _.interiorBounce,
824
+ r && this.lampLit ? _.interiorLevel * 0.45 : _.interiorLevel
825
+ );
826
+ ot.position.set(0, 0, 1), this.scene.add(ot), this.roomLight = ot;
827
+ const A = be(o), P = new v.DirectionalLight(A.color, this.poolLitOnly ? 0 : A.intensity);
828
+ P.position.set(...A.position);
829
+ const N = new v.DirectionalLight(
830
+ m ? "#7f90bd" : "#cfe0f2",
831
+ this.poolLitOnly ? 0 : m ? 0.16 : 0.18
832
+ );
833
+ N.layers.enableAll(), N.position.set(m ? -80 : 85, m ? 60 : 55, 45), this.scene.add(N), P.layers.enableAll(), P.castShadow = !0, P.shadow.autoUpdate = !1, P.shadow.mapSize.set(2048, 2048), P.shadow.bias = -15e-5, P.shadow.normalBias = 0.025, Object.assign(P.shadow.camera, { left: -140, right: 140, top: 140, bottom: -140, near: 1, far: 500 }), this.scene.add(P), this.scene.add(P.target), this.sun = P;
834
+ const st = (t) => {
835
+ var g;
836
+ return !!t && (((g = d.get(t)) == null ? void 0 : g.elevation) ?? 0) < 0;
837
+ }, H = !!a && x.levels.some((t) => t.buildingId === a.buildingId && t.elevation < a.elevation), J = (t, g) => Math.max(0.035, Math.min(0.38, 1 - t ** (1 / Math.max(1, g))));
838
+ this.ghostAlpha = J(Pi, x.levels.filter((t) => t.elevation > ((a == null ? void 0 : a.elevation) ?? 0)).length), this.underAlpha = J(Gi, x.levels.filter((t) => t.elevation < ((a == null ? void 0 : a.elevation) ?? 0)).length);
839
+ const rt = (t, g = !1) => {
840
+ var C;
841
+ return g ? Fi : a && (((C = d.get(t ?? "")) == null ? void 0 : C.elevation) ?? 0) < a.elevation ? this.underAlpha : this.ghostAlpha;
842
+ }, O = a != null && a.mezzanine && !n ? i.floors.filter((t) => t.buildingId === a.buildingId && t.elevation < a.elevation).sort((t, g) => g.elevation - t.elevation)[0] : void 0, Y = a && !n ? i.floors.filter(
843
+ (t) => t.mezzanine && t.buildingId === a.buildingId && t.id !== a.id && t.elevation > a.elevation && t.elevation < a.elevation + a.height
844
+ ) : [], it = new Map(
845
+ !n && !r && a ? x.levels.filter((t) => t.elevation >= 0 && t.elevation < a.elevation && t.id !== (O == null ? void 0 : O.id)).map((t) => [t.id, t]) : []
846
+ ), j = L && !n && !r && a ? x.levels.filter((t) => t.elevation < a.elevation && t.id !== (O == null ? void 0 : O.id)).sort((t, g) => g.elevation - t.elevation)[0] : void 0, tt = r ? mi(i, e) : [], Z = new Set(tt.map((t) => t.floorId).filter(Boolean)), V = r && a && e ? vi(i, a, Ce(i, e).floor) : void 0, dt = r && a ? yi(i, a, y.primary).filter(
847
+ (t) => t.id !== (O == null ? void 0 : O.id) && !Y.some((g) => g.id === t.id) && !Z.has(t.id)
848
+ ) : [], gt = new Set(dt.map((t) => t.id)), kt = [...new Map([...(V == null ? void 0 : V.levels) ?? [], ...dt].map((t) => [t.id, t])).values()], Ae = new Set(
849
+ [...U, ...it.keys(), ...Z, O == null ? void 0 : O.id, j == null ? void 0 : j.id, e].filter(
850
+ Boolean
851
+ )
852
+ ), ze = (t) => this.walking || n || x.levels.some((g) => {
853
+ var C;
854
+ return g.elevation < (((C = d.get(t)) == null ? void 0 : C.elevation) ?? 0) && g.id !== t && Ae.has(g.id);
855
+ }), lt = (t) => {
856
+ var g;
857
+ return t ? this.rebase + ((((g = d.get(t)) == null ? void 0 : g.elevation) ?? (a == null ? void 0 : a.elevation) ?? 0) - ((a == null ? void 0 : a.elevation) ?? 0)) : F;
858
+ }, Ft = /* @__PURE__ */ new Map(), At = (t) => {
859
+ if (!t) return;
860
+ let g = Ft.get(t);
861
+ if (!g) {
862
+ const C = this.xy([0, 0]), B = this.xy(ut([1, 0], _e(i, t))), z = xt(C, B);
863
+ g = [(B[0] - C[0]) / z, (B[1] - C[1]) / z], Ft.set(t, g);
864
+ }
865
+ return g;
866
+ }, ft = (t) => $i(x, r, t), Re = n ? i.objects : (
867
+ // Deduplicated: a shaft filed under the storey below reaches this one, and the carried
868
+ // levels bring their own copy of the same object.
869
+ [
870
+ ...new Map(
871
+ [
872
+ ...y.objects.get(null) ?? [],
873
+ ...O ? y.objects.get(O.id) ?? [] : [],
874
+ ...Y.flatMap((t) => y.objects.get(t.id) ?? []),
875
+ ...tt,
876
+ // Openings in the carried walls need their doors and glazing as well.
877
+ ...dt.flatMap(
878
+ (t) => (y.objects.get(t.id) ?? []).filter((g) => g.kind === "door" || g.kind === "window")
879
+ ),
880
+ ...e ? y.objects.get(e) ?? [] : [],
881
+ // A stair that climbs to this level belongs on it, even though it is filed under the
882
+ // one it starts from. Without this the flight you are standing at the top of is not
883
+ // drawn.
884
+ ...e ? y.reaching.get(e) ?? [] : [],
885
+ // And a driveway that surfaces belongs to the street as much as to the deck it leaves.
886
+ // It is filed on the deck, so the site view — which iterates the site's own objects —
887
+ // never saw it, and the garage came out with no mouth at the kerb.
888
+ ...e === null ? i.objects.filter((t) => t.slope && Math.max(t.slope.high, t.slope.low) >= -0.01) : [],
889
+ // A ramp is a floor that is two floors. It is filed on the deck it leaves, so the deck
890
+ // it arrives at drew nothing: from P2 the ramp down from P1 stopped existing, and a
891
+ // car came up out of a plate with no way onto it. Both decks it joins, then — the
892
+ // levels its own slope starts and ends at, rather than every storey the incline passes
893
+ // through. The driveway out to Mannerheimintie crosses -4.2, -6.6 and -9 a hundred
894
+ // metres away under the street, and it has nothing to do with any of them.
895
+ ...a ? i.objects.filter(
896
+ (t) => {
897
+ var g;
898
+ return gi(t, a.elevation) && ((g = d.get(t.floorId ?? "")) == null ? void 0 : g.buildingId) === a.buildingId;
899
+ }
900
+ ) : []
901
+ ].map((t) => [t.id, t])
902
+ ).values()
903
+ ]
904
+ );
905
+ for (const t of Re) {
906
+ if (L && (t.floorId === null || a && ((_t = d.get(t.floorId)) == null ? void 0 : _t.buildingId) !== a.buildingId))
907
+ continue;
908
+ const g = e === null && !!t.slope && Math.max(t.slope.high, t.slope.low) >= -0.01;
909
+ if (!L && st(t.floorId) && !gt.has(t.floorId) && !(Et(t.kind) && qe(i, t, e)) && !g || Et(t.kind) && !y.primary.has(t.id)) continue;
910
+ const C = Et(t.kind);
911
+ if (t.floorId && nt.has(t.floorId) && !C) continue;
912
+ const B = !D && (t.kind === "room" || t.kind === "zone" || !!t.slope);
913
+ if (t.floorId && U.has(t.floorId) && !C && !B || D && t.floorId !== e && t.kind !== "zone") continue;
914
+ const z = n ? ((Ht = d.get(t.floorId ?? "")) == null ? void 0 : Ht.elevation) ?? 0 : lt(t.floorId), X = t.floorId !== null && (t.kind === "room" || t.kind === "zone"), K = e !== null && t.floorId === e, T = !!(n && e !== null && t.floorId && t.floorId !== e), G = T && rt(t.floorId, !(t.kind === "room" || t.kind === "zone")), M = n && K && H, W = ((Nt = (Ut = this.mapStyle).objectColor) == null ? void 0 : Nt.call(Ut, t)) ?? (K || T ? t.color ?? yt[t.kind] ?? "#e6e8e1" : X && !t.material ? (
915
+ // A carried level under a cutaway is background: one neutral, so it reads as context
916
+ // for the storey in focus rather than competing with it.
917
+ "#e5e4df"
918
+ ) : t.color ?? yt[t.kind] ?? "#e6e8e1");
919
+ if (t.water && ((Zt = t.rings) != null && Zt.length)) {
920
+ T || (this.scene.add(
921
+ bi(t, (k) => this.xy(k), this.present(z + q + $), this.materials, this.waterTime, r)
922
+ ), this.hasWater = r);
923
+ continue;
924
+ }
925
+ if (t.slide) {
926
+ T || this.scene.add(xi(t, (k) => this.xy(k), this.present(z + q + $), this.materials));
927
+ continue;
928
+ }
929
+ if (t.kind === "fixture" || t.kind === "landscape") {
930
+ if (T) continue;
931
+ const k = Ye(
932
+ t,
933
+ (R) => this.xy(R),
934
+ z + (t.floorId ? q + $ : Ct),
935
+ this.materials,
936
+ m
937
+ );
938
+ if (L && !r && z < 0) {
939
+ const R = k.position.z, ht = Math.max(t.height, 0.01);
940
+ k.position.z = this.present(R), k.scale.z = (this.present(R + ht) - this.present(R)) / ht;
941
+ }
942
+ for (const R of this.batch.addObject(k, t.id, t.floorId === null)) this.scene.add(R);
943
+ continue;
944
+ }
945
+ if (t.rings && t.slope) {
946
+ this.slopedSurface(t, t.slope, q + $ + F, $, W, G);
947
+ const [k, R] = t.slope.axis, ht = xt(k, R), bt = wt(t.rings[0]);
948
+ for (let mt = 1; mt < bt.length; mt++) {
949
+ const ct = bt[mt - 1], pt = bt[mt], oe = xt(ct, pt), re = ((pt[0] - ct[0]) * (R[0] - k[0]) + (pt[1] - ct[1]) * (R[1] - k[1])) / (oe * ht);
950
+ if (!Number.isFinite(re) || Math.abs(re) < 0.98) continue;
951
+ const Oe = {
952
+ ...t,
953
+ rings: [
954
+ at(
955
+ [(ct[0] + pt[0]) / 2, (ct[1] + pt[1]) / 2],
956
+ oe,
957
+ 0.18,
958
+ Math.atan2(pt[1] - ct[1], pt[0] - ct[0]) * 180 / Math.PI
959
+ )
960
+ ]
961
+ };
962
+ this.slopedSurface(Oe, t.slope, q + $ + F + 0.16, 0.16, "#c7bd91", G);
963
+ }
964
+ continue;
965
+ }
966
+ if (t.rings) {
967
+ if (t.kind === "building" && e !== null) continue;
968
+ const k = t.floorId !== null, R = ["office", "container"].includes(t.kind), ht = z + (k ? (t.kind === "room" ? q + $ + 0.015 : q) + Me(ue(t)) : t.kind === "parcel" ? 0.02 : Ct), bt = R ? t.height : k ? t.kind === "room" ? Ie : $ : 0.04, mt = k && t.floorId ? this.withVoids(i, t.rings, t.floorId, y.primary, "floor", ze(t.floorId)) : [t.rings];
969
+ for (const ct of mt)
970
+ this.surface(
971
+ ct,
972
+ ht,
973
+ bt,
974
+ W,
975
+ t.id,
976
+ // The floor you are standing on used to be the one surface with no finish at all, which left
977
+ // the largest expanse in the frame as flat untextured colour. Give it the same plaster as
978
+ // the levels below — it is subtle enough not to compete with the plan, and without it a
979
+ // whole storey reads as paper.
980
+ T ? void 0 : t.material ?? (X ? "plaster" : void 0),
981
+ G,
982
+ !1,
983
+ M && X ? this.materials.plate(W) : (
984
+ // Walking, the floor is the one surface that can say where the light is, and only
985
+ // because you see it along rather than down onto it.
986
+ r && X && !T && t.material !== "carpet" ? this.materials.polished(this.materials.get(t.material ?? "plaster", W)) : void 0
987
+ ),
988
+ !k,
989
+ void 0,
990
+ At(t.floorId)
991
+ );
992
+ } else ["stairs", "elevator", "turnstile", "door", "gate", "window"].includes(t.kind) ? (!T || C) && this.opening(i, t, z) : ["office", "container", "storage"].includes(t.kind) && this.surface(
993
+ St(t),
994
+ z + Q(t.floorId),
995
+ t.height,
996
+ W,
997
+ t.id,
998
+ T ? void 0 : t.material,
999
+ G,
1000
+ !1,
1001
+ void 0,
1002
+ t.floorId === null
1003
+ );
1004
+ }
1005
+ for (const t of [...D ? U : [], ...j ? [j.id] : []]) {
1006
+ if (!L && st(t)) continue;
1007
+ const g = n ? ((Xt = d.get(t)) == null ? void 0 : Xt.elevation) ?? 0 : lt(t);
1008
+ for (const C of Le(i, t, y.primary, { lowest: L ? -1 / 0 : -0.01 }))
1009
+ this.surface(C, g + q + $, $, "#e5e4df", `shell:${t}`, void 0, n && rt(t));
1010
+ }
1011
+ if (r && e && a) {
1012
+ const { floor: t, soffit: g } = Ce(i, e), C = this.materials.luminous("#f0f1ee", _.interior, 0.8 * (((qt = a.light) == null ? void 0 : qt.level) ?? 1), "ceiling"), B = this.materials.luminous("#f2f3ef", _.interior, 0.7 * (((Yt = a.light) == null ? void 0 : Yt.level) ?? 1), "tile"), z = tt.filter(
1013
+ (M) => {
1014
+ var W, k;
1015
+ return M.ceilingHeight && ((W = M.rings) == null ? void 0 : W.length) && (((k = d.get(M.floorId)) == null ? void 0 : k.elevation) ?? 0) < a.elevation;
1016
+ }
1017
+ ), X = (M) => {
1018
+ if (!(V != null && V.ceilingOpenings.length)) return M;
1019
+ try {
1020
+ return Tt.difference(
1021
+ M,
1022
+ ...V.ceilingOpenings.map((W) => [W])
1023
+ );
1024
+ } catch {
1025
+ return M;
1026
+ }
1027
+ };
1028
+ for (const M of [...y.objects.get(t.id) ?? [], ...z]) {
1029
+ if (!((Kt = M.rings) != null && Kt.length) || M.kind !== "room" && M.kind !== "zone" || M.water || M.slope || !M.ceilingHeight && z.some((k) => Rt(M.position, k.rings[0]))) continue;
1030
+ const W = M.ceilingHeight !== void 0 ? Lt(
1031
+ 0,
1032
+ (((Jt = d.get(M.floorId)) == null ? void 0 : Jt.elevation) ?? a.elevation) + M.ceilingHeight - a.elevation
1033
+ ) : g;
1034
+ for (const k of X(this.withVoids(i, M.rings, t.id, y.primary, "ceiling")))
1035
+ this.surface(
1036
+ k,
1037
+ W - Me(ue(M)),
1038
+ $,
1039
+ "#f0f1ee",
1040
+ `ceiling:${M.id}`,
1041
+ void 0,
1042
+ !1,
1043
+ !1,
1044
+ M.material === "tile" ? B : C,
1045
+ !1,
1046
+ void 0,
1047
+ At(t.id)
1048
+ );
1049
+ }
1050
+ const K = i.floors.filter(
1051
+ (M) => M.buildingId === a.buildingId && M.elevation >= t.elevation + t.height - 0.01
1052
+ ).sort((M, W) => M.elevation - W.elevation)[0], T = Math.max(
1053
+ g + $,
1054
+ K ? Lt(lt(K.id), K.height) : g + a.height
1055
+ ), G = Gt(i, t.id, y.primary, {
1056
+ through: "ceiling",
1057
+ lowest: L ? -1 / 0 : -0.01
1058
+ });
1059
+ if (!kt.length)
1060
+ for (const [M, W] of G.entries())
1061
+ this.surface([W], T, $, "#d9dad6", `well:${e}:${M}`);
1062
+ for (const M of kt) {
1063
+ if (M.id === (O == null ? void 0 : O.id) || Y.some((R) => R.id === M.id) || Z.has(M.id)) continue;
1064
+ const W = lt(M.id), k = (y.objects.get(M.id) ?? []).find(
1065
+ (R) => R.kind === "zone" && !R.parentId && !R.slope && R.material
1066
+ );
1067
+ for (const R of Le(i, M.id, y.primary, { lowest: -1 / 0 }))
1068
+ this.surface(
1069
+ R,
1070
+ W + q,
1071
+ $,
1072
+ (k == null ? void 0 : k.color) ?? "#deddd6",
1073
+ `atrium-plate:${M.id}`,
1074
+ (k == null ? void 0 : k.material) ?? "plaster",
1075
+ !1,
1076
+ !1,
1077
+ void 0,
1078
+ !1,
1079
+ void 0,
1080
+ At(M.id)
1081
+ );
1082
+ if (gt.has(M.id) && !(V != null && V.levels.some((R) => R.id === M.id)))
1083
+ for (const R of It(i, M.id))
1084
+ for (const ht of M.elevation < a.elevation ? this.withVoids(i, [R], M.id, y.primary, "ceiling") : [[R]])
1085
+ this.surface(
1086
+ ht,
1087
+ Lt(W, M.height),
1088
+ $,
1089
+ "#e4e2db",
1090
+ `connection-ceiling:${M.id}`,
1091
+ "ceiling"
1092
+ );
1093
+ }
1094
+ if (V != null && V.roof)
1095
+ for (const M of It(i, V.roof.id))
1096
+ this.surface(
1097
+ [M],
1098
+ lt(V.roof.id) + V.roof.height + q,
1099
+ $,
1100
+ "#e4e2db",
1101
+ `atrium-roof:${V.roof.id}`,
1102
+ "ceiling"
1103
+ );
1104
+ }
1105
+ const Ee = [
1106
+ ...vt(i, e, n),
1107
+ ...O ? vt(i, O.id, !1) : [],
1108
+ ...Y.flatMap((t) => vt(i, t.id, !1)),
1109
+ // Distant atrium levels retain their authored envelope and open railings. A synthetic
1110
+ // wall around every outline ring incorrectly sealed the hole from floor to ceiling.
1111
+ ...kt.filter((t) => t.id !== (O == null ? void 0 : O.id) && !Y.some((g) => g.id === t.id) && !Z.has(t.id)).flatMap(
1112
+ (t) => vt(i, t.id).filter(
1113
+ (g) => {
1114
+ var C;
1115
+ return gt.has(t.id) || w.has(g.id) || ((C = p.get(g.id)) == null ? void 0 : C.kind) === "fence";
1116
+ }
1117
+ )
1118
+ ),
1119
+ ...[...Z].filter((t) => t !== (O == null ? void 0 : O.id) && !Y.some((g) => g.id === t)).flatMap(
1120
+ (t) => vt(i, t, !1).filter(
1121
+ (g) => tt.some(
1122
+ (C) => {
1123
+ var B;
1124
+ return C.floorId === t && ((B = C.rings) == null ? void 0 : B.length) && g.ring.some((z) => Rt(z, C.rings[0]));
1125
+ }
1126
+ )
1127
+ )
1128
+ )
1129
+ ];
1130
+ for (const t of Ee) {
1131
+ if (L && (t.floorId === null || a && ((Qt = d.get(t.floorId)) == null ? void 0 : Qt.buildingId) !== a.buildingId) || !L && st(t.floorId) && !gt.has(t.floorId) || t.floorId && nt.has(t.floorId) || t.floorId && U.has(t.floorId) && !w.has(t.id) || D && t.floorId !== e && !w.has(t.id)) continue;
1132
+ const g = u.get(t.id), C = p.get(t.id), B = d.get(t.floorId ?? "");
1133
+ if (r && B && !B.mezzanine && w.has(t.id) && t.height >= B.height - $ - 0.01) {
1134
+ const X = i.floors.filter((T) => T.buildingId === B.buildingId && !T.mezzanine && T.elevation > B.elevation).sort((T, G) => T.elevation - G.elevation)[0], K = X ? X.elevation - B.elevation - t.height - $ : 0;
1135
+ K > 0.01 && this.surface(
1136
+ [t.ring],
1137
+ lt(t.floorId) + Q(t.floorId) + t.height,
1138
+ K,
1139
+ g.color,
1140
+ `plenum:${t.id}`,
1141
+ g.material
1142
+ );
1143
+ }
1144
+ const z = n && e !== null && !!t.floorId && t.floorId !== e && rt(t.floorId, !0);
1145
+ if (C.kind === "fence") {
1146
+ const [X, K] = pe(i, C).map((M) => this.xy(M)), T = Fe(
1147
+ X,
1148
+ K,
1149
+ t.ring.map((M) => this.xy(M)),
1150
+ t.base + Q(t.floorId) + (n ? 0 : lt(t.floorId)),
1151
+ t.height - t.base,
1152
+ C.thickness
1153
+ ), G = T.getAttribute("position");
1154
+ for (let M = 0; M < G.count; M++) G.setZ(M, this.present(G.getZ(M)));
1155
+ T.computeVertexNormals(), this.batch.add(
1156
+ T,
1157
+ z ? this.materials.ghost(g.color, z) : this.materials.metal(g.color),
1158
+ t.id,
1159
+ t.floorId === null
1160
+ );
1161
+ continue;
1162
+ }
1163
+ this.surface(
1164
+ [t.ring],
1165
+ t.base + Q(t.floorId) + (n ? 0 : lt(t.floorId)),
1166
+ t.height - t.base,
1167
+ g.color,
1168
+ t.id,
1169
+ z ? void 0 : g.material,
1170
+ z,
1171
+ // Only where a wall actually starts at its floor. A piece that begins part-way up — the strip
1172
+ // over a door, say — has no junction to darken and would just get a dirty smear.
1173
+ t.base < 0.01,
1174
+ void 0,
1175
+ // A fence or a garden wall stands out of doors and is lit like the ground it stands on.
1176
+ t.floorId === null,
1177
+ // Assign plaster to the actual inner face. A separate skin competed with brick in the
1178
+ // depth buffer, especially at oblique angles and through the long hall.
1179
+ w.has(t.id) && !z && g.material && !["plaster", "tile"].includes(g.material) ? this.interiorFace(i, t, e) : null
1180
+ );
1181
+ }
1182
+ if (!L && (n || e === null)) {
1183
+ for (const t of i.buildings)
1184
+ if (t.roof && (!n || !a || t.id !== a.buildingId)) {
1185
+ const g = Ke(t.roof, (C) => this.xy(C), t.id, this.materials);
1186
+ for (const C of this.batch.addObject(g, t.id, !0)) this.scene.add(C);
1187
+ }
1188
+ }
1189
+ if (it.size && this.envelope(i, it, w, u), !n && e === null && !L) {
1190
+ const t = new Map(i.floors.filter((g) => g.elevation >= 0).map((g) => [g.id, g]));
1191
+ this.envelope(i, t, w, u);
1192
+ for (const g of i.buildings) {
1193
+ if (g.roof) continue;
1194
+ const C = i.floors.filter((z) => z.buildingId === g.id && z.elevation >= 0).sort((z, X) => z.elevation - X.elevation).at(-1), B = C && ((jt = y.outlines.get(C.id)) == null ? void 0 : jt.rings);
1195
+ C && B && this.surface(B, C.elevation + C.height, $, "#747976", g.id, "roof");
1196
+ }
1197
+ }
1198
+ if (L && f && !r) {
1199
+ const t = y.outlines.get((a == null ? void 0 : a.id) ?? ((te = x.levels[0]) == null ? void 0 : te.id) ?? ""), g = si(i, x.levels, n ? 1 / 0 : a == null ? void 0 : a.elevation), C = g.length ? g : (ee = t == null ? void 0 : t.rings) != null && ee[0] ? [zt(t.rings[0])] : [], B = ft((a == null ? void 0 : a.elevation) ?? 0), z = Math.min(
1200
+ B,
1201
+ j ? ft(j.elevation) : B,
1202
+ ...(y.objects.get(e ?? "") ?? []).filter((T) => T.slope).map((T) => ft(Math.min(T.slope.low, T.slope.high)))
1203
+ ), X = n ? ft(x.levels.reduce((T, G) => Math.min(T, G.elevation), 0)) - 2.5 : z - 2.5, K = x.levels.filter((T) => T.elevation < 0).map((T) => ft(T.elevation));
1204
+ for (const T of C) {
1205
+ const G = T.map((k) => this.xy(k)), M = G.reduce((k, R) => k + R[0], 0) / G.length, W = G.reduce((k, R) => k + R[1], 0) / G.length;
1206
+ this.scene.add(
1207
+ ai(
1208
+ G.map(([k, R]) => [M + (k - M) * 1.025, W + (R - W) * 1.025]),
1209
+ B,
1210
+ X,
1211
+ K
1212
+ )
1213
+ );
1214
+ }
1215
+ }
1216
+ if (L && a && f && !r && (!n || D)) {
1217
+ const t = x.levels.filter((T) => T.elevation > a.elevation && (n || T.elevation <= 0)).sort((T, G) => T.elevation - G.elevation), g = Math.max(1, Math.ceil((t.length - 1) / (oi - 1))), C = t.filter((T, G) => G % g === 0 || G === t.length - 1), B = [];
1218
+ let z = [], X = 0;
1219
+ for (const T of C) {
1220
+ const G = y.outlines.get(T.id);
1221
+ if (!G) continue;
1222
+ const M = zt(G.rings[0]);
1223
+ B.push(M.map((R) => new v.Vector3(...this.xy(R), ft(T.elevation) + q)));
1224
+ const W = T.mezzanine ? 0 : Math.abs(De(M));
1225
+ if (z.length && W <= X) continue;
1226
+ X = W;
1227
+ const k = Math.max(1, Math.ceil(M.length / 16));
1228
+ z = M.filter((R, ht) => ht % k === 0).map((R) => this.xy(R));
1229
+ }
1230
+ const K = t.at(-1);
1231
+ this.cage.configure(
1232
+ B,
1233
+ z,
1234
+ ft(a.elevation) + q,
1235
+ K ? ft(K.elevation + (n ? K.height : 0)) : 0,
1236
+ m
1237
+ ), this.cage.setBearing(((ie = this.map) == null ? void 0 : ie.getBearing()) ?? 0), this.scene.add(this.cage);
1238
+ }
1239
+ const Vt = this.present((n ? (a == null ? void 0 : a.elevation) ?? 0 : lt(e)) + q + $);
1240
+ if (r && e) {
1241
+ const t = [...y.objects.get(e) ?? [], ...tt].filter((g) => g.kind === "light" && g.light).map((g) => g.floorId === e ? g : { ...g, height: g.height + lt(g.floorId) });
1242
+ t.length && (this.fixtureLights = new di(this.scene, t, (g) => this.xy(g), Vt, this.materials));
1243
+ }
1244
+ if (r && (this.passenger = new Ri(this.cabinFinishes().environment), this.scene.add(this.passenger)), this.batch.finish(this.scene), this.hasWater && e && Mi(this.scene, i, e, (t) => this.xy(t), Vt, this.waterTime), n && a) {
1245
+ for (const t of this.scene.children)
1246
+ if (t instanceof v.Mesh) {
1247
+ const g = Array.isArray(t.material) ? t.material[0] : t.material;
1248
+ if (!g.transparent || g.depthWrite) continue;
1249
+ t.geometry.computeBoundingBox(), t.renderOrder = (((se = t.geometry.boundingBox) == null ? void 0 : se.min.z) ?? 0) >= this.present(a.elevation) ? 1 : -1;
1250
+ }
1251
+ }
1252
+ if (!L) {
1253
+ const t = new v.Box3().setFromObject(this.scene);
1254
+ if (!t.isEmpty()) {
1255
+ const g = t.getSize(new v.Vector3()), C = t.getCenter(new v.Vector3()), B = Math.max(24, g.z * 4), z = new v.Mesh(
1256
+ new v.PlaneGeometry(g.x + B * 2, g.y + B * 2),
1257
+ new v.ShadowMaterial({
1258
+ color: m ? "#282637" : "#3d4950",
1259
+ // A weak sun casts a weak shadow. Held at a fixed darkness, a midwinter noon threw a
1260
+ // shadow as black as midsummer's across a city it was barely lighting.
1261
+ opacity: m ? 0.34 : 0.1 + 0.2 * _.day,
1262
+ depthWrite: !1
1263
+ })
1264
+ );
1265
+ z.position.set(C.x, C.y, F + 0.012), z.layers.set(ii), z.receiveShadow = !0, z.raycast = () => {
1266
+ }, this.scene.add(z);
1267
+ }
1268
+ }
1269
+ this.fitShadowCamera(), this.scene.add(this.highlight), this.refreshHighlight(), this.pickables = [], this.scene.traverse((t) => {
1270
+ !(t instanceof v.Mesh) || !(Array.isArray(t.material) ? t.material : [t.material]).some((C) => C.visible && (!C.transparent || C.depthWrite)) || (t.userData.entityId || t.userData.entityIds || t.userData.spans) && this.pickables.push(t);
1271
+ }), this.buildRoute(), this.refreshShadows(), (ne = this.map) == null || ne.triggerRepaint();
1272
+ }
1273
+ /** Show/replace the 3D route. When only the active step changed, restyle materials in place;
1274
+ * otherwise rebuild the route group. Route state is retained across update() rebuilds — every
1275
+ * rebuild re-runs buildRoute with FRESH materials, because disposeScene disposes all mesh
1276
+ * materials and a cached material would die silently on the next floor switch. */
1277
+ setRoute(i, e = null) {
1278
+ var s;
1279
+ if (i === this.route && e === this.routeStep && (this.routeGroup !== null || !i)) return;
1280
+ const n = i !== null && i === this.route && this.routeGroup !== null;
1281
+ this.route = i, this.routeStep = e, n ? this.restyleRoute() : this.buildRoute(), (s = this.map) == null || s.triggerRepaint();
1282
+ }
1283
+ /** The track's texture already carries the green and the white kerb, so the material's own colour
1284
+ * stays white and only emphasis separates the step being walked from the rest of the path. A
1285
+ * route is one route: dimming the other steps says "later", where recolouring them would say
1286
+ * "different". */
1287
+ routeStyle(i) {
1288
+ return { opacity: this.routeStep === null || i === this.routeStep ? 1 : 0.5 };
1289
+ }
1290
+ restyleRoute() {
1291
+ var i;
1292
+ (i = this.routeGroup) == null || i.traverse((e) => {
1293
+ e instanceof v.Mesh && (e.material.opacity = this.routeStyle(e.userData.step).opacity);
1294
+ });
1295
+ }
1296
+ buildRoute() {
1297
+ var w, y;
1298
+ this.clipBoundsDirty = !0, this.rigs = this.rigs.filter((d) => d.id !== Pt), this.rigState.delete(Pt), this.routeGroup && (this.routeGroup.removeFromParent(), this.routeGroup.traverse((d) => {
1299
+ d instanceof v.Mesh && (d.geometry.dispose(), d.material.dispose());
1300
+ }), (w = this.routeTile) == null || w.dispose(), this.routeTile = null, this.routeGroup = null);
1301
+ const i = this.route, e = this.project;
1302
+ if (!i || !e) return;
1303
+ const n = Mt(e).floors, s = n.get(this.activeFloor ?? ""), o = (d) => {
1304
+ var S, p;
1305
+ return !(!this.buried && d && (((S = n.get(d)) == null ? void 0 : S.elevation) ?? 0) < 0 || this.buried && (d === null || s && ((p = n.get(d ?? "")) == null ? void 0 : p.buildingId) !== s.buildingId) || !this.stack && d !== null && d !== this.activeFloor);
1306
+ }, l = (d) => {
1307
+ var S;
1308
+ return d === null ? (
1309
+ // Out of doors is grade, and a walk has moved grade — see sceneGround.
1310
+ this.ground + Ct + 0.05
1311
+ ) : this.present((this.stack ? ((S = n.get(d)) == null ? void 0 : S.elevation) ?? 0 : this.rebase) + q + $ + Ie + 0.07);
1312
+ }, f = new v.Group(), r = Ve(i), m = new v.DataTexture(
1313
+ new Uint8Array(We(this.mapStyle.routeActive ?? le).data.buffer),
1314
+ he[0],
1315
+ he[1],
1316
+ v.RGBAFormat
1317
+ );
1318
+ m.wrapS = v.RepeatWrapping, m.colorSpace = v.SRGBColorSpace, m.generateMipmaps = !0, m.minFilter = v.LinearMipmapLinearFilter, m.magFilter = v.LinearFilter, m.anisotropy = Math.min(8, ((y = this.renderer) == null ? void 0 : y.capabilities.getMaxAnisotropy()) ?? 1), m.needsUpdate = !0;
1319
+ const h = (d, S, p = !0) => {
1320
+ const u = new v.Mesh(
1321
+ d,
1322
+ new v.MeshBasicMaterial({
1323
+ color: p ? "#ffffff" : this.mapStyle.routeActive ?? le,
1324
+ map: p ? m : null,
1325
+ opacity: this.routeStyle(S).opacity,
1326
+ transparent: !0,
1327
+ depthWrite: !1,
1328
+ side: v.DoubleSide
1329
+ })
1330
+ );
1331
+ u.renderOrder = 10, u.raycast = () => {
1332
+ }, u.userData.step = S, f.add(u);
1333
+ };
1334
+ let c = null;
1335
+ const I = () => {
1336
+ if (c && c.points.length > 1 && o(c.fid)) {
1337
+ const d = this.ribbonGeometry(c.points, l(c.fid), 0.7);
1338
+ d && h(d, c.step);
1339
+ }
1340
+ c = null;
1341
+ };
1342
+ for (let d = 0; d < i.legs.length; d++) {
1343
+ const S = i.legs[d], p = r[d];
1344
+ if (S.edge.kind === "stairs" || S.edge.kind === "elevator") {
1345
+ I();
1346
+ const [x, a] = this.xy(S.from.position);
1347
+ if (this.stack) {
1348
+ if (!o(S.from.floorId) && !o(S.to.floorId)) continue;
1349
+ const L = l(S.from.floorId), F = l(S.to.floorId), D = new v.CylinderGeometry(0.14, 0.14, Math.abs(F - L) || 0.1, 10);
1350
+ D.rotateX(Math.PI / 2), D.translate(x, a, (L + F) / 2), h(D, p, !1);
1351
+ } else if (S.from.floorId === this.activeFloor || S.to.floorId === this.activeFloor) {
1352
+ const L = new v.CylinderGeometry(0.8, 0.8, 0.05, 28);
1353
+ L.rotateX(Math.PI / 2), L.translate(x, a, l(this.activeFloor) + 0.02), h(L, p, !1);
1354
+ }
1355
+ continue;
1356
+ }
1357
+ const u = S.from.floorId === S.to.floorId ? S.from.floorId : S.from.floorId ?? S.to.floorId;
1358
+ (!c || c.step !== p || c.fid !== u) && (I(), c = { step: p, fid: u, points: [S.from.position] }), c.points.push(S.to.position);
1359
+ }
1360
+ if (I(), !f.children.length) {
1361
+ m.dispose();
1362
+ return;
1363
+ }
1364
+ this.routeGroup = f, this.routeTile = m, this.rig(
1365
+ Pt,
1366
+ f,
1367
+ 1,
1368
+ 0.55,
1369
+ // cycles a second: one arrow-length every ~1.8 s, a walking pace rather than a barber's pole
1370
+ (d, S) => {
1371
+ m.offset.x = -S;
1372
+ },
1373
+ !0
1374
+ ), this.scene.add(f);
1375
+ }
1376
+ /** Flat mitred triangle-strip ribbon at a constant z, in scene metres. */
1377
+ ribbonGeometry(i, e, n) {
1378
+ const s = [];
1379
+ for (const h of i) {
1380
+ const c = this.xy(h);
1381
+ (!s.length || Math.hypot(c[0] - s[s.length - 1][0], c[1] - s[s.length - 1][1]) > 0.01) && s.push(c);
1382
+ }
1383
+ if (s.length < 2) return null;
1384
+ const o = [], l = [], f = [];
1385
+ let r = 0;
1386
+ for (let h = 0; h < s.length; h++) {
1387
+ h && (r += Math.hypot(s[h][0] - s[h - 1][0], s[h][1] - s[h - 1][1]));
1388
+ const c = s[Math.max(0, h - 1)], I = s[Math.min(s.length - 1, h + 1)], w = I[0] - c[0], y = I[1] - c[1], d = Math.hypot(w, y) || 1;
1389
+ let S = -y / d, p = w / d, u = n;
1390
+ if (h > 0 && h < s.length - 1) {
1391
+ const x = s[h][0] - s[h - 1][0], a = s[h][1] - s[h - 1][1], L = Math.hypot(x, a) || 1, F = s[h + 1][0] - s[h][0], D = s[h + 1][1] - s[h][1], nt = Math.hypot(F, D) || 1, U = -(a / L + D / nt), _ = x / L + F / nt, et = Math.hypot(U, _);
1392
+ if (et > 1e-3) {
1393
+ S = U / et, p = _ / et;
1394
+ const ot = S * (-a / L) + p * (x / L);
1395
+ u = n / Math.max(0.4, Math.abs(ot));
1396
+ }
1397
+ }
1398
+ if (o.push(s[h][0] + S * u, s[h][1] + p * u, e, s[h][0] - S * u, s[h][1] - p * u, e), l.push(r / ce, 0, r / ce, 1), h) {
1399
+ const x = h * 2;
1400
+ f.push(x - 2, x - 1, x, x - 1, x + 1, x);
1401
+ }
1402
+ }
1403
+ const m = new v.BufferGeometry();
1404
+ return m.setAttribute("position", new v.Float32BufferAttribute(o, 3)), m.setAttribute("uv", new v.Float32BufferAttribute(l, 2)), m.setIndex(f), m;
1405
+ }
1406
+ // Grows the building out of the map when entering 3D so the mode switch reads as one motion.
1407
+ animateIn() {
1408
+ var i;
1409
+ this.growth = 0, this.growthStart = performance.now(), (i = this.map) == null || i.triggerRepaint();
1410
+ }
1411
+ refreshHighlight() {
1412
+ var i;
1413
+ for (const e of [...this.highlight.children]) {
1414
+ const n = e;
1415
+ n.geometry.dispose(), n.material.dispose();
1416
+ }
1417
+ this.highlight.clear();
1418
+ for (const [e, n] of [
1419
+ [this.hovered, this.mapStyle.hover ?? "#8d85dc"],
1420
+ [this.selected, this.mapStyle.selection ?? "#6355d4"]
1421
+ ])
1422
+ if (e) {
1423
+ const s = this.batch.outline(e, n);
1424
+ s && this.highlight.add(s);
1425
+ }
1426
+ (i = this.map) == null || i.triggerRepaint();
1427
+ }
1428
+ setHover(i) {
1429
+ i !== this.hovered && (this.hovered = i, this.refreshHighlight());
1430
+ }
1431
+ get diagnostics() {
1432
+ var i, e, n;
1433
+ return {
1434
+ revision: this.revision,
1435
+ drawCalls: ((i = this.renderer) == null ? void 0 : i.info.render.calls) ?? 0,
1436
+ geometries: ((e = this.renderer) == null ? void 0 : e.info.memory.geometries) ?? 0,
1437
+ textures: ((n = this.renderer) == null ? void 0 : n.info.memory.textures) ?? 0,
1438
+ animatedWater: this.hasWater
1439
+ };
1440
+ }
1441
+ render(i, e) {
1442
+ var m, h, c, I, w, y;
1443
+ if (!this.renderer) return;
1444
+ i.clear(i.DEPTH_BUFFER_BIT);
1445
+ const n = performance.now();
1446
+ this.hasWater && (this.waterTime.value = n / 1e3, this.paintSoon());
1447
+ const s = Math.min(0.1, this.lastFrame ? (n - this.lastFrame) / 1e3 : 0);
1448
+ this.lastFrame = n, this.rigs.length && this.animate(s) && (this.shadowsMoved && this.refreshShadows(), this.shadowsMoved ? (m = this.map) == null || m.triggerRepaint() : this.paintSoon()), this.growth < 1 ? (this.growth = Math.min(1, (performance.now() - this.growthStart) / 550), this.scene.scale.z = 0.03 + 0.97 * (1 - (1 - this.growth) ** 3), this.refreshShadows(), (h = this.map) == null || h.triggerRepaint()) : this.scene.scale.z = 1;
1449
+ const o = (I = (c = this.map) == null ? void 0 : c.transform) == null ? void 0 : I.mercatorMatrix;
1450
+ this.worldToClip.fromArray((o == null ? void 0 : o.length) === 16 ? o : e.defaultProjectionData.mainMatrix).multiply(this.transform);
1451
+ const l = this.walking ? li(this.worldToClip, e.nearZ, e.farZ) : e.nearZ;
1452
+ if (this.buried && (this.clipBoundsDirty && (this.scene.updateMatrixWorld(!0), this.clipBounds.setFromObject(this.scene).applyMatrix4(new v.Matrix4().copy(this.scene.matrixWorld).invert()), this.clipBoundsDirty = !1), hi(this.worldToClip, this.clipBounds, this.scene.scale.z, l, e.farZ)), this.clipToWorld.copy(this.worldToClip).invert(), ci(this.camera, this.worldToClip, this.clipToWorld), this.passenger) {
1453
+ const d = new v.Vector3(0, 0, 0).applyMatrix4(this.clipToWorld).sub(this.camera.position).normalize();
1454
+ this.passenger.update(this.camera.position, d, n / 1e3, s) && this.paintSoon(), this.passenger.updateMatrixWorld(!0);
1455
+ }
1456
+ if (this.fixtureLights) {
1457
+ const { shadowsChanged: d, animate: S } = this.fixtureLights.update(this.camera.position, n / 1e3);
1458
+ d && (this.renderer.shadowMap.needsUpdate = !0), S && this.paintSoon();
1459
+ }
1460
+ const f = (w = this.map) == null ? void 0 : w.getCanvas();
1461
+ f && this.renderer.setViewport(0, 0, f.width, f.height);
1462
+ const r = f ? `${f.clientWidth}:${f.clientHeight}` : "";
1463
+ (!this.previousProjection.equals(this.worldToClip) || this.previousScale !== this.scene.scale.z || this.previousSize !== r) && (this.markerCache.clear(), this.previousProjection.copy(this.worldToClip), this.previousScale = this.scene.scale.z, this.previousSize = r), this.cage.setBearing(((y = this.map) == null ? void 0 : y.getBearing()) ?? 0), this.renderer.resetState(), this.buried && (this.renderer.state.buffers.depth.setMask(!0), this.renderer.state.buffers.depth.setClear(1), this.renderer.clearDepth()), this.renderer.transmissionResolutionScale = this.hasWater ? 0.5 : 1, this.renderer.render(this.scene, this.camera), this.renderer.resetState();
1464
+ }
1465
+ /** Where a point on a floor's walking surface lands on screen — the avatar marker, which is not an
1466
+ * object and has no occlusion to test. `visible` is the frustum only. */
1467
+ projectPoint(i, e) {
1468
+ var o;
1469
+ if (!this.project || !this.map) return null;
1470
+ const n = (this.stack ? ((o = Mt(this.project).floors.get(e ?? "")) == null ? void 0 : o.elevation) ?? 0 : e ? this.rebase : 0) + Q(e), s = new v.Vector3(...this.xy(i), this.present(n) * this.scene.scale.z).applyMatrix4(this.worldToClip);
1471
+ return {
1472
+ x: (s.x + 1) / 2 * this.map.getCanvas().clientWidth,
1473
+ y: (1 - s.y) / 2 * this.map.getCanvas().clientHeight,
1474
+ visible: Math.abs(s.x) <= 1.05 && Math.abs(s.y) <= 1.05 && s.z < 1 && s.z > -1
1475
+ };
1476
+ }
1477
+ projectObject(i) {
1478
+ var m;
1479
+ if (!this.project || !this.map) return null;
1480
+ const e = this.markerCache.get(i.id);
1481
+ if (e) return e;
1482
+ const n = this.xy(fe(this.project, i)), s = (this.stack ? ((m = Mt(this.project).floors.get(i.floorId ?? "")) == null ? void 0 : m.elevation) ?? 0 : i.floorId ? this.rebase : 0) + Q(i.floorId) + (i.kind === "camera" ? i.height : 0.5), o = new v.Vector3(...n, this.present(s) * this.scene.scale.z), l = o.clone().applyMatrix4(this.worldToClip);
1483
+ let f = Math.abs(l.x) <= 1.05 && Math.abs(l.y) <= 1.05 && l.z < 1 && l.z > -1 && (!this.walking || this.camera.position.distanceTo(o) <= 12) && (!this.buried || i.floorId !== null) && (!this.stack || i.floorId === null || i.floorId === this.activeFloor);
1484
+ if (f && (this.stack || this.walking)) {
1485
+ const h = !this.map.isMoving(), c = this.occlusionCache.get(i.id);
1486
+ if (!this.walking && !h && c !== void 0) f = c;
1487
+ else {
1488
+ const I = new v.Vector3(l.x, l.y, -1).applyMatrix4(this.clipToWorld), w = I.distanceTo(o), y = (this.present(s) - this.present(s - 0.25)) * this.scene.scale.z, d = new v.Raycaster(I, o.clone().sub(I).normalize(), 0, Math.max(0, w - y));
1489
+ d.layers.enableAll(), f = !d.intersectObjects(this.pickables, !1).some((S) => S.object instanceof v.Mesh && ve(S) !== i.id), this.occlusionCache.set(i.id, f);
1490
+ }
1491
+ }
1492
+ const r = {
1493
+ x: (l.x + 1) / 2 * this.map.getCanvas().clientWidth,
1494
+ y: (1 - l.y) / 2 * this.map.getCanvas().clientHeight,
1495
+ visible: f
1496
+ };
1497
+ return this.markerCache.set(i.id, r), r;
1498
+ }
1499
+ pick(i) {
1500
+ if (!this.map) return null;
1501
+ const e = i.x / this.map.getCanvas().clientWidth * 2 - 1, n = 1 - i.y / this.map.getCanvas().clientHeight * 2, s = new v.Vector3(e, n, -1).applyMatrix4(this.clipToWorld), o = new v.Vector3(e, n, 1).applyMatrix4(this.clipToWorld), l = new v.Raycaster(s, o.sub(s).normalize());
1502
+ return l.layers.enableAll(), l.intersectObjects(this.pickables, !1).map(ve).find(Boolean) ?? null;
1503
+ }
1504
+ disposeScene() {
1505
+ var n;
1506
+ this.hasWater = !1, this.passenger = void 0, this.fixtureLights = void 0, this.cage.dispose(), this.markerCache.clear(), this.occlusionCache.clear(), this.pickables = [], this.clipBoundsDirty = !0;
1507
+ const i = /* @__PURE__ */ new Set(), e = /* @__PURE__ */ new Set();
1508
+ this.scene.traverse((s) => {
1509
+ var o;
1510
+ s instanceof ki && s.getRenderTarget().dispose(), (s instanceof v.Mesh || s instanceof v.LineSegments) && (i.add(s.geometry), (Array.isArray(s.material) ? s.material : [s.material]).forEach((l) => {
1511
+ var f;
1512
+ l.userData.shared || e.add(l), l.userData.ownedMap && ((f = l.map) == null || f.dispose());
1513
+ }), s instanceof v.InstancedMesh && s.dispose()), s instanceof v.Light && "shadow" in s && ((o = s.shadow) == null || o.dispose());
1514
+ }), i.forEach((s) => s.dispose()), e.forEach((s) => s.dispose()), this.scene.clear(), this.highlight.clear(), this.batch.clear(), (n = this.routeTile) == null || n.dispose(), this.routeTile = null, this.routeGroup = null;
1515
+ }
1516
+ onRemove() {
1517
+ var i, e, n;
1518
+ clearTimeout(this.loopTimer), this.loopTimer = void 0, this.disposeScene(), this.materials.dispose(), (i = this.cabinMaterials) == null || i.dispose(), this.cabinMaterials = void 0, this.envCache.forEach((s) => s.dispose()), this.envCache.clear(), (e = this.renderer) == null || e.resetState(), (n = this.renderer) == null || n.dispose(), this.renderer = void 0, this.map = void 0;
1519
+ }
1520
+ }
1521
+ export {
1522
+ q as LIFT,
1523
+ $ as SLAB,
1524
+ Ts as SceneLayer,
1525
+ Me as nestingLift,
1526
+ gi as rampJoins,
1527
+ ke as rampVoids,
1528
+ $i as sceneElevation,
1529
+ Wi as sceneGround,
1530
+ Le as shellPlate,
1531
+ Ce as walkCeiling,
1532
+ Lt as walkSoffit
1533
+ };
1534
+ //# sourceMappingURL=SceneLayer.js.map