@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,142 @@
|
|
|
1
|
+
const s = Math.PI / 180, u = 180 / Math.PI;
|
|
2
|
+
function N(t, n = /* @__PURE__ */ new Date()) {
|
|
3
|
+
const [a, e] = t, o = (typeof n == "number" ? n : n.getTime()) / 864e5 - 10957.5, i = (280.46 + 0.9856474 * o) * s, c = (357.528 + 0.9856003 * o) * s, l = i + (1.915 * Math.sin(c) + 0.02 * Math.sin(2 * c)) * s, r = (23.439 - 4e-7 * o) * s, h = Math.asin(Math.sin(r) * Math.sin(l)), M = Math.atan2(Math.cos(r) * Math.sin(l), Math.cos(l)), d = ((18.697374558 + 24.06570982441908 * o) % 24 * 15 + a) * s - M, v = Math.sin(e * s), I = Math.cos(e * s), T = Math.asin(v * Math.sin(h) + I * Math.cos(h) * Math.cos(d)), p = Math.atan2(-Math.sin(d), Math.tan(h) * I - v * Math.cos(d));
|
|
4
|
+
return { altitude: T * u, azimuth: (p * u % 360 + 360) % 360 };
|
|
5
|
+
}
|
|
6
|
+
const f = -6;
|
|
7
|
+
function z(t, n = /* @__PURE__ */ new Date()) {
|
|
8
|
+
const a = N(t, n);
|
|
9
|
+
return { ...a, evening: a.altitude < f };
|
|
10
|
+
}
|
|
11
|
+
const w = {
|
|
12
|
+
day: { altitude: 34, azimuth: 247, evening: !1 }
|
|
13
|
+
};
|
|
14
|
+
function E(t, n, a) {
|
|
15
|
+
if (n <= t[0][0]) return t[0][1];
|
|
16
|
+
for (let e = 1; e < t.length; e++) {
|
|
17
|
+
const [o, i] = t[e], [c, l] = t[e - 1];
|
|
18
|
+
if (n <= o) return a(l, i, (n - c) / (o - c || 1));
|
|
19
|
+
}
|
|
20
|
+
return t[t.length - 1][1];
|
|
21
|
+
}
|
|
22
|
+
const x = (t) => [
|
|
23
|
+
Number.parseInt(t.slice(1, 3), 16),
|
|
24
|
+
Number.parseInt(t.slice(3, 5), 16),
|
|
25
|
+
Number.parseInt(t.slice(5, 7), 16)
|
|
26
|
+
], y = (t) => `#${t.map(
|
|
27
|
+
(n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0")
|
|
28
|
+
).join("")}`, G = (t, n, a) => m(t, n, Math.max(0, Math.min(1, a))), m = (t, n, a) => {
|
|
29
|
+
const [e, o, i] = x(t), [c, l, r] = x(n);
|
|
30
|
+
return y([e + (c - e) * a, o + (l - o) * a, i + (r - i) * a]);
|
|
31
|
+
}, b = (t, n, a) => t + (n - t) * a;
|
|
32
|
+
function D(t) {
|
|
33
|
+
const n = 0.12 * Math.max(0, Math.min(1, (t - f) / -f)) * Math.max(0, Math.min(1, 1 - t / 3));
|
|
34
|
+
return L(t) + n;
|
|
35
|
+
}
|
|
36
|
+
function L(t) {
|
|
37
|
+
return t <= 0 ? 0 : 0.7 ** (1 / (Math.sin(t * s) + 0.50572 * (t + 6.07995) ** -1.6364)) ** 0.678;
|
|
38
|
+
}
|
|
39
|
+
const U = (t) => {
|
|
40
|
+
const n = Math.max(t.altitude, t.evening ? 4 : 7), a = 100 * Math.cos(n * s), e = [
|
|
41
|
+
a * Math.sin(t.azimuth * s),
|
|
42
|
+
a * Math.cos(t.azimuth * s),
|
|
43
|
+
100 * Math.sin(n * s)
|
|
44
|
+
], [o, i, c] = e;
|
|
45
|
+
return {
|
|
46
|
+
position: e,
|
|
47
|
+
// Low light is long light: the blue is scattered out of it before it arrives.
|
|
48
|
+
color: E(
|
|
49
|
+
[
|
|
50
|
+
[-6, "#ffb083"],
|
|
51
|
+
[2, "#ffc094"],
|
|
52
|
+
[10, "#ffdcb4"],
|
|
53
|
+
[25, "#fff1dc"],
|
|
54
|
+
[45, "#fff8ee"]
|
|
55
|
+
],
|
|
56
|
+
t.altitude,
|
|
57
|
+
m
|
|
58
|
+
),
|
|
59
|
+
// A fifth of what it was, and falling with the air the beam comes through. Two reasons. The sun
|
|
60
|
+
// no longer has to carry the scene — the building's own lighting does that — and a cutaway has
|
|
61
|
+
// no roof, so direct sun lands on a shop floor that in life never sees any. What the sun is for
|
|
62
|
+
// here is direction: which way the shadows fall and which façade is the bright one.
|
|
63
|
+
intensity: 2.3 * D(t.altitude),
|
|
64
|
+
mapPosition: [1.5, (Math.atan2(o, i) * u + 360) % 360, Math.atan2(Math.hypot(o, i), c) * u]
|
|
65
|
+
};
|
|
66
|
+
}, P = [
|
|
67
|
+
{ id: "tungsten", label: "Tungsten", kelvin: 2700 },
|
|
68
|
+
{ id: "halogen", label: "Halogen", kelvin: 3e3 },
|
|
69
|
+
{ id: "warm-led", label: "Warm LED", kelvin: 3500 },
|
|
70
|
+
{ id: "fluorescent", label: "Fluorescent", kelvin: 4e3 },
|
|
71
|
+
{ id: "daylight", label: "Daylight LED", kelvin: 5e3 },
|
|
72
|
+
{ id: "cool", label: "Cool white", kelvin: 6500 }
|
|
73
|
+
], R = { kelvin: 4e3, level: 1 }, C = (t) => P.reduce((n, a) => Math.abs(a.kelvin - t) < Math.abs(n.kelvin - t) ? a : n), S = 4500;
|
|
74
|
+
function k(t) {
|
|
75
|
+
const n = Math.max(1e3, Math.min(12e3, t)) / 100, a = (e) => Math.max(1, Math.min(255, e));
|
|
76
|
+
return [
|
|
77
|
+
n <= 66 ? 255 : a(329.698727446 * (n - 60) ** -0.1332047592),
|
|
78
|
+
a(n <= 66 ? 99.4708025861 * Math.log(n) - 161.1195681661 : 288.1221695283 * (n - 60) ** -0.0755148492),
|
|
79
|
+
n >= 66 ? 255 : n <= 19 ? 1 : a(138.5177312231 * Math.log(n - 10) - 305.0447927307)
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
function A(t) {
|
|
83
|
+
const n = k(t), a = k(S), e = n.map((M, g) => M / a[g]), o = Math.max(...e), [i, c, l] = e.map((M) => M / o * 255), r = (0.2126 * i + 0.7152 * c + 0.0722 * l) / 255, h = r > 0.01 ? 0.94 / r : 1;
|
|
84
|
+
return y([i * h, c * h, l * h]);
|
|
85
|
+
}
|
|
86
|
+
function F(t, n = R) {
|
|
87
|
+
const a = E(
|
|
88
|
+
[
|
|
89
|
+
[-6, 0],
|
|
90
|
+
[0, 0.3],
|
|
91
|
+
[12, 0.75],
|
|
92
|
+
[40, 1]
|
|
93
|
+
],
|
|
94
|
+
t.altitude,
|
|
95
|
+
b
|
|
96
|
+
), e = b(0.18, 0.26, a), o = b(0.2, 0.28, a), i = 2.3 * L(t.altitude) * Math.max(0, Math.sin(t.altitude * s)) + e + o, c = Math.max(0, Math.min(1, n.level));
|
|
97
|
+
return {
|
|
98
|
+
/** Hemisphere sky and ground colours, and its strength: the outdoor half. */
|
|
99
|
+
sky: m("#8ea7cf", "#c7ddf5", a),
|
|
100
|
+
ground: m("#3f3a48", "#b9ac94", a),
|
|
101
|
+
hemisphere: e,
|
|
102
|
+
/** The image-based environment, for specular life on glass and metal. */
|
|
103
|
+
environment: o,
|
|
104
|
+
/** The building's own lights: colour and strength, both from the floor, neither following the
|
|
105
|
+
* sun. A level that says nothing is lit by a fluorescent ceiling, because most levels are.
|
|
106
|
+
*
|
|
107
|
+
* A ceiling, which is to say it comes from ABOVE. Drawn as light arriving equally from every
|
|
108
|
+
* direction it lit the underside of a landing as brightly as the floor under it, and to get a
|
|
109
|
+
* floor bright enough it had to be turned up until the walls glowed — so the floor takes the
|
|
110
|
+
* full value and a wall about half of it, which is what a room of downlights actually does. */
|
|
111
|
+
interior: n.tint ?? A(n.kelvin),
|
|
112
|
+
/** What comes back UP off the floor: the same lamps, much weaker, which is the half of the
|
|
113
|
+
* picture that keeps a soffit from going black. */
|
|
114
|
+
interiorBounce: m(n.tint ?? A(n.kelvin), "#000000", 0.6),
|
|
115
|
+
// The ceiling makes up the difference between what the sky is giving and what a lit interior is
|
|
116
|
+
// supposed to sit at — which is what a real building does. A workplace is designed to a lux
|
|
117
|
+
// level and modern lighting control holds it there, dimming as the sun comes round and coming up
|
|
118
|
+
// as it goes; the lamps being "always on" is about the level being held, not about a fixed
|
|
119
|
+
// wattage. It is also the only way a plan is equally readable at every hour, which is the point
|
|
120
|
+
// of drawing one. A floor dimmed below full sits proportionally lower, and one set to nothing
|
|
121
|
+
// goes dark, because an unlit level is a thing a building has.
|
|
122
|
+
interiorLevel: c > 0 ? Math.max(0.25, 4.2 * c - i) : 0,
|
|
123
|
+
/** How far along the fade to night the scene is: 1 in full day, 0 after civil twilight. */
|
|
124
|
+
day: a,
|
|
125
|
+
/** What the sky alone puts on a level surface, before the ceiling makes up the difference. */
|
|
126
|
+
daylight: i
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
f as CIVIL_TWILIGHT,
|
|
131
|
+
R as DEFAULT_LIGHT,
|
|
132
|
+
w as FIXED,
|
|
133
|
+
P as LAMPS,
|
|
134
|
+
F as ambient,
|
|
135
|
+
A as kelvinColor,
|
|
136
|
+
C as lampFor,
|
|
137
|
+
G as mixColor,
|
|
138
|
+
N as solarPosition,
|
|
139
|
+
z as sunAt,
|
|
140
|
+
U as sunlight
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=lighting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lighting.js","sources":["../../../../src/map/lighting.ts"],"sourcesContent":["// Where the sun is, and how much of the light in the picture is its doing.\n//\n// The scene used to have two lightings — \"day\" and \"evening\" — chosen by a switch, and day was a\n// fixed strong lamp from the west-south-west. That is not what light does. A building at 60°N never\n// sees the sun higher than 53°, and for half the year it barely clears the rooftops; drawn under a\n// permanent midday sun from the wrong quarter, a model's shadows fall in a direction the building\n// has never seen and its façades read far brighter than they ever are. So the sun comes from the\n// clock and the place instead, and the dusk treatment is what that produces after sunset rather\n// than a mode someone remembers to turn on.\n//\n// The other half is that a building is lit from inside as well. Offices, shop floors and garages\n// burn their own light all day and all night, and a plan that goes dark at sunset is describing a\n// power cut, not an evening. So the interior term here does not follow the sun at all: it is what\n// the building itself contributes, and the sun is added on top of it. That is also why the sun is\n// weaker than it used to be — it is no longer carrying the whole scene.\nimport type { InteriorLight, Point } from '../model/types';\nexport type { InteriorLight } from '../model/types';\n\n/** The sun at the top of its scale, before the air takes its cut. Deliberately modest: see\n * `sunlight` for why the beam is no longer the thing lighting the model. */\nconst SUN_PEAK = 2.3;\n\n/** What a fully lit FLOOR sits at, all sources together, held across the day so the same storey\n * reads the same at noon and at midnight — see `ambient`.\n *\n * Set so a plate comes back at about the lightness it was authored at: a floor written as a pale\n * grey should read as a pale grey. A cutaway is mostly floor — an open-plan storey has nothing in\n * it to catch a highlight, and for most of a Nordic day the whole plate stands in the shadow of its\n * own façade — so this number is very nearly what you see, and it has to be generous for the\n * drawing to look like the inside of a working building rather than the inside of a cupboard. */\nconst TARGET_INTERIOR = 4.2;\n\nconst RAD = Math.PI / 180;\nconst DEG = 180 / Math.PI;\n\n/** Where the sun stands, seen from a place on the earth at a moment. */\nexport interface SolarPosition {\n /** Degrees above the horizon. Negative once the sun has set. */\n altitude: number;\n /** Degrees clockwise from due north: 90 is east, 180 south, 270 west. */\n azimuth: number;\n}\n\n/** The sun's position over `lngLat` at `when`.\n *\n * The NOAA low-precision formulae: good to about a hundredth of a degree over a century either\n * side of 2000, which is several orders of magnitude better than a picture of a building needs and\n * costs a few lines of arithmetic. Time is read in UTC, so no timezone table is involved — the\n * earth's rotation is the clock, and a longitude is a time of day.\n *\n * Atmospheric refraction is left out. It lifts a sun on the horizon by about half a degree, which\n * matters to an almanac and not to which way a shadow falls. */\nexport function solarPosition(lngLat: Point, when: Date | number = new Date()): SolarPosition {\n const [lng, lat] = lngLat;\n // Days since J2000.0 (2000-01-01 12:00 UTC), the epoch every term below is written against.\n const n = (typeof when === 'number' ? when : when.getTime()) / 86400000 - 10957.5;\n const meanLongitude = (280.46 + 0.9856474 * n) * RAD;\n const meanAnomaly = (357.528 + 0.9856003 * n) * RAD;\n // The earth's orbit is an ellipse, so the sun runs ahead of its mean position and falls behind it\n // over the year. Two terms of the equation of the centre carry that to well under a minute of arc.\n const ecliptic = meanLongitude + (1.915 * Math.sin(meanAnomaly) + 0.02 * Math.sin(2 * meanAnomaly)) * RAD;\n const obliquity = (23.439 - 0.0000004 * n) * RAD;\n const declination = Math.asin(Math.sin(obliquity) * Math.sin(ecliptic));\n const rightAscension = Math.atan2(Math.cos(obliquity) * Math.sin(ecliptic), Math.cos(ecliptic));\n // Greenwich mean sidereal time: where the sky has turned to. Sidereal days are about four minutes\n // short of solar ones, which is the whole reason the stars — and the sun against them — drift.\n const gmst = (18.697374558 + 24.06570982441908 * n) % 24;\n const localSidereal = (gmst * 15 + lng) * RAD;\n const hourAngle = localSidereal - rightAscension;\n const sinLat = Math.sin(lat * RAD),\n cosLat = Math.cos(lat * RAD);\n const altitude = Math.asin(sinLat * Math.sin(declination) + cosLat * Math.cos(declination) * Math.cos(hourAngle));\n const azimuth = Math.atan2(-Math.sin(hourAngle), Math.tan(declination) * cosLat - sinLat * Math.cos(hourAngle));\n return { altitude: altitude * DEG, azimuth: (((azimuth * DEG) % 360) + 360) % 360 };\n}\n\n/** How the scene is lit at a moment: where the sun is, and whether it is low enough that the model\n * should wear its dusk treatment. */\nexport interface Sun extends SolarPosition {\n /** Dusk: lamps on, warm low light, dark sky. True below civil twilight. */\n evening: boolean;\n}\n\n/** Civil twilight. Below this the sky is no longer doing the lighting and the lamps are what you\n * see by — which at 60°N in June the sun only just reaches, for an hour either side of midnight,\n * and that is the honest picture of a white night. */\nexport const CIVIL_TWILIGHT = -6;\n\n/** The sun over a project's origin at a moment, ready to light a scene with. */\nexport function sunAt(lngLat: Point, when: Date | number = new Date()): Sun {\n const at = solarPosition(lngLat, when);\n return { ...at, evening: at.altitude < CIVIL_TWILIGHT };\n}\n\n/** A sun pinned to a look rather than to a clock, for the manual override and for tests. Afternoon\n * and dusk, at the angles the two fixed lightings used before this file knew where the sun was. */\nexport const FIXED: Record<'day' | 'evening', Sun> = {\n day: { altitude: 34, azimuth: 247, evening: false },\n evening: { altitude: -3, azimuth: 294, evening: true },\n};\n\n/** Linear interpolation through a table of (altitude, value) stops, flat outside its ends. */\nfunction graded<T>(stops: [number, T][], altitude: number, mix: (a: T, b: T, t: number) => T): T {\n if (altitude <= stops[0][0]) return stops[0][1];\n for (let i = 1; i < stops.length; i++) {\n const [hi, above] = stops[i],\n [lo, below] = stops[i - 1];\n if (altitude <= hi) return mix(below, above, (altitude - lo) / (hi - lo || 1));\n }\n return stops[stops.length - 1][1];\n}\n\nconst hex = (c: string): [number, number, number] => [\n Number.parseInt(c.slice(1, 3), 16),\n Number.parseInt(c.slice(3, 5), 16),\n Number.parseInt(c.slice(5, 7), 16),\n];\nconst rgb = (c: [number, number, number]) =>\n `#${c\n .map(v =>\n Math.round(Math.max(0, Math.min(255, v)))\n .toString(16)\n .padStart(2, '0'),\n )\n .join('')}`;\n/** Mix two hex colours. Exported so the basemap's sky can follow the same sun the model does\n * rather than snapping between a day palette and a night one. */\nexport const mixColor = (a: string, b: string, t: number) => blend(a, b, Math.max(0, Math.min(1, t)));\nconst blend = (a: string, b: string, t: number) => {\n const [ar, ag, ab] = hex(a),\n [br, bg, bb] = hex(b);\n return rgb([ar + (br - ar) * t, ag + (bg - ag) * t, ab + (bb - ab) * t]);\n};\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\n/** How much of the beam survives the air it comes through.\n *\n * Kasten–Young air mass and a Meinel transmittance — two lines that say the thing everyone knows:\n * a sun overhead is fierce and a sun on the horizon is not, because the low one is shining through\n * ten times as much atmosphere. This is why the scene no longer needs a \"too strong\" fixed value.\n * Helsinki's sun tops out at 53° in June and 6° at midwinter, and the difference now shows. */\nfunction beam(altitude: number): number {\n // Afterglow. Below the horizon the sky is still lit from under the rim, and that light still\n // arrives from a direction — it is what gives a dusk façade its warm edge, and drawing dusk with\n // no directional light at all left the model flat and lifeless. Faded in as the sun nears the\n // horizon and out again at civil twilight, so nothing jumps as it crosses.\n const glow =\n 0.12 *\n Math.max(0, Math.min(1, (altitude - CIVIL_TWILIGHT) / -CIVIL_TWILIGHT)) *\n Math.max(0, Math.min(1, 1 - altitude / 3));\n return beamAbove(altitude) + glow;\n}\n\n/** The direct beam alone, with no allowance for afterglow: what the sun is actually delivering to a\n * surface, and so what the ceiling has to make up. */\nfunction beamAbove(altitude: number): number {\n if (altitude <= 0) return 0;\n const airMass = 1 / (Math.sin(altitude * RAD) + 0.50572 * (altitude + 6.07995) ** -1.6364);\n return 0.7 ** (airMass ** 0.678);\n}\n\n/** Scene axes are east, north, up. Both renderers use this world-anchored sun. */\nexport const sunlight = (sun: Sun) => {\n // Held a little above the true horizon: a sun at 2° throws a shadow a hundred times the height of\n // whatever casts it, and the shadow map would spend its whole resolution on a smear across the\n // next parcel. Keep the direction, borrow a few degrees of height.\n const altitude = Math.max(sun.altitude, sun.evening ? 4 : 7);\n const r = 100 * Math.cos(altitude * RAD);\n const position: [number, number, number] = [\n r * Math.sin(sun.azimuth * RAD),\n r * Math.cos(sun.azimuth * RAD),\n 100 * Math.sin(altitude * RAD),\n ];\n const [x, y, z] = position;\n return {\n position,\n // Low light is long light: the blue is scattered out of it before it arrives.\n color: graded<string>(\n [\n [-6, '#ffb083'],\n [2, '#ffc094'],\n [10, '#ffdcb4'],\n [25, '#fff1dc'],\n [45, '#fff8ee'],\n ],\n sun.altitude,\n blend,\n ),\n // A fifth of what it was, and falling with the air the beam comes through. Two reasons. The sun\n // no longer has to carry the scene — the building's own lighting does that — and a cutaway has\n // no roof, so direct sun lands on a shop floor that in life never sees any. What the sun is for\n // here is direction: which way the shadows fall and which façade is the bright one.\n intensity: SUN_PEAK * beam(sun.altitude),\n mapPosition: [1.5, (Math.atan2(x, y) * DEG + 360) % 360, Math.atan2(Math.hypot(x, y), z) * DEG] as [\n number,\n number,\n number,\n ],\n };\n};\n\n/** The artificial light a level burns.\n *\n * A building is not lit by the sky. Offices, shop floors, garages and platforms run their own\n * lighting through the working day and often through the night, and what colour that light is says\n * as much about a space as its plan does: a 4000 K fluorescent office, a 2700 K hotel corridor and\n * a 5000 K operating theatre are three different rooms before anything is drawn in them. So it is\n * a property of the floor, authored like its name and its height, rather than a render setting. */\n/** The lamps a floor is likely to be fitted with, warmest first. Fluorescent is the default because\n * it is what is actually overhead in most of the buildings anyone draws: offices, shops, schools,\n * hospitals, car parks. */\nexport const LAMPS: { id: string; label: string; kelvin: number }[] = [\n { id: 'tungsten', label: 'Tungsten', kelvin: 2700 },\n { id: 'halogen', label: 'Halogen', kelvin: 3000 },\n { id: 'warm-led', label: 'Warm LED', kelvin: 3500 },\n { id: 'fluorescent', label: 'Fluorescent', kelvin: 4000 },\n { id: 'daylight', label: 'Daylight LED', kelvin: 5000 },\n { id: 'cool', label: 'Cool white', kelvin: 6500 },\n];\n\n/** What a floor is lit by when it does not say: a fluorescent ceiling, fully on. Most floors are\n * fully lit; dimming is the thing worth authoring. */\nexport const DEFAULT_LIGHT: InteriorLight = { kelvin: 4000, level: 1 };\n\n/** The nearest named lamp to a colour temperature, for showing an authored number as a choice. */\nexport const lampFor = (kelvin: number) =>\n LAMPS.reduce((best, lamp) => (Math.abs(lamp.kelvin - kelvin) < Math.abs(best.kelvin - kelvin) ? lamp : best));\n\n/** Where white is. A room lit at 4500 K reads as neutral to someone standing in it, because eyes\n * adapt to the light they are under — which is why a 4000 K fluorescent office looks white and not\n * amber, though a 4000 K blackbody plotted against daylight is distinctly amber. Rendering the raw\n * blackbody colour stains the whole model beige; rendering it RELATIVE to this leaves the tint that\n * a person in the room would actually notice. */\nconst NEUTRAL_KELVIN = 4500;\n\n/** Tanner Helland's fit to the blackbody locus — three logarithms rather than a colour-science\n * dependency, and within a couple of percent across the range a luminaire lives in. */\nfunction blackbody(kelvin: number): [number, number, number] {\n const t = Math.max(1000, Math.min(12000, kelvin)) / 100;\n const clamp = (v: number) => Math.max(1, Math.min(255, v));\n return [\n t <= 66 ? 255 : clamp(329.698727446 * (t - 60) ** -0.1332047592),\n clamp(t <= 66 ? 99.4708025861 * Math.log(t) - 161.1195681661 : 288.1221695283 * (t - 60) ** -0.0755148492),\n t >= 66 ? 255 : t <= 19 ? 1 : clamp(138.5177312231 * Math.log(t - 10) - 305.0447927307),\n ];\n}\n\n/** A colour temperature as a light colour: relative to the adapted white above, and held at one\n * brightness so that changing the temperature changes the TINT and nothing else. How bright a floor\n * is lit is the level's job, and the two must not fight each other. */\nexport function kelvinColor(kelvin: number): string {\n const here = blackbody(kelvin),\n white = blackbody(NEUTRAL_KELVIN);\n const ratio = here.map((v, i) => v / white[i]) as [number, number, number];\n const peak = Math.max(...ratio);\n const [r, g, b] = ratio.map(v => (v / peak) * 255) as [number, number, number];\n // Rec. 709 luma, held just under white, so a tungsten floor is warm rather than dim.\n const luma = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;\n const scale = luma > 0.01 ? 0.94 / luma : 1;\n return rgb([r * scale, g * scale, b * scale]);\n}\n\n/** Everything in the picture that is not the sun.\n *\n * Two contributions, and they are different in kind. The SKY is daylight bouncing off the ground\n * and the air, so it follows the sun down and goes out with it. The INTERIOR is the building's own\n * lighting, and that does not follow anything: a shop floor is lit at four in the afternoon and at\n * four in the morning, which is why a cutaway of a storey at midnight is legible rather than black.\n * Kerros draws buildings from the inside, so this is most of what you are looking at. */\nexport function ambient(sun: Sun, light: InteriorLight = DEFAULT_LIGHT) {\n const day = graded<number>(\n [\n [-6, 0],\n [0, 0.3],\n [12, 0.75],\n [40, 1],\n ],\n sun.altitude,\n lerp,\n );\n const hemisphere = lerp(0.18, 0.26, day),\n environment = lerp(0.2, 0.28, day);\n // What the sky alone puts on a level surface: the beam foreshortened by its own angle, plus the\n // two diffuse terms. This is the number the ceiling has to make up.\n const daylight =\n SUN_PEAK * beamAbove(sun.altitude) * Math.max(0, Math.sin(sun.altitude * RAD)) + hemisphere + environment;\n const lit = Math.max(0, Math.min(1, light.level));\n return {\n /** Hemisphere sky and ground colours, and its strength: the outdoor half. */\n sky: blend('#8ea7cf', '#c7ddf5', day),\n ground: blend('#3f3a48', '#b9ac94', day),\n hemisphere,\n /** The image-based environment, for specular life on glass and metal. */\n environment,\n /** The building's own lights: colour and strength, both from the floor, neither following the\n * sun. A level that says nothing is lit by a fluorescent ceiling, because most levels are.\n *\n * A ceiling, which is to say it comes from ABOVE. Drawn as light arriving equally from every\n * direction it lit the underside of a landing as brightly as the floor under it, and to get a\n * floor bright enough it had to be turned up until the walls glowed — so the floor takes the\n * full value and a wall about half of it, which is what a room of downlights actually does. */\n interior: light.tint ?? kelvinColor(light.kelvin),\n /** What comes back UP off the floor: the same lamps, much weaker, which is the half of the\n * picture that keeps a soffit from going black. */\n interiorBounce: blend(light.tint ?? kelvinColor(light.kelvin), '#000000', 0.6),\n // The ceiling makes up the difference between what the sky is giving and what a lit interior is\n // supposed to sit at — which is what a real building does. A workplace is designed to a lux\n // level and modern lighting control holds it there, dimming as the sun comes round and coming up\n // as it goes; the lamps being \"always on\" is about the level being held, not about a fixed\n // wattage. It is also the only way a plan is equally readable at every hour, which is the point\n // of drawing one. A floor dimmed below full sits proportionally lower, and one set to nothing\n // goes dark, because an unlit level is a thing a building has.\n interiorLevel: lit > 0 ? Math.max(0.25, TARGET_INTERIOR * lit - daylight) : 0,\n /** How far along the fade to night the scene is: 1 in full day, 0 after civil twilight. */\n day,\n /** What the sky alone puts on a level surface, before the ceiling makes up the difference. */\n daylight,\n };\n}\n"],"names":["RAD","DEG","solarPosition","lngLat","when","lng","lat","n","meanLongitude","meanAnomaly","ecliptic","obliquity","declination","rightAscension","hourAngle","sinLat","cosLat","altitude","azimuth","CIVIL_TWILIGHT","sunAt","at","FIXED","graded","stops","mix","i","hi","above","lo","below","hex","c","rgb","v","mixColor","a","b","t","blend","ar","ag","ab","br","bg","bb","lerp","beam","glow","beamAbove","sunlight","sun","r","position","x","y","z","LAMPS","DEFAULT_LIGHT","lampFor","kelvin","best","lamp","NEUTRAL_KELVIN","blackbody","clamp","kelvinColor","here","white","ratio","peak","g","luma","scale","ambient","light","day","hemisphere","environment","daylight","lit"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
async function n(s, y) {
|
|
2
|
+
var i;
|
|
3
|
+
const u = (e) => {
|
|
4
|
+
var p;
|
|
5
|
+
return ((p = s.styleTransform) == null ? void 0 : p.call(s, e)) ?? e;
|
|
6
|
+
};
|
|
7
|
+
if (typeof s.style != "string") return u(s.style);
|
|
8
|
+
const l = await ((i = s.transformRequest) == null ? void 0 : i.call(
|
|
9
|
+
s,
|
|
10
|
+
s.style,
|
|
11
|
+
"Style"
|
|
12
|
+
)) ?? { url: s.style }, a = await fetch(l.url, { signal: y, headers: l.headers, credentials: l.credentials });
|
|
13
|
+
if (!a.ok) throw new Error(`Basemap style returned ${a.status}`);
|
|
14
|
+
const r = await a.json();
|
|
15
|
+
if (r.version !== 8 || !r.sources || !Array.isArray(r.layers))
|
|
16
|
+
throw new Error("Invalid MapLibre basemap style");
|
|
17
|
+
const t = (e) => new URL(e, a.url || l.url).toString().replaceAll("%7B", "{").replaceAll("%7D", "}");
|
|
18
|
+
r.glyphs && (r.glyphs = t(r.glyphs)), typeof r.sprite == "string" ? r.sprite = t(r.sprite) : Array.isArray(r.sprite) && (r.sprite = r.sprite.map((e) => ({ ...e, url: t(e.url) })));
|
|
19
|
+
for (const e of Object.values(r.sources))
|
|
20
|
+
"url" in e && typeof e.url == "string" && (e.url = t(e.url)), "tiles" in e && e.tiles && (e.tiles = e.tiles.map(t));
|
|
21
|
+
return u(r);
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
n as loadBasemap
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=loadBasemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadBasemap.js","sources":["../../../../src/map/loadBasemap.ts"],"sourcesContent":["import type { StyleSpecification } from 'maplibre-gl';\nimport type { BasemapConfig } from '../model/host';\n\n/** Fetch the external style without replacing the working, locally rendered plan. */\nexport async function loadBasemap(config: BasemapConfig, signal: AbortSignal): Promise<StyleSpecification> {\n const tune = (style: StyleSpecification) => config.styleTransform?.(style) ?? style;\n if (typeof config.style !== 'string') return tune(config.style);\n const request = (await config.transformRequest?.(\n config.style,\n 'Style' as Parameters<NonNullable<BasemapConfig['transformRequest']>>[1],\n )) ?? { url: config.style };\n const response = await fetch(request.url, { signal, headers: request.headers, credentials: request.credentials });\n if (!response.ok) throw new Error(`Basemap style returned ${response.status}`);\n const style = (await response.json()) as StyleSpecification;\n if (style.version !== 8 || !style.sources || !Array.isArray(style.layers))\n throw new Error('Invalid MapLibre basemap style');\n const absolute = (url: string) =>\n new URL(url, response.url || request.url).toString().replaceAll('%7B', '{').replaceAll('%7D', '}');\n if (style.glyphs) style.glyphs = absolute(style.glyphs);\n if (typeof style.sprite === 'string') style.sprite = absolute(style.sprite);\n else if (Array.isArray(style.sprite))\n style.sprite = style.sprite.map(sprite => ({ ...sprite, url: absolute(sprite.url) }));\n for (const source of Object.values(style.sources)) {\n if ('url' in source && typeof source.url === 'string') source.url = absolute(source.url);\n if ('tiles' in source && source.tiles) source.tiles = source.tiles.map(absolute);\n }\n return tune(style);\n}\n"],"names":["loadBasemap","config","signal","_a","tune","style","request","response","absolute","url","sprite","source"],"mappings":"AAIA,eAAsBA,EAAYC,GAAuBC,GAAkD;AAA3G,MAAAC;AACE,QAAMC,IAAO,CAACC,MAAAA;AADhB,QAAAF;AAC8C,aAAAA,IAAAF,EAAO,mBAAP,gBAAAE,EAAA,KAAAF,GAAwBI,OAAUA;AAAAA;AAC9E,MAAI,OAAOJ,EAAO,SAAU,SAAU,QAAOG,EAAKH,EAAO,KAAK;AAC9D,QAAMK,IAAW,QAAMH,IAAAF,EAAO,qBAAP,gBAAAE,EAAA;AAAA,IAAAF;AAAA,IACrBA,EAAO;AAAA,IACP;AAAA,QACI,EAAE,KAAKA,EAAO,MAAA,GACdM,IAAW,MAAM,MAAMD,EAAQ,KAAK,EAAE,QAAAJ,GAAQ,SAASI,EAAQ,SAAS,aAAaA,EAAQ,aAAa;AAChH,MAAI,CAACC,EAAS,GAAI,OAAM,IAAI,MAAM,0BAA0BA,EAAS,MAAM,EAAE;AAC7E,QAAMF,IAAS,MAAME,EAAS,KAAA;AAC9B,MAAIF,EAAM,YAAY,KAAK,CAACA,EAAM,WAAW,CAAC,MAAM,QAAQA,EAAM,MAAM;AACtE,UAAM,IAAI,MAAM,gCAAgC;AAClD,QAAMG,IAAW,CAACC,MAChB,IAAI,IAAIA,GAAKF,EAAS,OAAOD,EAAQ,GAAG,EAAE,SAAA,EAAW,WAAW,OAAO,GAAG,EAAE,WAAW,OAAO,GAAG;AACnG,EAAID,EAAM,WAAQA,EAAM,SAASG,EAASH,EAAM,MAAM,IAClD,OAAOA,EAAM,UAAW,aAAgB,SAASG,EAASH,EAAM,MAAM,IACjE,MAAM,QAAQA,EAAM,MAAM,MACjCA,EAAM,SAASA,EAAM,OAAO,IAAI,CAAAK,OAAW,EAAE,GAAGA,GAAQ,KAAKF,EAASE,EAAO,GAAG,IAAI;AACtF,aAAWC,KAAU,OAAO,OAAON,EAAM,OAAO;AAC9C,IAAI,SAASM,KAAU,OAAOA,EAAO,OAAQ,aAAUA,EAAO,MAAMH,EAASG,EAAO,GAAG,IACnF,WAAWA,KAAUA,EAAO,YAAc,QAAQA,EAAO,MAAM,IAAIH,CAAQ;AAEjF,SAAOJ,EAAKC,CAAK;AACnB;"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
var m = Object.defineProperty;
|
|
2
|
+
var c = (h, e, s) => e in h ? m(h, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : h[e] = s;
|
|
3
|
+
var o = (h, e, s) => c(h, typeof e != "symbol" ? e + "" : e, s);
|
|
4
|
+
import * as r from "three";
|
|
5
|
+
import { surfaceTextures as d } from "./textures.js";
|
|
6
|
+
class S {
|
|
7
|
+
constructor() {
|
|
8
|
+
o(this, "materials", /* @__PURE__ */ new Map());
|
|
9
|
+
o(this, "textures", []);
|
|
10
|
+
o(this, "finishes", /* @__PURE__ */ new Map());
|
|
11
|
+
}
|
|
12
|
+
// Slightly glossier than fully matte: with an environment present, roughness is what decides how
|
|
13
|
+
// much tonal gradient a large surface picks up across its extent. At 0.85 a big floor plate shades
|
|
14
|
+
// almost uniformly and reads as flat paper; easing it back lets the sky/ground gradient sweep across.
|
|
15
|
+
solid(e, s = 0.72) {
|
|
16
|
+
const t = `solid:${e}:${s}`;
|
|
17
|
+
if (!this.materials.has(t)) {
|
|
18
|
+
const a = new r.MeshStandardMaterial({ color: e, roughness: s, side: r.DoubleSide });
|
|
19
|
+
a.userData.shared = !0, this.materials.set(t, a);
|
|
20
|
+
}
|
|
21
|
+
return this.materials.get(t);
|
|
22
|
+
}
|
|
23
|
+
/** The active storey's own plate, when the stack has storeys under it. Opaque enough to read as
|
|
24
|
+
* the floor you are standing on, sheer enough that the levels beneath show through it — without
|
|
25
|
+
* this the plate is a lid and "All floors" shows one. depthWrite stays on: the plate must still
|
|
26
|
+
* occlude its own walls' hidden faces, and the ghosts below carry depthWrite off already, so they
|
|
27
|
+
* blend rather than fight it. */
|
|
28
|
+
plate(e, s = 0.72) {
|
|
29
|
+
const t = `plate:${e}:${s}`;
|
|
30
|
+
if (!this.materials.has(t)) {
|
|
31
|
+
const a = new r.MeshStandardMaterial({
|
|
32
|
+
color: e,
|
|
33
|
+
roughness: s,
|
|
34
|
+
side: r.DoubleSide,
|
|
35
|
+
transparent: !0,
|
|
36
|
+
opacity: 0.55
|
|
37
|
+
});
|
|
38
|
+
a.userData.shared = !0, this.materials.set(t, a);
|
|
39
|
+
}
|
|
40
|
+
return this.materials.get(t);
|
|
41
|
+
}
|
|
42
|
+
/** A lit ceiling. Its underside faces down, so every light in the scene misses it and a plain
|
|
43
|
+
* material renders it as the darkest thing in the room — which is the opposite of what a ceiling
|
|
44
|
+
* full of fittings looks like from beneath. Emissive is the honest fix: the ceiling is not lit,
|
|
45
|
+
* it is the thing doing the lighting, and `tint` is whatever the floor's lamps are. */
|
|
46
|
+
luminous(e, s, t, a) {
|
|
47
|
+
const l = `luminous:${e}:${s}:${t.toFixed(2)}:${a ?? ""}`;
|
|
48
|
+
if (!this.materials.has(l)) {
|
|
49
|
+
const i = a ? this.get(a, e).clone() : new r.MeshStandardMaterial({ color: e, roughness: 0.9, side: r.DoubleSide });
|
|
50
|
+
i.emissive = new r.Color(s), i.emissiveIntensity = t, i.emissiveMap = i.map, i.side = r.DoubleSide, i.userData.shared = !0, this.materials.set(l, i);
|
|
51
|
+
}
|
|
52
|
+
return this.materials.get(l);
|
|
53
|
+
}
|
|
54
|
+
/** A polished twin of a finish, for the floor you are standing on in walk mode. Seen from above a
|
|
55
|
+
* floor is a flat patch of its own colour and matte is right; seen along at eye level it is the
|
|
56
|
+
* one surface in the room that shows you where the light is, because a grazing angle is where
|
|
57
|
+
* reflectance lives. The roughness map still multiplies through, so the polish keeps the finish's
|
|
58
|
+
* own variation instead of turning the plate into a mirror. */
|
|
59
|
+
polished(e, s = 0.5) {
|
|
60
|
+
const t = `polished:${e.uuid}:${s}`;
|
|
61
|
+
if (!this.materials.has(t)) {
|
|
62
|
+
const a = e.clone();
|
|
63
|
+
a.roughness = s, a.userData.shared = !0, this.materials.set(t, a);
|
|
64
|
+
}
|
|
65
|
+
return this.materials.get(t);
|
|
66
|
+
}
|
|
67
|
+
/** A vertex-coloured twin of an existing material, so geometry can carry baked shading without
|
|
68
|
+
* doubling the palette. Cached per source material: everything drawn with the twin must supply a
|
|
69
|
+
* colour attribute, since a merge needs matching attributes across all its geometries. */
|
|
70
|
+
shaded(e) {
|
|
71
|
+
const s = `shaded:${e.uuid}`;
|
|
72
|
+
if (!this.materials.has(s)) {
|
|
73
|
+
const t = e.clone();
|
|
74
|
+
t.vertexColors = !0, t.userData.shared = !0, this.materials.set(s, t);
|
|
75
|
+
}
|
|
76
|
+
return this.materials.get(s);
|
|
77
|
+
}
|
|
78
|
+
/** Translucent variant for de-emphasised floors in the stacked view. */
|
|
79
|
+
ghost(e, s = 0.17) {
|
|
80
|
+
const t = `ghost:${e}:${s}`;
|
|
81
|
+
if (!this.materials.has(t)) {
|
|
82
|
+
const a = new r.Color(e).multiplyScalar(0.42), l = new r.MeshStandardMaterial({
|
|
83
|
+
color: a,
|
|
84
|
+
roughness: 0.9,
|
|
85
|
+
side: r.DoubleSide,
|
|
86
|
+
transparent: !0,
|
|
87
|
+
opacity: s,
|
|
88
|
+
depthWrite: !1
|
|
89
|
+
});
|
|
90
|
+
l.userData.shared = !0, this.materials.set(t, l);
|
|
91
|
+
}
|
|
92
|
+
return this.materials.get(t);
|
|
93
|
+
}
|
|
94
|
+
get(e, s) {
|
|
95
|
+
const t = `${e}:${s}`;
|
|
96
|
+
if (this.materials.has(t)) return this.materials.get(t);
|
|
97
|
+
let a = this.finishes.get(e);
|
|
98
|
+
a || (a = d(e), this.finishes.set(e, a), this.textures.push(...a));
|
|
99
|
+
const [l, i, u] = a, n = new r.MeshStandardMaterial({
|
|
100
|
+
color: s,
|
|
101
|
+
map: l,
|
|
102
|
+
bumpMap: i,
|
|
103
|
+
roughnessMap: u,
|
|
104
|
+
bumpScale: e === "veneer" ? 4e-4 : e === "brick" ? 0.018 : e === "roof" ? 0.026 : e === "grass" ? 0.025 : e === "tile" ? 1e-3 : 6e-3,
|
|
105
|
+
roughness: 1,
|
|
106
|
+
metalness: e === "roof" ? 0.48 : 0,
|
|
107
|
+
side: r.DoubleSide
|
|
108
|
+
});
|
|
109
|
+
return n.userData.shared = !0, n.userData.finish = e, this.materials.set(t, n), n;
|
|
110
|
+
}
|
|
111
|
+
glass(e = !1, s = !1) {
|
|
112
|
+
const t = `glass:${e}:${s}`;
|
|
113
|
+
if (!this.materials.has(t)) {
|
|
114
|
+
const a = new r.MeshPhysicalMaterial({
|
|
115
|
+
// Daylight glazing was a muted grey-green that went nearly black against a bright wall —
|
|
116
|
+
// the colour of old float glass in a photograph, not of a window you are looking at. A pale
|
|
117
|
+
// sky tint with less metal in it reads as glass with daylight behind it.
|
|
118
|
+
color: e ? "#5b6a75" : "#b9d2dc",
|
|
119
|
+
roughness: 0.1,
|
|
120
|
+
metalness: 0.12,
|
|
121
|
+
clearcoat: 1,
|
|
122
|
+
clearcoatRoughness: 0.05,
|
|
123
|
+
envMapIntensity: 2.1,
|
|
124
|
+
emissive: "#ffc780",
|
|
125
|
+
emissiveIntensity: e && s ? 0.65 : 0,
|
|
126
|
+
// Glass you can see through. It was opaque, so a window was a coloured panel in a wall and
|
|
127
|
+
// a room stayed a sealed box however many openings it had; the light in the plan came only
|
|
128
|
+
// from the missing ceiling. Kept well short of invisible — a pane has to read as a pane at
|
|
129
|
+
// a distance, and a fully clear one leaves nothing but its frame.
|
|
130
|
+
transparent: !0,
|
|
131
|
+
opacity: e && s ? 0.92 : 0.42,
|
|
132
|
+
// Still writes depth. Without it a pane occludes nothing, which is invisible on a house and
|
|
133
|
+
// catastrophic on a tower: a hundred glazed storeys all show through one another and the
|
|
134
|
+
// building becomes a haze with no floor you can pick out.
|
|
135
|
+
depthWrite: !0,
|
|
136
|
+
side: r.DoubleSide
|
|
137
|
+
});
|
|
138
|
+
a.userData.shared = !0, this.materials.set(t, a);
|
|
139
|
+
}
|
|
140
|
+
return this.materials.get(t);
|
|
141
|
+
}
|
|
142
|
+
metal(e = "#626c6d") {
|
|
143
|
+
const s = `metal:${e}`;
|
|
144
|
+
if (!this.materials.has(s)) {
|
|
145
|
+
const t = new r.MeshStandardMaterial({ color: e, metalness: 0.72, roughness: 0.32 });
|
|
146
|
+
t.userData.shared = !0, this.materials.set(s, t);
|
|
147
|
+
}
|
|
148
|
+
return this.materials.get(s);
|
|
149
|
+
}
|
|
150
|
+
/** One image per step; the UVs follow the machine's run, including rotated/reversed flights. */
|
|
151
|
+
escalator() {
|
|
152
|
+
const e = "escalator-tread";
|
|
153
|
+
if (!this.materials.has(e)) {
|
|
154
|
+
const s = document.createElement("canvas");
|
|
155
|
+
s.width = s.height = 512;
|
|
156
|
+
const t = s.getContext("2d");
|
|
157
|
+
t.fillStyle = "#a4aaac", t.fillRect(0, 0, 512, 512);
|
|
158
|
+
for (let i = 0; i < 512; i += 4)
|
|
159
|
+
t.fillStyle = "#30383c", t.fillRect(i, 0, 1, 512), t.fillStyle = "#d5dadb", t.fillRect(i + 1, 0, 1, 512);
|
|
160
|
+
t.fillStyle = "#dcb735", t.fillRect(0, 0, 512, 12), t.fillRect(0, 500, 512, 12), t.fillRect(0, 0, 8, 512), t.fillRect(504, 0, 8, 512);
|
|
161
|
+
const a = new r.CanvasTexture(s);
|
|
162
|
+
a.colorSpace = r.SRGBColorSpace, a.anisotropy = 8;
|
|
163
|
+
const l = new r.MeshStandardMaterial({
|
|
164
|
+
color: "#ffffff",
|
|
165
|
+
map: a,
|
|
166
|
+
bumpMap: a,
|
|
167
|
+
bumpScale: 2e-3,
|
|
168
|
+
metalness: 0.65,
|
|
169
|
+
roughness: 0.38,
|
|
170
|
+
side: r.DoubleSide
|
|
171
|
+
});
|
|
172
|
+
l.userData.shared = !0, l.userData.finish = "escalator-tread", this.materials.set(e, l), this.textures.push(a);
|
|
173
|
+
}
|
|
174
|
+
return this.materials.get(e);
|
|
175
|
+
}
|
|
176
|
+
dispose() {
|
|
177
|
+
this.materials.forEach((e) => e.dispose()), this.textures.forEach((e) => e.dispose()), this.materials.clear(), this.finishes.clear(), this.textures = [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
S as MaterialLibrary
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=materials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materials.js","sources":["../../../../src/map/materials.ts"],"sourcesContent":["import * as THREE from 'three';\nimport { surfaceTextures, type SurfaceFinish } from './textures';\n\n/** Small deterministic, metre-scaled textures keep exported projects independent of CDNs. */\nexport class MaterialLibrary {\n private materials = new Map<string, THREE.MeshStandardMaterial>();\n private textures: THREE.Texture[] = [];\n // Slightly glossier than fully matte: with an environment present, roughness is what decides how\n // much tonal gradient a large surface picks up across its extent. At 0.85 a big floor plate shades\n // almost uniformly and reads as flat paper; easing it back lets the sky/ground gradient sweep across.\n solid(color: string, roughness = 0.72) {\n const key = `solid:${color}:${roughness}`;\n if (!this.materials.has(key)) {\n const material = new THREE.MeshStandardMaterial({ color, roughness, side: THREE.DoubleSide });\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** The active storey's own plate, when the stack has storeys under it. Opaque enough to read as\n * the floor you are standing on, sheer enough that the levels beneath show through it — without\n * this the plate is a lid and \"All floors\" shows one. depthWrite stays on: the plate must still\n * occlude its own walls' hidden faces, and the ghosts below carry depthWrite off already, so they\n * blend rather than fight it. */\n plate(color: string, roughness = 0.72) {\n const key = `plate:${color}:${roughness}`;\n if (!this.materials.has(key)) {\n const material = new THREE.MeshStandardMaterial({\n color,\n roughness,\n side: THREE.DoubleSide,\n transparent: true,\n opacity: 0.55,\n });\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** A lit ceiling. Its underside faces down, so every light in the scene misses it and a plain\n * material renders it as the darkest thing in the room — which is the opposite of what a ceiling\n * full of fittings looks like from beneath. Emissive is the honest fix: the ceiling is not lit,\n * it is the thing doing the lighting, and `tint` is whatever the floor's lamps are. */\n luminous(color: string, tint: string, strength: number, finish?: SurfaceFinish) {\n const key = `luminous:${color}:${tint}:${strength.toFixed(2)}:${finish ?? ''}`;\n if (!this.materials.has(key)) {\n // Built on the finish's own material when there is one, so the ceiling keeps its tile grid and\n // its fissured face and gains the emission on top — a lit ceiling is still a ceiling, and a\n // flat glowing plane overhead is the one surface a room cannot have.\n const material = finish\n ? this.get(finish, color).clone()\n : new THREE.MeshStandardMaterial({ color, roughness: 0.9, side: THREE.DoubleSide });\n material.emissive = new THREE.Color(tint);\n material.emissiveIntensity = strength;\n // The emission follows the finish's own map. Flat emission over a patterned surface washes the\n // pattern out — and on a ceiling it is also wrong: the light comes through the tiles, not\n // through the metal grid holding them up.\n material.emissiveMap = material.map;\n material.side = THREE.DoubleSide;\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** A polished twin of a finish, for the floor you are standing on in walk mode. Seen from above a\n * floor is a flat patch of its own colour and matte is right; seen along at eye level it is the\n * one surface in the room that shows you where the light is, because a grazing angle is where\n * reflectance lives. The roughness map still multiplies through, so the polish keeps the finish's\n * own variation instead of turning the plate into a mirror. */\n polished(base: THREE.MeshStandardMaterial, roughness = 0.5): THREE.MeshStandardMaterial {\n const key = `polished:${base.uuid}:${roughness}`;\n if (!this.materials.has(key)) {\n const material = base.clone();\n material.roughness = roughness;\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** A vertex-coloured twin of an existing material, so geometry can carry baked shading without\n * doubling the palette. Cached per source material: everything drawn with the twin must supply a\n * colour attribute, since a merge needs matching attributes across all its geometries. */\n shaded(base: THREE.MeshStandardMaterial): THREE.MeshStandardMaterial {\n const key = `shaded:${base.uuid}`;\n if (!this.materials.has(key)) {\n const material = base.clone();\n material.vertexColors = true;\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** Translucent variant for de-emphasised floors in the stacked view. */\n ghost(color: string, opacity = 0.17) {\n const key = `ghost:${color}:${opacity}`;\n if (!this.materials.has(key)) {\n // A ghost tinted with its own colour is invisible: interiors are pale, backgrounds are pale,\n // and pale-on-pale at low opacity measures 1.0:1 against the map — the building disappears.\n //\n // Darken rather than tint towards a fixed slate. Both make a ghost visible; only darkening\n // keeps the colour it started as, and a stacked view where every storey has been recoloured\n // the same grey cannot show you that the storeys differ. Multiplying preserves the hue and\n // chroma relationships the floor palette was built to carry.\n //\n // Opacity is shared out across the stack, because a ghost is never seen alone: seventeen\n // storeys layer seventeen of them with depthWrite off and nothing sorting back-to-front, so a\n // value that reads well on a house compounds into an opaque mass on a tower. The caller scales\n // it by how many levels are actually stacked.\n const tint = new THREE.Color(color).multiplyScalar(0.42);\n const material = new THREE.MeshStandardMaterial({\n color: tint,\n roughness: 0.9,\n side: THREE.DoubleSide,\n transparent: true,\n opacity,\n depthWrite: false,\n });\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n private finishes = new Map<SurfaceFinish, THREE.Texture[]>();\n get(kind: SurfaceFinish, color: string): THREE.MeshStandardMaterial {\n const key = `${kind}:${color}`;\n if (this.materials.has(key)) return this.materials.get(key)!;\n let maps = this.finishes.get(kind);\n if (!maps) {\n maps = surfaceTextures(kind);\n this.finishes.set(kind, maps);\n this.textures.push(...maps);\n }\n const [map, bumpMap, roughnessMap] = maps;\n const material = new THREE.MeshStandardMaterial({\n color,\n map,\n bumpMap,\n roughnessMap,\n bumpScale:\n kind === 'veneer'\n ? 0.0004\n : kind === 'brick'\n ? 0.018\n : kind === 'roof'\n ? 0.026\n : kind === 'grass'\n ? 0.025\n : kind === 'tile'\n ? 0.001\n : 0.006,\n roughness: 1,\n metalness: kind === 'roof' ? 0.48 : 0,\n side: THREE.DoubleSide,\n });\n material.userData.shared = true;\n material.userData.finish = kind;\n this.materials.set(key, material);\n return material;\n }\n glass(evening = false, lit = false) {\n const key = `glass:${evening}:${lit}`;\n if (!this.materials.has(key)) {\n const material = new THREE.MeshPhysicalMaterial({\n // Daylight glazing was a muted grey-green that went nearly black against a bright wall —\n // the colour of old float glass in a photograph, not of a window you are looking at. A pale\n // sky tint with less metal in it reads as glass with daylight behind it.\n color: evening ? '#5b6a75' : '#b9d2dc',\n roughness: 0.1,\n metalness: 0.12,\n clearcoat: 1,\n clearcoatRoughness: 0.05,\n envMapIntensity: 2.1,\n emissive: '#ffc780',\n emissiveIntensity: evening && lit ? 0.65 : 0,\n // Glass you can see through. It was opaque, so a window was a coloured panel in a wall and\n // a room stayed a sealed box however many openings it had; the light in the plan came only\n // from the missing ceiling. Kept well short of invisible — a pane has to read as a pane at\n // a distance, and a fully clear one leaves nothing but its frame.\n transparent: true,\n opacity: evening && lit ? 0.92 : 0.42,\n // Still writes depth. Without it a pane occludes nothing, which is invisible on a house and\n // catastrophic on a tower: a hundred glazed storeys all show through one another and the\n // building becomes a haze with no floor you can pick out.\n depthWrite: true,\n side: THREE.DoubleSide,\n });\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n metal(color = '#626c6d') {\n const key = `metal:${color}`;\n if (!this.materials.has(key)) {\n const material = new THREE.MeshStandardMaterial({ color, metalness: 0.72, roughness: 0.32 });\n material.userData.shared = true;\n this.materials.set(key, material);\n }\n return this.materials.get(key)!;\n }\n /** One image per step; the UVs follow the machine's run, including rotated/reversed flights. */\n escalator() {\n const key = 'escalator-tread';\n if (!this.materials.has(key)) {\n const canvas = document.createElement('canvas');\n canvas.width = canvas.height = 512;\n const ctx = canvas.getContext('2d')!;\n ctx.fillStyle = '#a4aaac';\n ctx.fillRect(0, 0, 512, 512);\n for (let x = 0; x < 512; x += 4) {\n ctx.fillStyle = '#30383c';\n ctx.fillRect(x, 0, 1, 512);\n ctx.fillStyle = '#d5dadb';\n ctx.fillRect(x + 1, 0, 1, 512);\n }\n ctx.fillStyle = '#dcb735';\n ctx.fillRect(0, 0, 512, 12);\n ctx.fillRect(0, 500, 512, 12);\n ctx.fillRect(0, 0, 8, 512);\n ctx.fillRect(504, 0, 8, 512);\n const map = new THREE.CanvasTexture(canvas);\n map.colorSpace = THREE.SRGBColorSpace;\n map.anisotropy = 8;\n const material = new THREE.MeshStandardMaterial({\n color: '#ffffff',\n map,\n bumpMap: map,\n bumpScale: 0.002,\n metalness: 0.65,\n roughness: 0.38,\n side: THREE.DoubleSide,\n });\n material.userData.shared = true;\n material.userData.finish = 'escalator-tread';\n this.materials.set(key, material);\n this.textures.push(map);\n }\n return this.materials.get(key)!;\n }\n dispose() {\n this.materials.forEach(m => m.dispose());\n this.textures.forEach(t => t.dispose());\n this.materials.clear();\n this.finishes.clear();\n this.textures = [];\n }\n}\n"],"names":["MaterialLibrary","__publicField","color","roughness","key","material","THREE","tint","strength","finish","base","opacity","kind","maps","surfaceTextures","map","bumpMap","roughnessMap","evening","lit","canvas","ctx","x","m","t"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function m(l, d) {
|
|
2
|
+
var c, f;
|
|
3
|
+
const g = l.objects.filter((t) => t.floorId === d), a = new Map(g.filter((t) => t.kind === "room" && t.material === "tile").map((t) => [t.id, t])), o = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const t of g.filter((n) => n.water && n.rings)) {
|
|
5
|
+
const n = a.get(t.parentId ?? "");
|
|
6
|
+
if (!n) continue;
|
|
7
|
+
const i = g.filter(
|
|
8
|
+
(e) => e.kind === "light" && e.light && (e.light.mountHeight ?? 0) < 0 && Math.abs(e.position[0] - t.position[0]) <= t.width / 2 && Math.abs(e.position[1] - t.position[1]) <= t.depth / 2
|
|
9
|
+
), h = i.reduce((e, r) => e + r.light.intensity, 0);
|
|
10
|
+
if (!h) continue;
|
|
11
|
+
const s = Math.min(1.6, h / 650);
|
|
12
|
+
(((c = o.get(n.id)) == null ? void 0 : c.strength) ?? 0) >= s || o.set(n.id, {
|
|
13
|
+
room: n,
|
|
14
|
+
pool: t,
|
|
15
|
+
strength: s,
|
|
16
|
+
height: i.reduce((e, r) => e + r.light.mountHeight, 0) / i.length
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const p = new Set(o.keys()), u = (l.portals ?? []).filter(
|
|
20
|
+
(t) => t.passage !== "none" && !t.openingId && a.has(t.a) && a.has(t.b)
|
|
21
|
+
);
|
|
22
|
+
for (let t = 0; t < a.size; t++) {
|
|
23
|
+
let n = !1;
|
|
24
|
+
for (const i of u)
|
|
25
|
+
for (const [h, s] of [
|
|
26
|
+
[i.a, i.b],
|
|
27
|
+
[i.b, i.a]
|
|
28
|
+
]) {
|
|
29
|
+
const e = o.get(h), r = ((e == null ? void 0 : e.strength) ?? 0) * 0.72;
|
|
30
|
+
!e || p.has(s) || r <= (((f = o.get(s)) == null ? void 0 : f.strength) ?? 0) + 1e-4 || (o.set(s, { room: a.get(s), strength: r, height: e.height }), n = !0);
|
|
31
|
+
}
|
|
32
|
+
if (!n) break;
|
|
33
|
+
}
|
|
34
|
+
return [...o.values()];
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
m as poolBounces
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=poolLighting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poolLighting.js","sources":["../../../../src/map/poolLighting.ts"],"sourcesContent":["import type { ProjectDocument, SiteObject } from '../model/types';\n\nexport interface PoolBounce {\n room: SiteObject;\n pool?: SiteObject;\n strength: number;\n height: number;\n}\n\n/** A diffuse approximation for white tiled rooms. Pool chambers receive the submerged fixtures;\n * connecting chambers receive a weaker bounce through open portals. No light crosses a sealed\n * wall, and switching off the source removes its reflected light as well. */\nexport function poolBounces(project: ProjectDocument, floorId: string): PoolBounce[] {\n const objects = project.objects.filter(o => o.floorId === floorId);\n const rooms = new Map(objects.filter(o => o.kind === 'room' && o.material === 'tile').map(o => [o.id, o]));\n const lit = new Map<string, PoolBounce>();\n for (const pool of objects.filter(o => o.water && o.rings)) {\n const room = rooms.get(pool.parentId ?? '');\n if (!room) continue;\n const lamps = objects.filter(\n o =>\n o.kind === 'light' &&\n o.light &&\n (o.light.mountHeight ?? 0) < 0 &&\n Math.abs(o.position[0] - pool.position[0]) <= pool.width / 2 &&\n Math.abs(o.position[1] - pool.position[1]) <= pool.depth / 2,\n );\n const intensity = lamps.reduce((sum, lamp) => sum + lamp.light!.intensity, 0);\n if (!intensity) continue;\n const strength = Math.min(1.6, intensity / 650);\n if ((lit.get(room.id)?.strength ?? 0) >= strength) continue;\n lit.set(room.id, {\n room,\n pool,\n strength,\n height: lamps.reduce((sum, lamp) => sum + lamp.light!.mountHeight!, 0) / lamps.length,\n });\n }\n const direct = new Set(lit.keys());\n const links = (project.portals ?? []).filter(\n p => p.passage !== 'none' && !p.openingId && rooms.has(p.a) && rooms.has(p.b),\n );\n // Max-product propagation avoids counting the same light repeatedly around a portal cycle.\n // White tile is highly reflective, but each doorway loses light: caustics stay in the pool room.\n for (let pass = 0; pass < rooms.size; pass++) {\n let changed = false;\n for (const link of links)\n for (const [a, b] of [\n [link.a, link.b],\n [link.b, link.a],\n ]) {\n const source = lit.get(a),\n strength = (source?.strength ?? 0) * 0.72;\n if (!source || direct.has(b) || strength <= (lit.get(b)?.strength ?? 0) + 0.0001) continue;\n lit.set(b, { room: rooms.get(b)!, strength, height: source.height });\n changed = true;\n }\n if (!changed) break;\n }\n return [...lit.values()];\n}\n"],"names":["poolBounces","project","floorId","_a","_b","objects","o","rooms","lit","pool","room","lamps","intensity","sum","lamp","strength","direct","links","p","pass","changed","link","a","b","source"],"mappings":"AAYO,SAASA,EAAYC,GAA0BC,GAA+B;AAA9E,MAAAC,GAAAC;AACL,QAAMC,IAAUJ,EAAQ,QAAQ,OAAO,CAAAK,MAAKA,EAAE,YAAYJ,CAAO,GAC3DK,IAAQ,IAAI,IAAIF,EAAQ,OAAO,CAAAC,MAAKA,EAAE,SAAS,UAAUA,EAAE,aAAa,MAAM,EAAE,IAAI,CAAAA,MAAK,CAACA,EAAE,IAAIA,CAAC,CAAC,CAAC,GACnGE,wBAAU,IAAA;AAChB,aAAWC,KAAQJ,EAAQ,OAAO,CAAAC,MAAKA,EAAE,SAASA,EAAE,KAAK,GAAG;AAC1D,UAAMI,IAAOH,EAAM,IAAIE,EAAK,YAAY,EAAE;AAC1C,QAAI,CAACC,EAAM;AACX,UAAMC,IAAQN,EAAQ;AAAA,MACpB,OACEC,EAAE,SAAS,WACXA,EAAE,UACDA,EAAE,MAAM,eAAe,KAAK,KAC7B,KAAK,IAAIA,EAAE,SAAS,CAAC,IAAIG,EAAK,SAAS,CAAC,CAAC,KAAKA,EAAK,QAAQ,KAC3D,KAAK,IAAIH,EAAE,SAAS,CAAC,IAAIG,EAAK,SAAS,CAAC,CAAC,KAAKA,EAAK,QAAQ;AAAA,IAAA,GAEzDG,IAAYD,EAAM,OAAO,CAACE,GAAKC,MAASD,IAAMC,EAAK,MAAO,WAAW,CAAC;AAC5E,QAAI,CAACF,EAAW;AAChB,UAAMG,IAAW,KAAK,IAAI,KAAKH,IAAY,GAAG;AAC9C,OAAKT,IAAAK,EAAI,IAAIE,EAAK,EAAE,MAAf,gBAAAP,EAAkB,aAAY,MAAMY,KACzCP,EAAI,IAAIE,EAAK,IAAI;AAAA,MACf,MAAAA;AAAA,MACA,MAAAD;AAAA,MACA,UAAAM;AAAA,MACA,QAAQJ,EAAM,OAAO,CAACE,GAAKC,MAASD,IAAMC,EAAK,MAAO,aAAc,CAAC,IAAIH,EAAM;AAAA,IAAA,CAChF;AAAA,EACH;AACA,QAAMK,IAAS,IAAI,IAAIR,EAAI,MAAM,GAC3BS,KAAShB,EAAQ,WAAW,CAAA,GAAI;AAAA,IACpC,CAAAiB,MAAKA,EAAE,YAAY,UAAU,CAACA,EAAE,aAAaX,EAAM,IAAIW,EAAE,CAAC,KAAKX,EAAM,IAAIW,EAAE,CAAC;AAAA,EAAA;AAI9E,WAASC,IAAO,GAAGA,IAAOZ,EAAM,MAAMY,KAAQ;AAC5C,QAAIC,IAAU;AACd,eAAWC,KAAQJ;AACjB,iBAAW,CAACK,GAAGC,CAAC,KAAK;AAAA,QACnB,CAACF,EAAK,GAAGA,EAAK,CAAC;AAAA,QACf,CAACA,EAAK,GAAGA,EAAK,CAAC;AAAA,MAAA,GACd;AACD,cAAMG,IAAShB,EAAI,IAAIc,CAAC,GACtBP,MAAYS,KAAA,gBAAAA,EAAQ,aAAY,KAAK;AACvC,QAAI,CAACA,KAAUR,EAAO,IAAIO,CAAC,KAAKR,QAAaX,IAAAI,EAAI,IAAIe,CAAC,MAAT,gBAAAnB,EAAY,aAAY,KAAK,SAC1EI,EAAI,IAAIe,GAAG,EAAE,MAAMhB,EAAM,IAAIgB,CAAC,GAAI,UAAAR,GAAU,QAAQS,EAAO,OAAA,CAAQ,GACnEJ,IAAU;AAAA,MACZ;AACF,QAAI,CAACA,EAAS;AAAA,EAChB;AACA,SAAO,CAAC,GAAGZ,EAAI,QAAQ;AACzB;"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function y(c, o, n) {
|
|
2
|
+
const t = n.elements;
|
|
3
|
+
Math.abs(t[11]) < 1e-12 || (c.position.set(t[8] / t[11], t[9] / t[11], t[10] / t[11]), c.updateMatrixWorld(!0), c.projectionMatrix.copy(o).multiply(c.matrixWorld), c.projectionMatrixInverse.copy(c.projectionMatrix).invert());
|
|
4
|
+
}
|
|
5
|
+
function A(c, o, n, t, i) {
|
|
6
|
+
if (o.isEmpty() || !(t > 0 && i > t)) return;
|
|
7
|
+
const e = c.elements, { min: l, max: x } = o, s = (e[15] + Math.max(e[3] * l.x, e[3] * x.x) + Math.max(e[7] * l.y, e[7] * x.y) + Math.max(e[11] * l.z * n, e[11] * x.z * n)) * 1.01;
|
|
8
|
+
if (!Number.isFinite(s) || s <= i) return;
|
|
9
|
+
const d = -(i + t) / (i - t), h = -(s + t) / (s - t), M = s / (s - t) / (i / (i - t)), m = M * d - h;
|
|
10
|
+
for (let p = 0; p < 16; p += 4) e[p + 2] = M * e[p + 2] + m * e[p + 3];
|
|
11
|
+
}
|
|
12
|
+
function j(c, o, n) {
|
|
13
|
+
const t = c.elements, i = Math.min(o, 0.04 * Math.hypot(t[3], t[7], t[11]));
|
|
14
|
+
if (!(i > 0 && o > 0 && n > o)) return o;
|
|
15
|
+
const e = -(n + o) / (n - o), l = -(n + i) / (n - i), x = i / (n - i) / (o / (n - o)), u = x * e - l;
|
|
16
|
+
for (let s = 0; s < 16; s += 4) t[s + 2] = x * t[s + 2] + u * t[s + 3];
|
|
17
|
+
return i;
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
A as includeSceneDepth,
|
|
21
|
+
y as syncLightingCamera,
|
|
22
|
+
j as walkNearPlane
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.js","sources":["../../../../src/map/projection.ts"],"sourcesContent":["import type { Box3, Camera, Matrix4 } from 'three';\n\n/** Split the map's combined matrix so PBR shading sees the same eye as projection and picking.\n * Keeping the camera axes in world space avoids decomposing Mercator's reflected Y axis. */\nexport function syncLightingCamera(camera: Camera, worldToClip: Matrix4, clipToWorld: Matrix4) {\n const e = clipToWorld.elements;\n if (Math.abs(e[11]) < 1e-12) return;\n camera.position.set(e[8] / e[11], e[9] / e[11], e[10] / e[11]);\n camera.updateMatrixWorld(true);\n camera.projectionMatrix.copy(worldToClip).multiply(camera.matrixWorld);\n camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();\n}\n\n/** Extend a Mercator scene's far plane without changing its screen position or near plane. */\nexport function includeSceneDepth(matrix: Matrix4, bounds: Box3, scaleZ: number, near: number, far: number) {\n if (bounds.isEmpty() || !(near > 0 && far > near)) return;\n const e = matrix.elements,\n { min, max } = bounds;\n // Perspective clip W is camera distance, in the same units as MapLibre's nearZ/farZ.\n // Maximise its linear expression over the cached box; no vertex scan while panning.\n const distance =\n e[15] +\n Math.max(e[3] * min.x, e[3] * max.x) +\n Math.max(e[7] * min.y, e[7] * max.y) +\n Math.max(e[11] * min.z * scaleZ, e[11] * max.z * scaleZ);\n const extendedFar = distance * 1.01;\n if (!Number.isFinite(extendedFar) || extendedFar <= far) return;\n\n // Z = A * viewZ + B, W = -viewZ. Replace only the depth row with k*Z + t*W.\n // Keeping X/Y/W untouched preserves the precise basemap alignment. A finite far plane\n // also keeps inverse-projected picking rays valid at NDC z=1.\n const oldA = -(far + near) / (far - near);\n const newA = -(extendedFar + near) / (extendedFar - near);\n const k = extendedFar / (extendedFar - near) / (far / (far - near));\n const t = k * oldA - newA;\n for (let i = 0; i < 16; i += 4) e[i + 2] = k * e[i + 2] + t * e[i + 3];\n}\n\n/** MapLibre's near distance follows viewport pixels and can cut away a wall before the walker\n * touches it. Give POV a 4 cm near plane in scene metres, preserving the eye, X/Y and far plane. */\nexport function walkNearPlane(matrix: Matrix4, near: number, far: number): number {\n const e = matrix.elements;\n const next = Math.min(near, 0.04 * Math.hypot(e[3], e[7], e[11]));\n if (!(next > 0 && near > 0 && far > near)) return near;\n const oldA = -(far + near) / (far - near),\n newA = -(far + next) / (far - next);\n const k = next / (far - next) / (near / (far - near));\n const t = k * oldA - newA;\n for (let i = 0; i < 16; i += 4) e[i + 2] = k * e[i + 2] + t * e[i + 3];\n return next;\n}\n"],"names":["syncLightingCamera","camera","worldToClip","clipToWorld","e","includeSceneDepth","matrix","bounds","scaleZ","near","far","min","max","extendedFar","oldA","newA","k","t","i","walkNearPlane","next"],"mappings":"AAIO,SAASA,EAAmBC,GAAgBC,GAAsBC,GAAsB;AAC7F,QAAMC,IAAID,EAAY;AACtB,EAAI,KAAK,IAAIC,EAAE,EAAE,CAAC,IAAI,UACtBH,EAAO,SAAS,IAAIG,EAAE,CAAC,IAAIA,EAAE,EAAE,GAAGA,EAAE,CAAC,IAAIA,EAAE,EAAE,GAAGA,EAAE,EAAE,IAAIA,EAAE,EAAE,CAAC,GAC7DH,EAAO,kBAAkB,EAAI,GAC7BA,EAAO,iBAAiB,KAAKC,CAAW,EAAE,SAASD,EAAO,WAAW,GACrEA,EAAO,wBAAwB,KAAKA,EAAO,gBAAgB,EAAE,OAAA;AAC/D;AAGO,SAASI,EAAkBC,GAAiBC,GAAcC,GAAgBC,GAAcC,GAAa;AAC1G,MAAIH,EAAO,aAAa,EAAEE,IAAO,KAAKC,IAAMD,GAAO;AACnD,QAAM,IAAIH,EAAO,UACf,EAAE,KAAAK,GAAK,KAAAC,MAAQL,GAQXM,KAJJ,EAAE,EAAE,IACJ,KAAK,IAAI,EAAE,CAAC,IAAIF,EAAI,GAAG,EAAE,CAAC,IAAIC,EAAI,CAAC,IACnC,KAAK,IAAI,EAAE,CAAC,IAAID,EAAI,GAAG,EAAE,CAAC,IAAIC,EAAI,CAAC,IACnC,KAAK,IAAI,EAAE,EAAE,IAAID,EAAI,IAAIH,GAAQ,EAAE,EAAE,IAAII,EAAI,IAAIJ,CAAM,KAC1B;AAC/B,MAAI,CAAC,OAAO,SAASK,CAAW,KAAKA,KAAeH,EAAK;AAKzD,QAAMI,IAAO,EAAEJ,IAAMD,MAASC,IAAMD,IAC9BM,IAAO,EAAEF,IAAcJ,MAASI,IAAcJ,IAC9CO,IAAIH,KAAeA,IAAcJ,MAASC,KAAOA,IAAMD,KACvDQ,IAAID,IAAIF,IAAOC;AACrB,WAASG,IAAI,GAAGA,IAAI,IAAIA,KAAK,KAAKA,IAAI,CAAC,IAAIF,IAAI,EAAEE,IAAI,CAAC,IAAID,IAAI,EAAEC,IAAI,CAAC;AACvE;AAIO,SAASC,EAAcb,GAAiBG,GAAcC,GAAqB;AAChF,QAAMN,IAAIE,EAAO,UACXc,IAAO,KAAK,IAAIX,GAAM,OAAO,KAAK,MAAML,EAAE,CAAC,GAAGA,EAAE,CAAC,GAAGA,EAAE,EAAE,CAAC,CAAC;AAChE,MAAI,EAAEgB,IAAO,KAAKX,IAAO,KAAKC,IAAMD,GAAO,QAAOA;AAClD,QAAMK,IAAO,EAAEJ,IAAMD,MAASC,IAAMD,IAClCM,IAAO,EAAEL,IAAMU,MAASV,IAAMU,IAC1BJ,IAAII,KAAQV,IAAMU,MAASX,KAAQC,IAAMD,KACzCQ,IAAID,IAAIF,IAAOC;AACrB,WAASG,IAAI,GAAGA,IAAI,IAAIA,KAAK,KAAKA,IAAI,CAAC,IAAIF,IAAIZ,EAAEc,IAAI,CAAC,IAAID,IAAIb,EAAEc,IAAI,CAAC;AACrE,SAAOE;AACT;"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import y from "polygon-clipping";
|
|
2
|
+
import { closeRing as u } from "../model/geometry.js";
|
|
3
|
+
import { rampFloors as d } from "../model/ramps.js";
|
|
4
|
+
function M(i, e, f, a) {
|
|
5
|
+
const [t, o] = e.axis, n = Math.hypot(o[0] - t[0], o[1] - t[1]);
|
|
6
|
+
if (n < 1e-8 || !i.length) return [];
|
|
7
|
+
const s = (o[0] - t[0]) / n, m = (o[1] - t[1]) / n, r = Math.max(n, ...i.flat().map((l) => Math.hypot(l[0] - t[0], l[1] - t[1]))) + 1, p = Math.max(-r, f * n), h = Math.min(r, a * n);
|
|
8
|
+
if (h <= p) return [];
|
|
9
|
+
const c = (l, g) => [t[0] + s * l - m * g, t[1] + m * l + s * g];
|
|
10
|
+
return y.intersection(i.map(u), [
|
|
11
|
+
u([c(p, -r), c(h, -r), c(h, r), c(p, r)])
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
const B = (i, e) => [
|
|
15
|
+
[-1 / 0, 0],
|
|
16
|
+
[0, 1],
|
|
17
|
+
[1, 1 / 0]
|
|
18
|
+
].flatMap(([f, a]) => M(i, e, f, a));
|
|
19
|
+
function C(i, e, f = "floor") {
|
|
20
|
+
const a = i.floors.find((t) => t.id === e);
|
|
21
|
+
return a ? i.objects.flatMap((t) => {
|
|
22
|
+
var r;
|
|
23
|
+
if (!t.slope || !((r = t.rings) != null && r.length)) return [];
|
|
24
|
+
const { low: o, high: n } = d(i, t);
|
|
25
|
+
if (f === "floor") return (n == null ? void 0 : n.id) === e && t.floorId === e ? [u(t.rings[0])] : [];
|
|
26
|
+
if ((o == null ? void 0 : o.id) !== e) return [];
|
|
27
|
+
const s = t.slope.high - t.slope.low;
|
|
28
|
+
if (s <= 0) return [];
|
|
29
|
+
const m = (s - a.height + 2.4) / s;
|
|
30
|
+
return M(t.rings, t.slope, -1 / 0, Math.min(1, m)).map((p) => p[0]);
|
|
31
|
+
}) : [];
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
M as rampBand,
|
|
35
|
+
B as rampSections,
|
|
36
|
+
C as rampVoids
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=rampGeometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rampGeometry.js","sources":["../../../../src/map/rampGeometry.ts"],"sourcesContent":["import polygonClipping from 'polygon-clipping';\nimport { closeRing } from '../model/geometry';\nimport { rampFloors } from '../model/ramps';\nimport type { Point, ProjectDocument, Ring, Slope } from '../model/types';\n\n/** Clip in the slope's local frame. Clipping, rather than moving existing corner vertices, inserts\n * the break lines where an incline meets its flat aprons, including concave outlines and holes. */\nexport function rampBand(rings: Ring[], slope: Slope, from: number, to: number): Ring[][] {\n const [a, b] = slope.axis;\n const length = Math.hypot(b[0] - a[0], b[1] - a[1]);\n if (length < 1e-8 || !rings.length) return [];\n const ux = (b[0] - a[0]) / length,\n uy = (b[1] - a[1]) / length;\n const reach = Math.max(length, ...rings.flat().map(p => Math.hypot(p[0] - a[0], p[1] - a[1]))) + 1;\n const lo = Math.max(-reach, from * length),\n hi = Math.min(reach, to * length);\n if (hi <= lo) return [];\n const at = (x: number, y: number): Point => [a[0] + ux * x - uy * y, a[1] + uy * x + ux * y];\n return polygonClipping.intersection(rings.map(closeRing), [\n closeRing([at(lo, -reach), at(hi, -reach), at(hi, reach), at(lo, reach)]),\n ]) as Ring[][];\n}\n\nexport const rampSections = (rings: Ring[], slope: Slope) =>\n [\n [-Infinity, 0],\n [0, 1],\n [1, Infinity],\n ].flatMap(([a, b]) => rampBand(rings, slope, a, b));\n\n/** A descending ramp replaces the upper floor. The ceiling below also needs an opening wherever\n * the ramp comes within head height. Arrival aprons stay supported by their lower deck. */\nexport function rampVoids(project: ProjectDocument, floorId: string, through: 'floor' | 'ceiling' = 'floor'): Ring[] {\n const level = project.floors.find(f => f.id === floorId);\n if (!level) return [];\n return project.objects.flatMap(o => {\n if (!o.slope || !o.rings?.length) return [];\n const { low, high } = rampFloors(project, o);\n if (through === 'floor') return high?.id === floorId && o.floorId === floorId ? [closeRing(o.rings[0])] : [];\n if (low?.id !== floorId) return [];\n const rise = o.slope.high - o.slope.low;\n if (rise <= 0) return [];\n // Include slab thickness and finish offsets in addition to standing headroom.\n const end = (rise - level.height + 2.4) / rise;\n return rampBand(o.rings, o.slope, -Infinity, Math.min(1, end)).map(pg => pg[0]);\n });\n}\n"],"names":["rampBand","rings","slope","from","to","a","b","length","ux","uy","reach","p","lo","hi","at","x","y","polygonClipping","closeRing","rampSections","rampVoids","project","floorId","through","level","f","o","_a","low","high","rampFloors","rise","end","pg"],"mappings":";;;AAOO,SAASA,EAASC,GAAeC,GAAcC,GAAcC,GAAsB;AACxF,QAAM,CAACC,GAAGC,CAAC,IAAIJ,EAAM,MACfK,IAAS,KAAK,MAAMD,EAAE,CAAC,IAAID,EAAE,CAAC,GAAGC,EAAE,CAAC,IAAID,EAAE,CAAC,CAAC;AAClD,MAAIE,IAAS,QAAQ,CAACN,EAAM,eAAe,CAAA;AAC3C,QAAMO,KAAMF,EAAE,CAAC,IAAID,EAAE,CAAC,KAAKE,GACzBE,KAAMH,EAAE,CAAC,IAAID,EAAE,CAAC,KAAKE,GACjBG,IAAQ,KAAK,IAAIH,GAAQ,GAAGN,EAAM,OAAO,IAAI,CAAAU,MAAK,KAAK,MAAMA,EAAE,CAAC,IAAIN,EAAE,CAAC,GAAGM,EAAE,CAAC,IAAIN,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAC3FO,IAAK,KAAK,IAAI,CAACF,GAAOP,IAAOI,CAAM,GACvCM,IAAK,KAAK,IAAIH,GAAON,IAAKG,CAAM;AAClC,MAAIM,KAAMD,EAAI,QAAO,CAAA;AACrB,QAAME,IAAK,CAACC,GAAWC,MAAqB,CAACX,EAAE,CAAC,IAAIG,IAAKO,IAAIN,IAAKO,GAAGX,EAAE,CAAC,IAAII,IAAKM,IAAIP,IAAKQ,CAAC;AAC3F,SAAOC,EAAgB,aAAahB,EAAM,IAAIiB,CAAS,GAAG;AAAA,IACxDA,EAAU,CAACJ,EAAGF,GAAI,CAACF,CAAK,GAAGI,EAAGD,GAAI,CAACH,CAAK,GAAGI,EAAGD,GAAIH,CAAK,GAAGI,EAAGF,GAAIF,CAAK,CAAC,CAAC;AAAA,EAAA,CACzE;AACH;AAEO,MAAMS,IAAe,CAAClB,GAAeC,MAC1C;AAAA,EACE,CAAC,QAAW,CAAC;AAAA,EACb,CAAC,GAAG,CAAC;AAAA,EACL,CAAC,GAAG,KAAQ;AACd,EAAE,QAAQ,CAAC,CAACG,GAAGC,CAAC,MAAMN,EAASC,GAAOC,GAAOG,GAAGC,CAAC,CAAC;AAI7C,SAASc,EAAUC,GAA0BC,GAAiBC,IAA+B,SAAiB;AACnH,QAAMC,IAAQH,EAAQ,OAAO,KAAK,CAAAI,MAAKA,EAAE,OAAOH,CAAO;AACvD,SAAKE,IACEH,EAAQ,QAAQ,QAAQ,CAAAK,MAAK;;AAClC,QAAI,CAACA,EAAE,SAAS,GAACC,IAAAD,EAAE,UAAF,QAAAC,EAAS,gBAAe,CAAA;AACzC,UAAM,EAAE,KAAAC,GAAK,MAAAC,EAAA,IAASC,EAAWT,GAASK,CAAC;AAC3C,QAAIH,MAAY,QAAS,SAAOM,KAAA,gBAAAA,EAAM,QAAOP,KAAWI,EAAE,YAAYJ,IAAU,CAACJ,EAAUQ,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1G,SAAIE,KAAA,gBAAAA,EAAK,QAAON,EAAS,QAAO,CAAA;AAChC,UAAMS,IAAOL,EAAE,MAAM,OAAOA,EAAE,MAAM;AACpC,QAAIK,KAAQ,EAAG,QAAO,CAAA;AAEtB,UAAMC,KAAOD,IAAOP,EAAM,SAAS,OAAOO;AAC1C,WAAO/B,EAAS0B,EAAE,OAAOA,EAAE,OAAO,QAAW,KAAK,IAAI,GAAGM,CAAG,CAAC,EAAE,IAAI,CAAAC,MAAMA,EAAG,CAAC,CAAC;AAAA,EAChF,CAAC,IAXkB,CAAA;AAYrB;"}
|