@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.
- package/LICENSE +21 -0
- package/README.md +31 -2
- package/dist/adapters/basemap.js +11 -0
- package/dist/adapters/basemap.js.map +1 -0
- package/dist/adapters/persistence.js +121 -0
- package/dist/adapters/persistence.js.map +1 -0
- package/dist/adapters/status.js +18 -0
- package/dist/adapters/status.js.map +1 -0
- package/dist/components/ElevatorPanel.js +120 -0
- package/dist/components/ElevatorPanel.js.map +1 -0
- package/dist/components/Icons.js +57 -0
- package/dist/components/Icons.js.map +1 -0
- package/dist/components/Inspector.js +1184 -0
- package/dist/components/Inspector.js.map +1 -0
- package/dist/components/NavigatePanel.js +301 -0
- package/dist/components/NavigatePanel.js.map +1 -0
- package/dist/components/NavigationGraph.js +234 -0
- package/dist/components/NavigationGraph.js.map +1 -0
- package/dist/components/StructureExplorer.js +510 -0
- package/dist/components/StructureExplorer.js.map +1 -0
- package/dist/components/StructureView.js +286 -0
- package/dist/components/StructureView.js.map +1 -0
- package/dist/components/controls.js +170 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/graphLayout.js +27 -0
- package/dist/components/graphLayout.js.map +1 -0
- package/dist/host.js +169 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n.js +88 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/map/CabinMaterials.js +81 -0
- package/dist/map/CabinMaterials.js.map +1 -0
- package/dist/map/CabinMirror.js +43 -0
- package/dist/map/CabinMirror.js.map +1 -0
- package/dist/map/Doors.js +80 -0
- package/dist/map/Doors.js.map +1 -0
- package/dist/map/ElevatorCabin.js +93 -0
- package/dist/map/ElevatorCabin.js.map +1 -0
- package/dist/map/FixtureLights.js +117 -0
- package/dist/map/FixtureLights.js.map +1 -0
- package/dist/map/MapCanvas.js +2161 -0
- package/dist/map/MapCanvas.js.map +1 -0
- package/dist/map/Passenger.js +69 -0
- package/dist/map/Passenger.js.map +1 -0
- package/dist/map/SceneLayer.js +1534 -0
- package/dist/map/SceneLayer.js.map +1 -0
- package/dist/map/UndergroundContext.js +153 -0
- package/dist/map/UndergroundContext.js.map +1 -0
- package/dist/map/ambience.js +194 -0
- package/dist/map/ambience.js.map +1 -0
- package/dist/map/architecture.js +305 -0
- package/dist/map/architecture.js.map +1 -0
- package/dist/map/bathMusic.js +92 -0
- package/dist/map/bathMusic.js.map +1 -0
- package/dist/map/cameraAim.js +33 -0
- package/dist/map/cameraAim.js.map +1 -0
- package/dist/map/elevators.js +56 -0
- package/dist/map/elevators.js.map +1 -0
- package/dist/map/excavation.js +52 -0
- package/dist/map/excavation.js.map +1 -0
- package/dist/map/exteriors.js +25 -0
- package/dist/map/exteriors.js.map +1 -0
- package/dist/map/features.js +218 -0
- package/dist/map/features.js.map +1 -0
- package/dist/map/fences.js +29 -0
- package/dist/map/fences.js.map +1 -0
- package/dist/map/journey.js +112 -0
- package/dist/map/journey.js.map +1 -0
- package/dist/map/levels.js +6 -0
- package/dist/map/levels.js.map +1 -0
- package/dist/map/lighting.js +142 -0
- package/dist/map/lighting.js.map +1 -0
- package/dist/map/loadBasemap.js +26 -0
- package/dist/map/loadBasemap.js.map +1 -0
- package/dist/map/materials.js +183 -0
- package/dist/map/materials.js.map +1 -0
- package/dist/map/poolLighting.js +39 -0
- package/dist/map/poolLighting.js.map +1 -0
- package/dist/map/projection.js +24 -0
- package/dist/map/projection.js.map +1 -0
- package/dist/map/rampGeometry.js +38 -0
- package/dist/map/rampGeometry.js.map +1 -0
- package/dist/map/route.js +107 -0
- package/dist/map/route.js.map +1 -0
- package/dist/map/stairSurfaces.js +115 -0
- package/dist/map/stairSurfaces.js.map +1 -0
- package/dist/map/surfaces.js +136 -0
- package/dist/map/surfaces.js.map +1 -0
- package/dist/map/textures.js +95 -0
- package/dist/map/textures.js.map +1 -0
- package/dist/map/underground.js +34 -0
- package/dist/map/underground.js.map +1 -0
- package/dist/map/walk.js +433 -0
- package/dist/map/walk.js.map +1 -0
- package/dist/map/walkContext.js +46 -0
- package/dist/map/walkContext.js.map +1 -0
- package/dist/map/walkDimensions.js +7 -0
- package/dist/map/walkDimensions.js.map +1 -0
- package/dist/map/walkJourney.js +85 -0
- package/dist/map/walkJourney.js.map +1 -0
- package/dist/map/walkSurfaces.js +106 -0
- package/dist/map/walkSurfaces.js.map +1 -0
- package/dist/map/wallJoins.js +70 -0
- package/dist/map/wallJoins.js.map +1 -0
- package/dist/map/water.js +274 -0
- package/dist/map/water.js.map +1 -0
- package/dist/model/authoring.js +80 -0
- package/dist/model/authoring.js.map +1 -0
- package/dist/model/boundaries.js +548 -0
- package/dist/model/boundaries.js.map +1 -0
- package/dist/model/coverage.js +30 -0
- package/dist/model/coverage.js.map +1 -0
- package/dist/model/document.js +52 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/doors.js +47 -0
- package/dist/model/doors.js.map +1 -0
- package/dist/model/factory.js +56 -0
- package/dist/model/factory.js.map +1 -0
- package/dist/model/geometry.js +394 -0
- package/dist/model/geometry.js.map +1 -0
- package/dist/model/inference.js +182 -0
- package/dist/model/inference.js.map +1 -0
- package/dist/model/materials.js +11 -0
- package/dist/model/materials.js.map +1 -0
- package/dist/model/mutations.js +157 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/navigation.js +275 -0
- package/dist/model/navigation.js.map +1 -0
- package/dist/model/ontology.js +109 -0
- package/dist/model/ontology.js.map +1 -0
- package/dist/model/passages.js +5 -0
- package/dist/model/passages.js.map +1 -0
- package/dist/model/portals.js +42 -0
- package/dist/model/portals.js.map +1 -0
- package/dist/model/precision.js +11 -0
- package/dist/model/precision.js.map +1 -0
- package/dist/model/project.js +17 -0
- package/dist/model/project.js.map +1 -0
- package/dist/model/ramps.js +10 -0
- package/dist/model/ramps.js.map +1 -0
- package/dist/model/roomNavigation.js +87 -0
- package/dist/model/roomNavigation.js.map +1 -0
- package/dist/model/routeClearance.js +72 -0
- package/dist/model/routeClearance.js.map +1 -0
- package/dist/model/spaces.js +104 -0
- package/dist/model/spaces.js.map +1 -0
- package/dist/model/stairGeometry.js +40 -0
- package/dist/model/stairGeometry.js.map +1 -0
- package/dist/model/stairTraversal.js +48 -0
- package/dist/model/stairTraversal.js.map +1 -0
- package/dist/model/topology.js +63 -0
- package/dist/model/topology.js.map +1 -0
- package/dist/model/types.js +35 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validate.js +240 -0
- package/dist/model/validate.js.map +1 -0
- package/dist/model/vertical.js +129 -0
- package/dist/model/vertical.js.map +1 -0
- package/dist/model/walls.js +71 -0
- package/dist/model/walls.js.map +1 -0
- package/dist/styles.css +4931 -0
- package/dist/theme/index.js +34 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/types/adapters/basemap.d.ts +2 -0
- package/dist/types/adapters/persistence.d.ts +29 -0
- package/dist/types/adapters/status.d.ts +4 -0
- package/dist/types/components/ElevatorPanel.d.ts +18 -0
- package/dist/types/components/Icons.d.ts +8 -0
- package/dist/types/components/Inspector.d.ts +38 -0
- package/dist/types/components/NavigatePanel.d.ts +27 -0
- package/dist/types/components/NavigationGraph.d.ts +10 -0
- package/dist/types/components/StructureExplorer.d.ts +8 -0
- package/dist/types/components/StructureView.d.ts +22 -0
- package/dist/types/components/controls.d.ts +52 -0
- package/dist/types/components/graphLayout.d.ts +9 -0
- package/dist/types/i18n.d.ts +85 -0
- package/dist/types/import/analysisPreview.d.ts +27 -0
- package/dist/types/import/brief.d.ts +22 -0
- package/dist/types/import/checkpoint.d.ts +49 -0
- package/dist/types/import/instructions.d.ts +7 -0
- package/dist/types/import/usage.d.ts +10 -0
- package/dist/types/map/CabinMaterials.d.ts +18 -0
- package/dist/types/map/CabinMirror.d.ts +13 -0
- package/dist/types/map/Doors.d.ts +11 -0
- package/dist/types/map/ElevatorCabin.d.ts +20 -0
- package/dist/types/map/FixtureLights.d.ts +35 -0
- package/dist/types/map/MapCanvas.d.ts +87 -0
- package/dist/types/map/Passenger.d.ts +18 -0
- package/dist/types/map/SceneLayer.d.ts +306 -0
- package/dist/types/map/UndergroundContext.d.ts +18 -0
- package/dist/types/map/ambience.d.ts +29 -0
- package/dist/types/map/architecture.d.ts +8 -0
- package/dist/types/map/bathMusic.d.ts +14 -0
- package/dist/types/map/cameraAim.d.ts +20 -0
- package/dist/types/map/elevators.d.ts +16 -0
- package/dist/types/map/excavation.d.ts +9 -0
- package/dist/types/map/exteriors.d.ts +3 -0
- package/dist/types/map/features.d.ts +33 -0
- package/dist/types/map/fences.d.ts +4 -0
- package/dist/types/map/journey.d.ts +36 -0
- package/dist/types/map/levels.d.ts +4 -0
- package/dist/types/map/lighting.d.ts +103 -0
- package/dist/types/map/loadBasemap.d.ts +4 -0
- package/dist/types/map/materials.d.ts +38 -0
- package/dist/types/map/poolLighting.d.ts +11 -0
- package/dist/types/map/projection.d.ts +9 -0
- package/dist/types/map/rampGeometry.d.ts +8 -0
- package/dist/types/map/route.d.ts +59 -0
- package/dist/types/map/stairSurfaces.d.ts +29 -0
- package/dist/types/map/surfaces.d.ts +36 -0
- package/dist/types/map/textures.d.ts +8 -0
- package/dist/types/map/underground.d.ts +30 -0
- package/dist/types/map/walk.d.ts +232 -0
- package/dist/types/map/walkContext.d.ts +11 -0
- package/dist/types/map/walkDimensions.d.ts +5 -0
- package/dist/types/map/walkJourney.d.ts +33 -0
- package/dist/types/map/walkSurfaces.d.ts +27 -0
- package/dist/types/map/wallJoins.d.ts +6 -0
- package/dist/types/map/water.d.ts +12 -0
- package/dist/types/model/authoring.d.ts +38 -0
- package/dist/types/model/boundaries.d.ts +41 -0
- package/dist/types/model/coverage.d.ts +8 -0
- package/dist/types/model/document.d.ts +9 -0
- package/dist/types/model/doors.d.ts +19 -0
- package/dist/types/model/factory.d.ts +5 -0
- package/dist/types/model/geometry.d.ts +99 -0
- package/dist/types/model/host.d.ts +154 -0
- package/dist/types/model/inference.d.ts +46 -0
- package/dist/types/model/live.d.ts +60 -0
- package/dist/types/model/materials.d.ts +28 -0
- package/dist/types/model/mutations.d.ts +178 -0
- package/dist/types/model/navigation.d.ts +70 -0
- package/dist/types/model/ontology.d.ts +43 -0
- package/dist/types/model/passages.d.ts +2 -0
- package/dist/types/model/portals.d.ts +9 -0
- package/dist/types/model/precision.d.ts +11 -0
- package/dist/types/model/project.d.ts +8 -0
- package/dist/types/model/ramps.d.ts +7 -0
- package/dist/types/model/roomNavigation.d.ts +15 -0
- package/dist/types/model/routeClearance.d.ts +11 -0
- package/dist/types/model/spaces.d.ts +46 -0
- package/dist/types/model/stairGeometry.d.ts +27 -0
- package/dist/types/model/stairTraversal.d.ts +8 -0
- package/dist/types/model/topology.d.ts +14 -0
- package/dist/types/model/types.d.ts +369 -0
- package/dist/types/model/validate.d.ts +38 -0
- package/dist/types/model/vertical.d.ts +145 -0
- package/dist/types/model/walls.d.ts +71 -0
- package/dist/types/schema/index.d.ts +25 -0
- package/dist/types/theme/index.d.ts +55 -0
- package/dist/types/viewer/FloorViewer.d.ts +76 -0
- package/dist/types/viewer/ViewerWorkspace.d.ts +2 -0
- package/dist/types/viewer/host.d.ts +11 -0
- package/dist/types/viewer/index.d.ts +2 -0
- package/dist/types/viewer/panels.d.ts +12 -0
- package/dist/viewer/FloorViewer.js +104 -0
- package/dist/viewer/FloorViewer.js.map +1 -0
- package/dist/viewer/ViewerWorkspace.js +370 -0
- package/dist/viewer/ViewerWorkspace.js.map +1 -0
- package/dist/viewer/panels.js +35 -0
- package/dist/viewer/panels.js.map +1 -0
- 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
|