@kerros/editor 0.0.0-stage → 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -2
- package/dist/SitePlanner.js +1189 -0
- package/dist/SitePlanner.js.map +1 -0
- 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 +11 -0
- package/dist/adapters/status.js.map +1 -0
- package/dist/adapters/useProjectPersistence.js +56 -0
- package/dist/adapters/useProjectPersistence.js.map +1 -0
- package/dist/components/AiImportPanel.js +635 -0
- package/dist/components/AiImportPanel.js.map +1 -0
- package/dist/components/AnalysisPreview.js +179 -0
- package/dist/components/AnalysisPreview.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/ImportDialog.js +467 -0
- package/dist/components/ImportDialog.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 +209 -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 +175 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n.js +88 -0
- package/dist/i18n.js.map +1 -0
- package/dist/import/analysisPreview.js +41 -0
- package/dist/import/analysisPreview.js.map +1 -0
- package/dist/import/brief.js +29 -0
- package/dist/import/brief.js.map +1 -0
- package/dist/import/checkpoint.js +50 -0
- package/dist/import/checkpoint.js.map +1 -0
- package/dist/import/detect.js +120 -0
- package/dist/import/detect.js.map +1 -0
- package/dist/import/documentSvg.js +49 -0
- package/dist/import/documentSvg.js.map +1 -0
- package/dist/import/instructions.js +20 -0
- package/dist/import/instructions.js.map +1 -0
- package/dist/import/openings.js +61 -0
- package/dist/import/openings.js.map +1 -0
- package/dist/import/planImport.js +233 -0
- package/dist/import/planImport.js.map +1 -0
- package/dist/import/register.js +47 -0
- package/dist/import/register.js.map +1 -0
- package/dist/import/runs.js +67 -0
- package/dist/import/runs.js.map +1 -0
- package/dist/import/session.js +23 -0
- package/dist/import/session.js.map +1 -0
- package/dist/import/types.js +13 -0
- package/dist/import/types.js.map +1 -0
- package/dist/import/usage.js +29 -0
- package/dist/import/usage.js.map +1 -0
- package/dist/import/walls.js +112 -0
- package/dist/import/walls.js.map +1 -0
- package/dist/index.js +178 -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 +143 -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 +99 -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 +454 -0
- package/dist/model/geometry.js.map +1 -0
- package/dist/model/history.js +29 -0
- package/dist/model/history.js.map +1 -0
- package/dist/model/imports.js +45 -0
- package/dist/model/imports.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 +31 -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 +261 -0
- package/dist/model/walls.js.map +1 -0
- package/dist/planner/DrawingDock.js +231 -0
- package/dist/planner/DrawingDock.js.map +1 -0
- package/dist/planner/FloorControls.js +93 -0
- package/dist/planner/FloorControls.js.map +1 -0
- package/dist/planner/MapSettings.js +143 -0
- package/dist/planner/MapSettings.js.map +1 -0
- package/dist/planner/PlannerHelp.js +57 -0
- package/dist/planner/PlannerHelp.js.map +1 -0
- package/dist/planner/ProjectSidebar.js +246 -0
- package/dist/planner/ProjectSidebar.js.map +1 -0
- package/dist/planner/floors.js +18 -0
- package/dist/planner/floors.js.map +1 -0
- package/dist/planner/useDrawingTools.js +341 -0
- package/dist/planner/useDrawingTools.js.map +1 -0
- package/dist/planner/useMapDisplay.js +51 -0
- package/dist/planner/useMapDisplay.js.map +1 -0
- package/dist/planner/usePlannerKeyboard.js +138 -0
- package/dist/planner/usePlannerKeyboard.js.map +1 -0
- package/dist/planner/useReferenceAlignment.js +50 -0
- package/dist/planner/useReferenceAlignment.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/SitePlanner.d.ts +5 -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/adapters/useProjectPersistence.d.ts +13 -0
- package/dist/types/components/AiImportPanel.d.ts +9 -0
- package/dist/types/components/AnalysisPreview.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/ImportDialog.d.ts +42 -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/editor/host.d.ts +9 -0
- package/dist/types/editor/index.d.ts +2 -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/detect.d.ts +34 -0
- package/dist/types/import/documentSvg.d.ts +2 -0
- package/dist/types/import/index.d.ts +10 -0
- package/dist/types/import/instructions.d.ts +7 -0
- package/dist/types/import/openings.d.ts +22 -0
- package/dist/types/import/planImport.d.ts +22 -0
- package/dist/types/import/register.d.ts +25 -0
- package/dist/types/import/runs.d.ts +31 -0
- package/dist/types/import/session.d.ts +33 -0
- package/dist/types/import/types.d.ts +56 -0
- package/dist/types/import/usage.d.ts +10 -0
- package/dist/types/import/walls.d.ts +37 -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/history.d.ts +10 -0
- package/dist/types/model/host.d.ts +154 -0
- package/dist/types/model/imports.d.ts +7 -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/planner/DrawingDock.d.ts +16 -0
- package/dist/types/planner/FloorControls.d.ts +17 -0
- package/dist/types/planner/MapSettings.d.ts +16 -0
- package/dist/types/planner/PlannerHelp.d.ts +3 -0
- package/dist/types/planner/ProjectSidebar.d.ts +32 -0
- package/dist/types/planner/floors.d.ts +3 -0
- package/dist/types/planner/types.d.ts +3 -0
- package/dist/types/planner/useDrawingTools.d.ts +52 -0
- package/dist/types/planner/useMapDisplay.d.ts +29 -0
- package/dist/types/planner/usePlannerKeyboard.d.ts +18 -0
- package/dist/types/planner/useReferenceAlignment.d.ts +30 -0
- package/dist/types/schema/index.d.ts +25 -0
- package/dist/types/theme/index.d.ts +55 -0
- package/package.json +58 -4
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { footprint as q, openRing as D, distance as M, segmentProjection as T, pointInRing as H } from "./geometry.js";
|
|
2
|
+
import { createRouteClearance as J, routeBounds as K, ROUTE_CLEARANCE as O } from "./routeClearance.js";
|
|
3
|
+
const B = "@room-nav:", P = (e) => Math.sign(
|
|
4
|
+
e.reduce((u, b, f) => {
|
|
5
|
+
const g = e[(f + 1) % e.length];
|
|
6
|
+
return u + b[0] * g[1] - g[0] * b[1];
|
|
7
|
+
}, 0)
|
|
8
|
+
) || 1;
|
|
9
|
+
function X(e, u, b = { nodes: [], edges: [] }) {
|
|
10
|
+
var m;
|
|
11
|
+
const f = e.objects.filter((o) => {
|
|
12
|
+
var l;
|
|
13
|
+
return o.kind === "room" && ((l = q(o)[0]) == null ? void 0 : l.length);
|
|
14
|
+
}), g = b.nodes.filter((o) => !o.id.startsWith(B)).map((o) => ({ ...o, position: [...o.position] })), $ = b.edges.filter((o) => !o.id.startsWith(B)), v = new Map(g.map((o) => [o.id, o])), y = new Map(f.map((o) => [o.id, []])), a = new Map(f.map((o) => [o.id, o.floorId]));
|
|
15
|
+
for (const o of g)
|
|
16
|
+
o.objectId && a.get(o.objectId) === o.floorId && ((m = y.get(o.objectId)) == null || m.push(o));
|
|
17
|
+
for (const { spaceId: o, nodeId: l } of u) {
|
|
18
|
+
const s = v.get(l), d = y.get(o);
|
|
19
|
+
s && d && a.get(o) === s.floorId && !d.includes(s) && d.push(s);
|
|
20
|
+
}
|
|
21
|
+
const c = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const o of e.objects)
|
|
23
|
+
if (o.water && o.parentId) {
|
|
24
|
+
const l = c.get(o.parentId) ?? [];
|
|
25
|
+
l.push(...q(o).slice(0, 1)), c.set(o.parentId, l);
|
|
26
|
+
}
|
|
27
|
+
const r = J(e);
|
|
28
|
+
for (const o of f) {
|
|
29
|
+
const l = `${B}${encodeURIComponent(o.id)}:`, s = y.get(o.id), [d, ...C] = q(o), _ = r(o.floorId, K(d)), k = (t, n) => Q(t, n, d) && _(t, n), z = [...C, ...c.get(o.id) ?? []], U = (t) => {
|
|
30
|
+
if (!k(t, t) || s.some((i) => M(i.position, t) < 0.01)) return;
|
|
31
|
+
const n = { id: `${l}point:${s.length}`, floorId: o.floorId, position: t };
|
|
32
|
+
g.push(n), s.push(n);
|
|
33
|
+
}, G = (t, n) => {
|
|
34
|
+
const i = D(t), h = P(t), j = (I, p) => {
|
|
35
|
+
const w = M(I, p) || 1;
|
|
36
|
+
return [h * (I[1] - p[1]) / w, h * (p[0] - I[0]) / w];
|
|
37
|
+
};
|
|
38
|
+
for (let I = 0; I < i.length; I++) {
|
|
39
|
+
const p = i[I], w = i[(I + i.length - 1) % i.length], x = i[(I + 1) % i.length], N = j(w, p), E = j(p, x), W = 1 + N[0] * E[0] + N[1] * E[1], F = h * ((p[0] - w[0]) * (x[1] - p[1]) - (p[1] - w[1]) * (x[0] - p[0]));
|
|
40
|
+
n > 0 && F >= 0 || (W > 0.05 && U([p[0] + n * (N[0] + E[0]) / W, p[1] + n * (N[1] + E[1]) / W]), n < 0 && U([(p[0] + x[0]) / 2 + n * E[0], (p[1] + x[1]) / 2 + n * E[1]]));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
G(d, O + 0.2);
|
|
44
|
+
for (const t of z) G(t, -O - 0.1);
|
|
45
|
+
const A = D(d), L = P(d);
|
|
46
|
+
for (const t of [...s])
|
|
47
|
+
for (let n = 0; n < A.length; n++) {
|
|
48
|
+
const i = A[n], h = A[(n + 1) % A.length], j = M(i, h);
|
|
49
|
+
j && T(t.position, i, h).distance < 0.01 && U([
|
|
50
|
+
t.position[0] + L * (i[1] - h[1]) / j * 0.9,
|
|
51
|
+
t.position[1] + L * (h[0] - i[0]) / j * 0.9
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
let R = s.find((t) => t.objectId === o.id);
|
|
55
|
+
if (R || (R = { id: `${l}anchor`, floorId: o.floorId, position: [...o.position], objectId: o.id }, g.push(R), s.push(R)), !k(R.position, R.position)) {
|
|
56
|
+
const t = s.filter((n) => k(n.position, n.position)).sort((n, i) => M(n.position, o.position) - M(i.position, o.position))[0];
|
|
57
|
+
t && (R.position = [...t.position]);
|
|
58
|
+
}
|
|
59
|
+
for (let t = 0; t < s.length; t++)
|
|
60
|
+
for (let n = t + 1; n < s.length; n++) {
|
|
61
|
+
const i = s[t], h = s[n];
|
|
62
|
+
k(i.position, h.position) && $.push({ id: `${l}edge:${t}:${n}`, kind: "walk", aId: i.id, bId: h.id });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { nodes: g, edges: $ };
|
|
66
|
+
}
|
|
67
|
+
function Q(e, u, b) {
|
|
68
|
+
const f = D(b), g = (c) => H(c, f) || f.some((r, m) => T(c, r, f[(m + 1) % f.length]).distance < 1e-7);
|
|
69
|
+
if (!g(e) || !g(u)) return !1;
|
|
70
|
+
const $ = u[0] - e[0], v = u[1] - e[1];
|
|
71
|
+
if (M(e, u) < 1e-8) return !0;
|
|
72
|
+
const a = [0, 1];
|
|
73
|
+
for (let c = 0; c < f.length; c++) {
|
|
74
|
+
const r = f[c], m = f[(c + 1) % f.length], o = m[0] - r[0], l = m[1] - r[1], s = $ * l - v * o;
|
|
75
|
+
if (Math.abs(s) < 1e-9)
|
|
76
|
+
for (const d of [r, m]) T(d, e, u).distance < 1e-7 && a.push(T(d, e, u).t);
|
|
77
|
+
else {
|
|
78
|
+
const d = ((r[0] - e[0]) * l - (r[1] - e[1]) * o) / s, C = ((r[0] - e[0]) * v - (r[1] - e[1]) * $) / s;
|
|
79
|
+
d > 0 && d < 1 && C >= 0 && C <= 1 && a.push(d);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return a.sort((c, r) => c - r), a.slice(1).every((c, r) => g([e[0] + $ * (c + a[r]) / 2, e[1] + v * (c + a[r]) / 2]));
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
X as buildRoomNavigation
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=roomNavigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roomNavigation.js","sources":["../../../../src/model/roomNavigation.ts"],"sourcesContent":["import { distance, footprint, openRing, pointInRing, segmentProjection } from './geometry';\nimport { createRouteClearance, routeBounds, ROUTE_CLEARANCE } from './routeClearance';\nimport type { NavEdge, NavNode, Point, ProjectDocument, Ring } from './types';\n\n/** Bind a graph node to the room whose walkable floor it can enter. Use separate nodes on the\n * sides of a controlled portal and join those with a directed/door edge in `graph`. */\nexport interface RoomAccess {\n spaceId: string;\n nodeId: string;\n}\nexport interface RoomNavigationGraph {\n nodes: NavNode[];\n edges: NavEdge[];\n}\nconst GENERATED = '@room-nav:';\nconst winding = (ring: Ring) =>\n Math.sign(\n ring.reduce((sum, p, i) => {\n const q = ring[(i + 1) % ring.length];\n return sum + p[0] * q[1] - q[0] * p[1];\n }, 0),\n ) || 1;\n\n/** Build the geometric walking graph inside rooms. Explicit access points describe connectivity;\n * room outlines, holes, pools and wall thickness describe where a person can walk. The input\n * document/graph are not mutated. Supplied door/vertical/directed edges are kept verbatim. */\nexport function buildRoomNavigation(\n project: ProjectDocument,\n access: readonly RoomAccess[],\n graph: RoomNavigationGraph = { nodes: [], edges: [] },\n): RoomNavigationGraph {\n const rooms = project.objects.filter(o => o.kind === 'room' && footprint(o)[0]?.length);\n const nodes: NavNode[] = graph.nodes\n .filter(n => !n.id.startsWith(GENERATED))\n .map(n => ({ ...n, position: [...n.position] }));\n const edges = graph.edges.filter(e => !e.id.startsWith(GENERATED));\n const byId = new Map(nodes.map(n => [n.id, n]));\n const members = new Map(rooms.map(room => [room.id, [] as NavNode[]]));\n const floors = new Map(rooms.map(room => [room.id, room.floorId]));\n for (const node of nodes)\n if (node.objectId && floors.get(node.objectId) === node.floorId) members.get(node.objectId)?.push(node);\n for (const { spaceId, nodeId } of access) {\n const node = byId.get(nodeId),\n room = members.get(spaceId);\n if (node && room && floors.get(spaceId) === node.floorId && !room.includes(node)) room.push(node);\n }\n const pools = new Map<string, Ring[]>();\n for (const o of project.objects)\n if (o.water && o.parentId) {\n const rings = pools.get(o.parentId) ?? [];\n rings.push(...footprint(o).slice(0, 1));\n pools.set(o.parentId, rings);\n }\n const clearance = createRouteClearance(project);\n for (const room of rooms) {\n const prefix = `${GENERATED}${encodeURIComponent(room.id)}:`;\n const candidates = members.get(room.id)!;\n const [outline, ...holes] = footprint(room);\n const visible = clearance(room.floorId, routeBounds(outline));\n const clear = (a: Point, b: Point) => segmentInRoom(a, b, outline) && visible(a, b);\n const obstacles = [...holes, ...(pools.get(room.id) ?? [])];\n const add = (position: Point) => {\n if (!clear(position, position)) return;\n if (candidates.some(n => distance(n.position, position) < 0.01)) return;\n const node: NavNode = { id: `${prefix}point:${candidates.length}`, floorId: room.floorId, position };\n nodes.push(node);\n candidates.push(node);\n };\n // Polygon offset corners handle concave rooms as well as rectangular halls. Each candidate is\n // still checked against the real geometry; no bounding-box shortcut can cross a notch or hole.\n const corners = (ring: Ring, inset: number) => {\n const points = openRing(ring),\n sign = winding(ring);\n const normal = (a: Point, b: Point): Point => {\n const d = distance(a, b) || 1;\n return [(sign * (a[1] - b[1])) / d, (sign * (b[0] - a[0])) / d];\n };\n for (let i = 0; i < points.length; i++) {\n const at = points[i],\n before = points[(i + points.length - 1) % points.length],\n next = points[(i + 1) % points.length];\n const a = normal(before, at),\n b = normal(at, next),\n divisor = 1 + a[0] * b[0] + a[1] * b[1];\n const turn = sign * ((at[0] - before[0]) * (next[1] - at[1]) - (at[1] - before[1]) * (next[0] - at[0]));\n if (inset > 0 && turn >= 0) continue; // only concave outer corners need waypoints\n if (divisor > 0.05) add([at[0] + (inset * (a[0] + b[0])) / divisor, at[1] + (inset * (a[1] + b[1])) / divisor]);\n if (inset < 0) add([(at[0] + next[0]) / 2 + inset * b[0], (at[1] + next[1]) / 2 + inset * b[1]]);\n }\n };\n corners(outline, ROUTE_CLEARANCE + 0.2);\n for (const obstacle of obstacles) corners(obstacle, -ROUTE_CLEARANCE - 0.1);\n // Optional perpendicular approaches avoid grazing a jamb on an oblique crossing. Aligned\n // access points can still connect directly, without a detour through either approach.\n const ring = openRing(outline),\n sign = winding(outline);\n for (const node of [...candidates])\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i],\n b = ring[(i + 1) % ring.length],\n length = distance(a, b);\n if (length && segmentProjection(node.position, a, b).distance < 0.01)\n add([\n node.position[0] + ((sign * (a[1] - b[1])) / length) * 0.9,\n node.position[1] + ((sign * (b[0] - a[0])) / length) * 0.9,\n ]);\n }\n let anchor = candidates.find(n => n.objectId === room.id);\n if (!anchor) {\n anchor = { id: `${prefix}anchor`, floorId: room.floorId, position: [...room.position], objectId: room.id };\n nodes.push(anchor);\n candidates.push(anchor);\n }\n if (!clear(anchor.position, anchor.position)) {\n const dry = candidates\n .filter(n => clear(n.position, n.position))\n .sort((a, b) => distance(a.position, room.position) - distance(b.position, room.position))[0];\n if (dry) anchor.position = [...dry.position];\n }\n for (let i = 0; i < candidates.length; i++)\n for (let j = i + 1; j < candidates.length; j++) {\n const a = candidates[i],\n b = candidates[j];\n if (clear(a.position, b.position))\n edges.push({ id: `${prefix}edge:${i}:${j}`, kind: 'walk', aId: a.id, bId: b.id });\n }\n }\n return { nodes, edges };\n}\n\n/** Every interval of the segment must stay inside the room, including when it passes exactly\n * through a concave vertex. Testing only the midpoint misses paths that leave and re-enter. */\nfunction segmentInRoom(a: Point, b: Point, outline: Ring): boolean {\n const ring = openRing(outline);\n const inside = (p: Point) =>\n pointInRing(p, ring) || ring.some((q, i) => segmentProjection(p, q, ring[(i + 1) % ring.length]).distance < 1e-7);\n if (!inside(a) || !inside(b)) return false;\n const dx = b[0] - a[0],\n dy = b[1] - a[1],\n length = distance(a, b);\n if (length < 1e-8) return true;\n const cuts = [0, 1];\n for (let i = 0; i < ring.length; i++) {\n const c = ring[i],\n d = ring[(i + 1) % ring.length],\n ex = d[0] - c[0],\n ey = d[1] - c[1],\n cross = dx * ey - dy * ex;\n if (Math.abs(cross) < 1e-9) {\n for (const p of [c, d]) if (segmentProjection(p, a, b).distance < 1e-7) cuts.push(segmentProjection(p, a, b).t);\n } else {\n const t = ((c[0] - a[0]) * ey - (c[1] - a[1]) * ex) / cross;\n const u = ((c[0] - a[0]) * dy - (c[1] - a[1]) * dx) / cross;\n if (t > 0 && t < 1 && u >= 0 && u <= 1) cuts.push(t);\n }\n }\n cuts.sort((a, b) => a - b);\n return cuts.slice(1).every((t, i) => inside([a[0] + (dx * (t + cuts[i])) / 2, a[1] + (dy * (t + cuts[i])) / 2]));\n}\n"],"names":["GENERATED","winding","ring","sum","p","i","q","buildRoomNavigation","project","access","graph","rooms","_a","footprint","nodes","n","edges","e","byId","members","room","floors","node","spaceId","nodeId","pools","rings","clearance","createRouteClearance","prefix","candidates","outline","holes","visible","routeBounds","clear","a","b","segmentInRoom","obstacles","add","position","distance","corners","inset","points","openRing","sign","normal","d","at","before","next","divisor","turn","ROUTE_CLEARANCE","obstacle","length","segmentProjection","anchor","dry","j","inside","pointInRing","dx","dy","cuts","c","ex","ey","cross","t","u"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { distance as W, footprint as x, openRing as v, pointInRing as E, intersects as y, segmentProjection as w } from "./geometry.js";
|
|
2
|
+
const k = 0.4, b = (t, o = 0) => [
|
|
3
|
+
Math.min(...t.map((e) => e[0])) - o,
|
|
4
|
+
Math.min(...t.map((e) => e[1])) - o,
|
|
5
|
+
Math.max(...t.map((e) => e[0])) + o,
|
|
6
|
+
Math.max(...t.map((e) => e[1])) + o
|
|
7
|
+
], R = (t, o) => t[0] <= o[2] && t[2] >= o[0] && t[1] <= o[3] && t[3] >= o[1], z = (t, o, e, l) => y(t, o, e, l) ? 0 : Math.min(
|
|
8
|
+
w(t, e, l).distance,
|
|
9
|
+
w(o, e, l).distance,
|
|
10
|
+
w(e, t, o).distance,
|
|
11
|
+
w(l, t, o).distance
|
|
12
|
+
);
|
|
13
|
+
function B(t) {
|
|
14
|
+
const o = new Map(t.junctions.map((n) => [n.id, n.position])), e = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const n of t.objects) {
|
|
16
|
+
if (!n.barrierId || n.kind !== "door" && n.kind !== "gate") continue;
|
|
17
|
+
const s = e.get(n.barrierId) ?? [];
|
|
18
|
+
s.push([(n.offset ?? 0) - n.width / 2, (n.offset ?? 0) + n.width / 2]), e.set(n.barrierId, s);
|
|
19
|
+
}
|
|
20
|
+
const l = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const n of t.barriers) {
|
|
22
|
+
const s = o.get(n.startId), i = o.get(n.endId);
|
|
23
|
+
if (!s || !i) continue;
|
|
24
|
+
const r = W(s, i);
|
|
25
|
+
if (!r) continue;
|
|
26
|
+
const M = (g) => [s[0] + (i[0] - s[0]) * g / r, s[1] + (i[1] - s[1]) * g / r], c = l.get(n.floorId) ?? [], u = (s[1] - i[1]) / r * n.thickness / 2, d = (i[0] - s[0]) / r * n.thickness / 2, a = (g, p) => {
|
|
27
|
+
if (p <= g) return;
|
|
28
|
+
const h = M(g), m = M(p), C = [
|
|
29
|
+
[h[0] + u, h[1] + d],
|
|
30
|
+
[m[0] + u, m[1] + d],
|
|
31
|
+
[m[0] - u, m[1] - d],
|
|
32
|
+
[h[0] - u, h[1] - d]
|
|
33
|
+
];
|
|
34
|
+
c.push({ ring: C, bounds: b(C, k) });
|
|
35
|
+
};
|
|
36
|
+
let f = 0;
|
|
37
|
+
for (const [g, p] of (e.get(n.id) ?? []).sort((h, m) => h[0] - m[0]))
|
|
38
|
+
a(f, Math.min(r, Math.max(0, g))), f = Math.max(f, Math.min(r, p));
|
|
39
|
+
a(f, r), l.set(n.floorId, c);
|
|
40
|
+
}
|
|
41
|
+
const I = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const n of t.objects) {
|
|
43
|
+
const s = n.water ? x(n).slice(0, 1) : n.kind === "room" || n.kind === "zone" ? x(n).slice(1) : [], i = I.get(n.floorId) ?? [];
|
|
44
|
+
for (const r of s) i.push({ ring: v(r), bounds: b(r, k) });
|
|
45
|
+
I.set(n.floorId, i);
|
|
46
|
+
}
|
|
47
|
+
return (n, s) => {
|
|
48
|
+
const i = (l.get(n) ?? []).filter((c) => !s || R(s, c.bounds)), r = (I.get(n) ?? []).filter((c) => !s || R(s, c.bounds)), M = [...i, ...r];
|
|
49
|
+
return (c, u) => {
|
|
50
|
+
const d = b([c, u]);
|
|
51
|
+
for (const a of M)
|
|
52
|
+
if (R(d, a.bounds)) {
|
|
53
|
+
if (E(c, a.ring) || E(u, a.ring)) return !1;
|
|
54
|
+
for (let f = 0; f < a.ring.length; f++)
|
|
55
|
+
if (z(c, u, a.ring[f], a.ring[(f + 1) % a.ring.length]) < k - 1e-7) return !1;
|
|
56
|
+
}
|
|
57
|
+
return !0;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const A = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
function L(t) {
|
|
63
|
+
let o = A.get(t);
|
|
64
|
+
return o || (o = B(t), A.set(t, o)), o;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
k as ROUTE_CLEARANCE,
|
|
68
|
+
B as createRouteClearance,
|
|
69
|
+
b as routeBounds,
|
|
70
|
+
L as routeClearance
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=routeClearance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeClearance.js","sources":["../../../../src/model/routeClearance.ts"],"sourcesContent":["import { distance, footprint, intersects, openRing, pointInRing, segmentProjection } from './geometry';\nimport type { Point, ProjectDocument, Ring } from './types';\n\n/** Extra room for shoulders and for the route marking, in plan metres. */\nexport const ROUTE_CLEARANCE = 0.4;\ntype Bounds = [number, number, number, number];\nexport const routeBounds = (points: Point[], pad = 0): Bounds => [\n Math.min(...points.map(p => p[0])) - pad,\n Math.min(...points.map(p => p[1])) - pad,\n Math.max(...points.map(p => p[0])) + pad,\n Math.max(...points.map(p => p[1])) + pad,\n];\nconst overlaps = (a: Bounds, b: Bounds) => a[0] <= b[2] && a[2] >= b[0] && a[1] <= b[3] && a[3] >= b[1];\nconst gap = (a: Point, b: Point, c: Point, d: Point) =>\n intersects(a, b, c, d)\n ? 0\n : Math.min(\n segmentProjection(a, c, d).distance,\n segmentProjection(b, c, d).distance,\n segmentProjection(c, a, b).distance,\n segmentProjection(d, a, b).distance,\n );\n\n/** A geometric guard for walking connections. It checks the whole segment, not samples along it.\n * Walls retain their thickness; doors/gates cut their actual apertures. Pools and floor holes are\n * obstacles even when a containing room's outer ring includes them. Build once for a document. */\nexport function createRouteClearance(project: ProjectDocument) {\n const junctions = new Map(project.junctions.map(j => [j.id, j.position]));\n const openings = new Map<string, [number, number][]>();\n for (const o of project.objects) {\n if (!o.barrierId || (o.kind !== 'door' && o.kind !== 'gate')) continue;\n const ranges = openings.get(o.barrierId) ?? [];\n ranges.push([(o.offset ?? 0) - o.width / 2, (o.offset ?? 0) + o.width / 2]);\n openings.set(o.barrierId, ranges);\n }\n const walls = new Map<string | null, { ring: Ring; bounds: Bounds }[]>();\n for (const wall of project.barriers) {\n const a = junctions.get(wall.startId),\n b = junctions.get(wall.endId);\n if (!a || !b) continue;\n const length = distance(a, b);\n if (!length) continue;\n const at = (d: number): Point => [a[0] + ((b[0] - a[0]) * d) / length, a[1] + ((b[1] - a[1]) * d) / length];\n const parts = walls.get(wall.floorId) ?? [];\n const nx = (((a[1] - b[1]) / length) * wall.thickness) / 2;\n const ny = (((b[0] - a[0]) / length) * wall.thickness) / 2;\n const add = (from: number, to: number) => {\n if (to <= from) return;\n const start = at(from),\n end = at(to);\n const ring: Ring = [\n [start[0] + nx, start[1] + ny],\n [end[0] + nx, end[1] + ny],\n [end[0] - nx, end[1] - ny],\n [start[0] - nx, start[1] - ny],\n ];\n parts.push({ ring, bounds: routeBounds(ring, ROUTE_CLEARANCE) });\n };\n let cursor = 0;\n for (const [from, to] of (openings.get(wall.id) ?? []).sort((a, b) => a[0] - b[0])) {\n add(cursor, Math.min(length, Math.max(0, from)));\n cursor = Math.max(cursor, Math.min(length, to));\n }\n add(cursor, length);\n walls.set(wall.floorId, parts);\n }\n const holes = new Map<string | null, { ring: Ring; bounds: Bounds }[]>();\n for (const o of project.objects) {\n const rings = o.water\n ? footprint(o).slice(0, 1)\n : o.kind === 'room' || o.kind === 'zone'\n ? footprint(o).slice(1)\n : [];\n const parts = holes.get(o.floorId) ?? [];\n for (const ring of rings) parts.push({ ring: openRing(ring), bounds: routeBounds(ring, ROUTE_CLEARANCE) });\n holes.set(o.floorId, parts);\n }\n /** Restrict the obstacle list once per room or query, so graph construction stays local. */\n return (floorId: string | null, extent?: Bounds) => {\n const localWalls = (walls.get(floorId) ?? []).filter(w => !extent || overlaps(extent, w.bounds));\n const localHoles = (holes.get(floorId) ?? []).filter(h => !extent || overlaps(extent, h.bounds));\n const obstacles = [...localWalls, ...localHoles];\n return (a: Point, b: Point): boolean => {\n const bounds = routeBounds([a, b]);\n for (const h of obstacles) {\n if (!overlaps(bounds, h.bounds)) continue;\n if (pointInRing(a, h.ring) || pointInRing(b, h.ring)) return false;\n for (let i = 0; i < h.ring.length; i++)\n if (gap(a, b, h.ring[i], h.ring[(i + 1) % h.ring.length]) < ROUTE_CLEARANCE - 1e-7) return false;\n }\n return true;\n };\n };\n}\n\nconst cache = new WeakMap<ProjectDocument, ReturnType<typeof createRouteClearance>>();\nexport function routeClearance(project: ProjectDocument) {\n let check = cache.get(project);\n if (!check) {\n check = createRouteClearance(project);\n cache.set(project, check);\n }\n return check;\n}\n"],"names":["ROUTE_CLEARANCE","routeBounds","points","pad","p","overlaps","a","b","gap","c","d","intersects","segmentProjection","createRouteClearance","project","junctions","j","openings","o","ranges","walls","wall","length","distance","at","parts","nx","ny","add","from","to","start","end","ring","cursor","holes","rings","footprint","openRing","floorId","extent","localWalls","w","localHoles","h","obstacles","bounds","pointInRing","i","cache","routeClearance","check"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import R from "polygon-clipping";
|
|
2
|
+
import { footprint as C, centroid as F, objectPosition as Y, pointInRing as S, barrierEnds as T, distance as E, closeRing as I, rectangle as _, openRing as z, objectArea as B, ringArea as k } from "./geometry.js";
|
|
3
|
+
import { isSpace as O } from "./types.js";
|
|
4
|
+
function L(s, r, i) {
|
|
5
|
+
let a = null;
|
|
6
|
+
for (const c of V(s, r))
|
|
7
|
+
S(i, c) && (!a || Math.abs(k(c)) < Math.abs(k(a))) && (a = c);
|
|
8
|
+
return a;
|
|
9
|
+
}
|
|
10
|
+
function V(s, r, i = 1) {
|
|
11
|
+
const a = s.barriers.filter((t) => t.floorId === r);
|
|
12
|
+
if (!a.length) return [];
|
|
13
|
+
const c = a.flatMap((t) => T(s, t)), u = 2, m = Math.min(...c.map((t) => t[0])) - u, x = Math.max(...c.map((t) => t[0])) + u, M = Math.min(...c.map((t) => t[1])) - u, y = Math.max(...c.map((t) => t[1])) + u, e = [
|
|
14
|
+
[m, M],
|
|
15
|
+
[x, M],
|
|
16
|
+
[x, y],
|
|
17
|
+
[m, y],
|
|
18
|
+
[m, M]
|
|
19
|
+
], h = a.map((t) => {
|
|
20
|
+
const [o, n] = T(s, t), g = E(o, n) || 1e-3, f = Math.atan2(n[1] - o[1], n[0] - o[0]) * 180 / Math.PI, P = [(o[0] + n[0]) / 2, (o[1] + n[1]) / 2];
|
|
21
|
+
return [
|
|
22
|
+
I(_(P, g + t.thickness, t.thickness, f)).map(
|
|
23
|
+
(d) => [Math.round(d[0] * 1e4) / 1e4, Math.round(d[1] * 1e4) / 1e4]
|
|
24
|
+
)
|
|
25
|
+
];
|
|
26
|
+
});
|
|
27
|
+
let l;
|
|
28
|
+
try {
|
|
29
|
+
l = R.difference([e], ...h);
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const p = (t) => Math.abs(
|
|
34
|
+
t.reduce((o, n, g) => {
|
|
35
|
+
const f = t[(g + 1) % t.length];
|
|
36
|
+
return o + n[0] * f[1] - f[0] * n[1];
|
|
37
|
+
}, 0) / 2
|
|
38
|
+
), b = (t) => {
|
|
39
|
+
const n = [];
|
|
40
|
+
for (const f of t) (!n.length || E(n[n.length - 1], f) > 1e-3) && n.push(f);
|
|
41
|
+
n.length > 1 && E(n[0], n[n.length - 1]) <= 1e-3 && n.pop();
|
|
42
|
+
const g = [];
|
|
43
|
+
for (let f = 0; f < n.length; f++) {
|
|
44
|
+
const P = n[(f + n.length - 1) % n.length], d = n[f], A = n[(f + 1) % n.length], N = d[0] - P[0], j = d[1] - P[1], v = A[0] - d[0], w = A[1] - d[1];
|
|
45
|
+
Math.abs(N * w - j * v) / (Math.hypot(N, j) * Math.hypot(v, w) || 1) > 1e-4 && g.push(d);
|
|
46
|
+
}
|
|
47
|
+
return g.length >= 3 ? g : n;
|
|
48
|
+
};
|
|
49
|
+
return l.filter((t) => !t[0].some((o) => o[0] <= m + 1e-6 || o[0] >= x - 1e-6 || o[1] <= M + 1e-6 || o[1] >= y - 1e-6)).map((t) => b(z(t[0]))).filter((t) => t.length >= 3 && p(t) >= i).sort((t, o) => p(o) - p(t));
|
|
50
|
+
}
|
|
51
|
+
function Q(s, r, i) {
|
|
52
|
+
var M, y;
|
|
53
|
+
if (!i.length) return 0;
|
|
54
|
+
const a = V(s, r), c = (e, h) => {
|
|
55
|
+
try {
|
|
56
|
+
const l = R.intersection([I(e)], [I(h)]), p = R.union([I(e)], [I(h)]), b = (o) => o.reduce((n, g) => n + Math.abs(k(g[0])), 0), t = b(p);
|
|
57
|
+
return t > 0 ? b(l) / t : 0;
|
|
58
|
+
} catch {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
}, u = (e, h) => h.find((l) => c(e, l) > 0.95), m = [];
|
|
62
|
+
for (const e of s.objects) {
|
|
63
|
+
if (e.floorId !== r || e.kind !== "room" || !((M = e.rings) != null && M.length) || e.geometry !== void 0 || !u(e.rings[0], i)) continue;
|
|
64
|
+
const h = (y = a.map((l) => ({ r: l, score: c(e.rings[0], l) })).filter((l) => l.score > 0.2).sort((l, p) => p.score - l.score)[0]) == null ? void 0 : y.r;
|
|
65
|
+
h && m.push({ room: e, ring: h });
|
|
66
|
+
}
|
|
67
|
+
let x = 0;
|
|
68
|
+
for (const { room: e, ring: h } of m) {
|
|
69
|
+
if (m.some((t) => t.room !== e && t.ring === h)) continue;
|
|
70
|
+
const l = e.rings.slice(1);
|
|
71
|
+
e.rings = [I(h), ...l], e.position = F(h);
|
|
72
|
+
const p = h.map((t) => t[0]), b = h.map((t) => t[1]);
|
|
73
|
+
e.width = Math.max(...p) - Math.min(...p), e.depth = Math.max(...b) - Math.min(...b), x++;
|
|
74
|
+
}
|
|
75
|
+
return x;
|
|
76
|
+
}
|
|
77
|
+
const U = (s) => s.objects.filter((r) => O(r.kind));
|
|
78
|
+
function W(s, r) {
|
|
79
|
+
const i = C(r)[0];
|
|
80
|
+
return i != null && i.length ? F(i) : Y(s, r);
|
|
81
|
+
}
|
|
82
|
+
function D(s, r) {
|
|
83
|
+
const [i, ...a] = C(s);
|
|
84
|
+
return !!i && S(r, i) && !a.some((c) => S(r, c));
|
|
85
|
+
}
|
|
86
|
+
function X(s, r, i) {
|
|
87
|
+
let a = null, c = Number.POSITIVE_INFINITY;
|
|
88
|
+
for (const u of s.objects) {
|
|
89
|
+
if (u.floorId !== r || !O(u.kind) || !D(u, i)) continue;
|
|
90
|
+
const m = B(u);
|
|
91
|
+
m >= c || (c = m, a = u);
|
|
92
|
+
}
|
|
93
|
+
return a;
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
L as enclosedRegion,
|
|
97
|
+
V as enclosedRegions,
|
|
98
|
+
D as inSpace,
|
|
99
|
+
Q as refitEnclosedRooms,
|
|
100
|
+
X as spaceAt,
|
|
101
|
+
W as spacePoint,
|
|
102
|
+
U as spaces
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=spaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spaces.js","sources":["../../../../src/model/spaces.ts"],"sourcesContent":["// Space queries: which objects are places, and which place a point is in. A space is somewhere you\n// can stand — the ringed area objects already on the plan, plus lifts and stairs — and everything\n// the ontology says is said about these, so the questions \"what counts?\" and \"where am I?\" sit\n// here, beneath both the described layer (ontology.ts) and the derived one (topology.ts).\nimport polygonClipping from 'polygon-clipping';\nimport {\n barrierEnds,\n centroid,\n closeRing,\n distance,\n footprint,\n objectArea,\n objectPosition,\n openRing,\n pointInRing,\n rectangle,\n ringArea,\n} from './geometry';\nimport { isSpace, type Point, type ProjectDocument, type Ring, type SiteObject } from './types';\n\n/** The enclosed region a point stands in, read off the walls around it.\n *\n * A floor's open area is its extent minus the bodies of its walls, and that difference falls apart\n * into exactly the regions the walls divide it into — so the room under a click is whichever of\n * those contains the click. No tracing, no flood fill, no tolerance to tune: the walls say where\n * the rooms are, and this reads the answer.\n *\n * Doorways do not leak, because a door is an object ON a continuous wall rather than a hole in the\n * run of it. A doorless passage does leak, and should: the drawing said those two places flow into\n * one another. Returns null when the point is not enclosed — outside the building, or inside a\n * boundary with a gap in it, where the region reaches the edge of the extent. */\nexport function enclosedRegion(project: ProjectDocument, floorId: string | null, point: Point): Ring | null {\n // The SMALLEST region containing the point, as spaceAt picks the smallest space: a walled island\n // — a sauna in a bathroom, a WC off a hall, a lift core in a lobby — sits inside the region around\n // it, and both contain a click in the island. The island is the one that was clicked.\n let best: Ring | null = null;\n for (const ring of enclosedRegions(project, floorId))\n if (pointInRing(point, ring) && (!best || Math.abs(ringArea(ring)) < Math.abs(ringArea(best)))) best = ring;\n return best;\n}\n\n/** Every enclosed region on a floor, largest first.\n *\n * Outer rings only: a region with a walled island inside it comes back with the island filled in,\n * so its area overstates the floor you could stand on by the island's footprint. Carrying the holes\n * through as further rings is the complete answer and changes this signature; until then the island\n * is its own region in the list, and enclosedRegion returns it for a click inside it.\n *\n * The same difference as `enclosedRegion`, read whole rather than probed: one subtraction gives all\n * the rooms the walls describe. Regions touching the extent are open to the outside and dropped,\n * and slivers below `minArea` — the gap between two walls that nearly meet — with them. */\nexport function enclosedRegions(project: ProjectDocument, floorId: string | null, minArea = 1): Ring[] {\n const walls = project.barriers.filter(b => b.floorId === floorId);\n if (!walls.length) return [];\n const corners: Point[] = walls.flatMap(b => barrierEnds(project, b) as unknown as Point[]);\n const pad = 2;\n const x0 = Math.min(...corners.map(p => p[0])) - pad,\n x1 = Math.max(...corners.map(p => p[0])) + pad,\n y0 = Math.min(...corners.map(p => p[1])) - pad,\n y1 = Math.max(...corners.map(p => p[1])) + pad;\n const extent: Ring = [\n [x0, y0],\n [x1, y0],\n [x1, y1],\n [x0, y1],\n [x0, y0],\n ];\n // Each wall as a solid: its centreline swept by its thickness, run on by half a thickness at each\n // end so corners close and no region leaks through the notch between two walls that meet.\n const solids = walls.map(b => {\n const [a, c] = barrierEnds(project, b);\n const len = distance(a, c) || 0.001;\n const angle = (Math.atan2(c[1] - a[1], c[0] - a[0]) * 180) / Math.PI;\n const mid: Point = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2];\n // Shared corners reached through different rotations differ at floating-point precision.\n // Quantise before clipping (as floorOutline does), or even a closed rectangle can fail\n // polygon-clipping's sweep and appear to enclose no space at all.\n return [\n closeRing(rectangle(mid, len + b.thickness, b.thickness, angle)).map(\n q => [Math.round(q[0] * 1e4) / 1e4, Math.round(q[1] * 1e4) / 1e4] as Point,\n ),\n ];\n });\n let open: Ring[][];\n try {\n open = polygonClipping.difference([extent], ...solids) as unknown as Ring[][];\n } catch {\n return []; // degenerate walls: no answer is better than a wrong one\n }\n const area = (ring: Ring) =>\n Math.abs(\n ring.reduce((sum, q, i) => {\n const r = ring[(i + 1) % ring.length];\n return sum + q[0] * r[1] - r[0] * q[1];\n }, 0) / 2,\n );\n // Clipping leaves vertices the document will not hold: a point repeated where two solids met, and\n // the collinear ones left along a straight edge that was cut and rejoined.\n const tidy = (ring: Ring): Ring => {\n // Use the document's 1 mm minimum edge, including near-duplicates introduced by clipping.\n const EPS = 0.001;\n const out: Ring = [];\n for (const q of ring) if (!out.length || distance(out[out.length - 1], q) > EPS) out.push(q);\n if (out.length > 1 && distance(out[0], out[out.length - 1]) <= EPS) out.pop();\n const straight: Ring = [];\n for (let i = 0; i < out.length; i++) {\n const prev = out[(i + out.length - 1) % out.length],\n here = out[i],\n next = out[(i + 1) % out.length];\n const ax = here[0] - prev[0],\n ay = here[1] - prev[1],\n bx = next[0] - here[0],\n by = next[1] - here[1];\n // The SINE of the turn, not the raw cross product: a cross product scales with the product of\n // the edge lengths, so a fixed threshold on it means something different on a 0.1 m edge than\n // on a 20 m one, and long collinear edges survive a cut-and-rejoin as false corners.\n const sine = Math.abs(ax * by - ay * bx) / (Math.hypot(ax, ay) * Math.hypot(bx, by) || 1);\n if (sine > 1e-4) straight.push(here);\n }\n return straight.length >= 3 ? straight : out;\n };\n return open\n .filter(pg => !pg[0].some(q => q[0] <= x0 + 1e-6 || q[0] >= x1 - 1e-6 || q[1] <= y0 + 1e-6 || q[1] >= y1 - 1e-6))\n .map(pg => tidy(openRing(pg[0])))\n .filter(ring => ring.length >= 3 && area(ring) >= minArea)\n .sort((a, b) => area(b) - area(a));\n}\n\n/** Re-fit the rooms that the walls were defining, after those walls have moved.\n *\n * A room stores its own outline, which is what lets a space exist where no wall does — a zone over\n * an open floor, a parcel, an area someone drew freehand. The cost is that moving a wall used to\n * leave the room it bounded exactly where it was, so the two drifted apart and had to be kept in\n * step by hand.\n *\n * They do not have to be. Pass the regions as they stood BEFORE the edit: a room whose outline was\n * one of them was being defined by the walls, so it takes the region that now stands in its place.\n * A room that matched nothing was drawn rather than enclosed, and is left alone. Identity is never\n * touched — the name, colour, bindings and id are the room; only its shape came from the walls. */\nexport function refitEnclosedRooms(project: ProjectDocument, floorId: string | null, before: Ring[]): number {\n if (!before.length) return 0;\n const after = enclosedRegions(project, floorId);\n // How much two outlines are the same outline. Overlap rather than centroid-in-ring: an L-shaped\n // room has its centroid outside itself, and a containment test quietly fails to match every such\n // room — of which a building with a bay or a wing has several.\n const sameness = (one: Ring, two: Ring) => {\n try {\n const inter = polygonClipping.intersection([closeRing(one)], [closeRing(two)]) as unknown as Ring[][];\n const union = polygonClipping.union([closeRing(one)], [closeRing(two)]) as unknown as Ring[][];\n const sum = (pg: Ring[][]) => pg.reduce((t, poly) => t + Math.abs(ringArea(poly[0])), 0);\n const u = sum(union);\n return u > 0 ? sum(inter) / u : 0;\n } catch {\n return 0;\n }\n };\n const matches = (ring: Ring, pool: Ring[]) => pool.find(r => sameness(ring, r) > 0.95);\n // Work out every room's claim first, then apply only the claims that are unambiguous. Removing a\n // wall merges two regions into one, and both of the rooms it separated then point at that same\n // region: growing both to fill it would draw two rooms over each other. Two claims on one region\n // means the edit changed what the rooms ARE, not merely their shape, and that is a question for\n // whoever made the edit — mergeSpaces, or leaving them be — not for a re-fit to answer.\n const claims: { room: SiteObject; ring: Ring }[] = [];\n for (const room of project.objects) {\n if (room.floorId !== floorId || room.kind !== 'room' || !room.rings?.length || room.geometry !== undefined)\n continue;\n if (!matches(room.rings[0], before)) continue; // drawn, not enclosed — leave it be\n // The region that best stands where this room stood. Best overlap, not containment, for the\n // same concave reason.\n const now = after\n .map(r => ({ r, score: sameness(room.rings![0], r) }))\n .filter(x => x.score > 0.2)\n .sort((a, b) => b.score - a.score)[0]?.r;\n if (!now) continue; // its walls no longer enclose anything; keep the last good shape\n claims.push({ room, ring: now });\n }\n let refitted = 0;\n for (const { room, ring } of claims) {\n if (claims.some(other => other.room !== room && other.ring === ring)) continue; // contested\n // Holes are carried across untouched. A wall that bounded a courtyard rather than the room's\n // outside leaves its hole stale; rare enough to name rather than solve.\n const holes = room.rings!.slice(1);\n room.rings = [closeRing(ring), ...holes];\n room.position = centroid(ring);\n const xs = ring.map(p => p[0]),\n ys = ring.map(p => p[1]);\n room.width = Math.max(...xs) - Math.min(...xs);\n room.depth = Math.max(...ys) - Math.min(...ys);\n refitted++;\n }\n return refitted;\n}\n\n/** Spaces are objects you can stand in — the areas already in the document, plus lifts and stairs.\n * Nothing new is stored for them: a room *is* a space, and zones and portals reference it by id. */\nexport const spaces = (project: ProjectDocument): SiteObject[] => project.objects.filter(o => isSpace(o.kind));\n\n/** Where a space sits for routing. Its footprint's centroid, falling back to its placed position. */\nexport function spacePoint(project: ProjectDocument, space: SiteObject): Point {\n const ring = footprint(space)[0];\n return ring?.length ? centroid(ring) : objectPosition(project, space);\n}\n\n/** True when `point` falls inside a space's footprint — inside its outline and outside its holes. */\nexport function inSpace(space: SiteObject, point: Point): boolean {\n const [outline, ...holes] = footprint(space);\n return !!outline && pointInRing(point, outline) && !holes.some(hole => pointInRing(point, hole));\n}\n\n/** The space a point falls in: the *smallest* one containing it, since a room inside a zone inside a\n * floor plate are all true and only the innermost is useful. */\nexport function spaceAt(project: ProjectDocument, floorId: string | null, point: Point): SiteObject | null {\n let best: SiteObject | null = null,\n bestArea = Number.POSITIVE_INFINITY;\n for (const space of project.objects) {\n if (space.floorId !== floorId || !isSpace(space.kind) || !inSpace(space, point)) continue;\n const area = objectArea(space);\n if (area >= bestArea) continue;\n bestArea = area;\n best = space;\n }\n return best;\n}\n"],"names":["enclosedRegion","project","floorId","point","best","ring","enclosedRegions","pointInRing","ringArea","minArea","walls","b","corners","barrierEnds","pad","x0","p","x1","y0","y1","extent","solids","a","c","len","distance","angle","mid","closeRing","rectangle","q","open","polygonClipping","area","sum","i","r","tidy","out","straight","prev","here","next","ax","ay","bx","by","pg","openRing","refitEnclosedRooms","before","after","sameness","one","two","inter","union","t","poly","u","matches","pool","claims","room","_a","now","_b","x","refitted","other","holes","centroid","xs","ys","spaces","o","isSpace","spacePoint","space","footprint","objectPosition","inSpace","outline","hole","spaceAt","bestArea","objectArea"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { rectangle as u, add as G, rotate as I } from "./geometry.js";
|
|
2
|
+
import { treads as r } from "./vertical.js";
|
|
3
|
+
function Y(n, o, h, p) {
|
|
4
|
+
const f = [], w = [], M = o.at(0), m = (t) => G(M, I(t, o.rotation)), c = (t, s, e, d, i) => {
|
|
5
|
+
e > 1e-3 && d > 1e-3 && w.push({ ring: u(m([t, s]), e, d, o.rotation), height: i });
|
|
6
|
+
}, g = (t, s, e, d, i) => {
|
|
7
|
+
const l = Math.hypot(s[0] - t[0], s[1] - t[1]);
|
|
8
|
+
f.push({ foot: m(t), head: m(s), width: e, base: d, rise: i, steps: r(i, l).steps });
|
|
9
|
+
}, a = o.half;
|
|
10
|
+
if (p === "straight")
|
|
11
|
+
g([0, -a], [0, -o.topT], n.width * 0.94, 0, h), c(0, (a - o.topT) / 2, n.width, a + o.topT, h);
|
|
12
|
+
else if (p === "dogleg") {
|
|
13
|
+
const t = Math.min(n.width, o.length) * 0.45, s = -n.width / 2 + t / 2, e = a - t / 2, d = r(h / 2, o.length - t), i = r(h / 2, n.width - t), l = d.steps * d.going, T = i.steps * i.going, x = a - t - l, y = -n.width / 2 + t + T;
|
|
14
|
+
g([s, x], [s, a - t], t * 0.94, 0, h / 2), c(s, (x - a) / 2, t, x + a, 0), c(s, e, t, t, h / 2), g([-n.width / 2 + t, e], [y, e], t * 0.94, h / 2, h / 2), c((y + n.width / 2) / 2, e, n.width / 2 - y, t, h);
|
|
15
|
+
} else {
|
|
16
|
+
const t = Math.min(Math.max(0.6, n.width / 2), o.length * 0.35), { steps: s, going: e } = r(h / 2, o.length - t), d = -a + s * e;
|
|
17
|
+
g([-n.width / 4, -a], [-n.width / 4, d], n.width * 0.47, 0, h / 2), c(0, (d + a) / 2, n.width, a - d, h / 2), g([n.width / 4, d], [n.width / 4, -a], n.width * 0.47, h / 2, h / 2);
|
|
18
|
+
}
|
|
19
|
+
return { lanes: f, landings: w };
|
|
20
|
+
}
|
|
21
|
+
function k(n, o, h) {
|
|
22
|
+
const p = Math.max(0.8, Math.min(n.width, n.depth) / 2), f = Math.min(0.16, p / 5), w = Math.max(6, Math.min(30, Math.round(h / 0.18))), M = Array.from({ length: w }, (m, c) => {
|
|
23
|
+
const g = c * Math.PI * 2 / w, a = (c + 1.04) * Math.PI * 2 / w, t = [];
|
|
24
|
+
for (const [s, e, d] of [
|
|
25
|
+
[p, g, a],
|
|
26
|
+
[f, a, g]
|
|
27
|
+
])
|
|
28
|
+
for (let i = 0; i <= 4; i++) {
|
|
29
|
+
const l = e + (d - e) * i / 4;
|
|
30
|
+
t.push([o[0] + Math.cos(l) * s, o[1] + Math.sin(l) * s]);
|
|
31
|
+
}
|
|
32
|
+
return { ring: t, height: h * (c + 1) / w };
|
|
33
|
+
});
|
|
34
|
+
return { inner: f, wedges: M };
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
k as spiralGeometry,
|
|
38
|
+
Y as stairGeometry
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=stairGeometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stairGeometry.js","sources":["../../../../src/model/stairGeometry.ts"],"sourcesContent":["import type { Point, Ring, SiteObject } from './types';\nimport { add, rectangle, rotate } from './geometry';\nimport { treads, type FlightRun, type StairModel } from './vertical';\n\nexport interface StairLane {\n foot: Point;\n head: Point;\n width: number;\n base: number;\n rise: number;\n steps: number;\n}\nexport interface StairLanding {\n ring: Ring;\n height: number;\n}\n\n/** One description of the steps and landings, used by both rendering and walking support. */\nexport function stairGeometry(o: SiteObject, run: FlightRun, rise: number, model: StairModel) {\n const lanes: StairLane[] = [],\n landings: StairLanding[] = [];\n const center = run.at(0);\n const at = (p: Point) => add(center, rotate(p, run.rotation));\n const landing = (x: number, y: number, w: number, d: number, height: number) => {\n if (w > 0.001 && d > 0.001) landings.push({ ring: rectangle(at([x, y]), w, d, run.rotation), height });\n };\n const lane = (foot: Point, head: Point, width: number, base: number, climb: number) => {\n const length = Math.hypot(head[0] - foot[0], head[1] - foot[1]);\n lanes.push({ foot: at(foot), head: at(head), width, base, rise: climb, steps: treads(climb, length).steps });\n };\n const half = run.half;\n if (model === 'straight') {\n lane([0, -half], [0, -run.topT], o.width * 0.94, 0, rise);\n landing(0, (half - run.topT) / 2, o.width, half + run.topT, rise);\n } else if (model === 'dogleg') {\n const width = Math.min(o.width, run.length) * 0.45;\n const x = -o.width / 2 + width / 2,\n y = half - width / 2;\n const first = treads(rise / 2, run.length - width),\n second = treads(rise / 2, o.width - width);\n const firstRun = first.steps * first.going,\n secondRun = second.steps * second.going;\n const footY = half - width - firstRun,\n headX = -o.width / 2 + width + secondRun;\n lane([x, footY], [x, half - width], width * 0.94, 0, rise / 2);\n landing(x, (footY - half) / 2, width, footY + half, 0);\n landing(x, y, width, width, rise / 2);\n lane([-o.width / 2 + width, y], [headX, y], width * 0.94, rise / 2, rise / 2);\n landing((headX + o.width / 2) / 2, y, o.width / 2 - headX, width, rise);\n } else {\n const landingDepth = Math.min(Math.max(0.6, o.width / 2), run.length * 0.35);\n const { steps, going } = treads(rise / 2, run.length - landingDepth);\n const headY = -half + steps * going;\n lane([-o.width / 4, -half], [-o.width / 4, headY], o.width * 0.47, 0, rise / 2);\n landing(0, (headY + half) / 2, o.width, half - headY, rise / 2);\n lane([o.width / 4, headY], [o.width / 4, -half], o.width * 0.47, rise / 2, rise / 2);\n }\n return { lanes, landings };\n}\n\n/** The same wedge treads for the spiral mesh and its walkable support. */\nexport function spiralGeometry(o: SiteObject, position: Point, rise: number) {\n const outer = Math.max(0.8, Math.min(o.width, o.depth) / 2),\n inner = Math.min(0.16, outer / 5);\n const steps = Math.max(6, Math.min(30, Math.round(rise / 0.18)));\n const wedges = Array.from({ length: steps }, (_, i) => {\n const start = (i * Math.PI * 2) / steps,\n end = ((i + 1.04) * Math.PI * 2) / steps;\n const ring: Ring = [];\n for (const [r, from, to] of [\n [outer, start, end],\n [inner, end, start],\n ])\n for (let k = 0; k <= 4; k++) {\n const angle = from + ((to - from) * k) / 4;\n ring.push([position[0] + Math.cos(angle) * r, position[1] + Math.sin(angle) * r]);\n }\n return { ring, height: (rise * (i + 1)) / steps };\n });\n return { inner, wedges };\n}\n"],"names":["stairGeometry","o","run","rise","model","lanes","landings","center","at","p","add","rotate","landing","x","y","w","height","rectangle","lane","foot","head","width","base","climb","length","treads","half","first","second","firstRun","secondRun","footY","headX","landingDepth","steps","going","headY","spiralGeometry","position","outer","inner","wedges","_","i","start","end","ring","r","from","to","k","angle"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { add as I, objectPosition as P, distance as y, centroid as k } from "./geometry.js";
|
|
2
|
+
import { stairGeometry as w } from "./stairGeometry.js";
|
|
3
|
+
import { primaryShafts as G, shaftKey as x, flights as K, stairModel as R, flightRun as S } from "./vertical.js";
|
|
4
|
+
function B(n, c, f, m) {
|
|
5
|
+
const T = G(n), e = n.objects.find((t) => t.kind === "stairs" && T.has(t.id) && x(t) === x(c)) ?? c, p = K(n, e), l = p.findIndex((t) => t.from.id === f && t.to.id === m || t.from.id === m && t.to.id === f);
|
|
6
|
+
if (l < 0) return null;
|
|
7
|
+
const d = p[l], h = R(n, e), o = S(n, e, d.rise, h, l), a = [];
|
|
8
|
+
if (h === "escalator")
|
|
9
|
+
a.push(o.at(o.half + 0.35), o.at(o.footT), o.at(o.topT), o.at(-o.half - 0.35));
|
|
10
|
+
else if (h === "spiral") {
|
|
11
|
+
const t = Math.max(0.8, Math.min(e.width, e.depth) / 2) * 0.65;
|
|
12
|
+
for (let s = 0; s <= 160; s++) {
|
|
13
|
+
const r = 0.01 + (Math.PI * 2 - 0.02) * s / 160;
|
|
14
|
+
a.push(I(P(n, e), [Math.cos(r) * t, Math.sin(r) * t]));
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
const t = w(e, o, d.rise, h);
|
|
18
|
+
for (const [b, i] of t.lanes.entries()) {
|
|
19
|
+
const g = y(i.foot, i.head);
|
|
20
|
+
if (!b)
|
|
21
|
+
a.push([
|
|
22
|
+
i.foot[0] - (i.head[0] - i.foot[0]) / g * 0.35,
|
|
23
|
+
i.foot[1] - (i.head[1] - i.foot[1]) / g * 0.35
|
|
24
|
+
]);
|
|
25
|
+
else {
|
|
26
|
+
const M = t.landings.find((v) => v.height === i.base);
|
|
27
|
+
M && a.push(k(M.ring));
|
|
28
|
+
}
|
|
29
|
+
a.push(i.foot, i.head);
|
|
30
|
+
}
|
|
31
|
+
const s = t.lanes.at(-1), r = y(s.foot, s.head);
|
|
32
|
+
a.push([
|
|
33
|
+
s.head[0] + (s.head[0] - s.foot[0]) / r * 0.35,
|
|
34
|
+
s.head[1] + (s.head[1] - s.foot[1]) / r * 0.35
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
const u = d.from.id === f;
|
|
38
|
+
return u || a.reverse(), {
|
|
39
|
+
points: a,
|
|
40
|
+
up: u,
|
|
41
|
+
escalator: h === "escalator",
|
|
42
|
+
speed: 0.5 * o.incline / Math.hypot(o.incline, d.rise)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
B as stairTraversal
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=stairTraversal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stairTraversal.js","sources":["../../../../src/model/stairTraversal.ts"],"sourcesContent":["import { add, centroid, distance, objectPosition } from './geometry';\nimport { stairGeometry } from './stairGeometry';\nimport { flightRun, flights, primaryShafts, shaftKey, stairModel } from './vertical';\nimport type { Point, ProjectDocument, SiteObject } from './types';\n\n/** The tread/landing centreline shared with physical walking, in travel order. */\nexport function stairTraversal(project: ProjectDocument, object: SiteObject, from: string, to: string) {\n const primary = primaryShafts(project);\n const stair =\n project.objects.find(o => o.kind === 'stairs' && primary.has(o.id) && shaftKey(o) === shaftKey(object)) ?? object;\n const all = flights(project, stair);\n const index = all.findIndex(f => (f.from.id === from && f.to.id === to) || (f.from.id === to && f.to.id === from));\n if (index < 0) return null;\n const flight = all[index],\n model = stairModel(project, stair),\n run = flightRun(project, stair, flight.rise, model, index);\n const points: Point[] = [];\n if (model === 'escalator') {\n points.push(run.at(run.half + 0.35), run.at(run.footT), run.at(run.topT), run.at(-run.half - 0.35));\n } else if (model === 'spiral') {\n const radius = Math.max(0.8, Math.min(stair.width, stair.depth) / 2) * 0.65;\n for (let i = 0; i <= 160; i++) {\n const angle = 0.01 + ((Math.PI * 2 - 0.02) * i) / 160;\n points.push(add(objectPosition(project, stair), [Math.cos(angle) * radius, Math.sin(angle) * radius]));\n }\n } else {\n const geometry = stairGeometry(stair, run, flight.rise, model);\n for (const [i, lane] of geometry.lanes.entries()) {\n const length = distance(lane.foot, lane.head);\n if (!i)\n points.push([\n lane.foot[0] - ((lane.head[0] - lane.foot[0]) / length) * 0.35,\n lane.foot[1] - ((lane.head[1] - lane.foot[1]) / length) * 0.35,\n ]);\n else {\n const landing = geometry.landings.find(l => l.height === lane.base);\n if (landing) points.push(centroid(landing.ring));\n }\n points.push(lane.foot, lane.head);\n }\n const last = geometry.lanes.at(-1)!;\n const length = distance(last.foot, last.head);\n points.push([\n last.head[0] + ((last.head[0] - last.foot[0]) / length) * 0.35,\n last.head[1] + ((last.head[1] - last.foot[1]) / length) * 0.35,\n ]);\n }\n const up = flight.from.id === from;\n if (!up) points.reverse();\n return {\n points,\n up,\n escalator: model === 'escalator',\n speed: (0.5 * run.incline) / Math.hypot(run.incline, flight.rise),\n };\n}\n"],"names":["stairTraversal","project","object","from","to","primary","primaryShafts","stair","o","shaftKey","all","flights","index","f","flight","model","stairModel","run","flightRun","points","radius","i","angle","add","objectPosition","geometry","stairGeometry","lane","length","distance","landing","l","centroid","last","up"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "polygon-clipping";
|
|
2
|
+
import { isSpace as v } from "./types.js";
|
|
3
|
+
import { spacePoint as b } from "./spaces.js";
|
|
4
|
+
import { zoneSpaces as h } from "./ontology.js";
|
|
5
|
+
import { TRAVERSABLE as I } from "./passages.js";
|
|
6
|
+
import { effectivePortals as w } from "./portals.js";
|
|
7
|
+
const $ = (e) => `space:${e}`;
|
|
8
|
+
function z(e, d, f) {
|
|
9
|
+
const a = e.floorId === d.floorId;
|
|
10
|
+
return !a && e.floorId !== null && d.floorId !== null ? e.kind === "elevator" || d.kind === "elevator" ? "elevator" : "stairs" : a ? f && I.has(f.kind) ? "door" : "walk" : "door";
|
|
11
|
+
}
|
|
12
|
+
function M(e) {
|
|
13
|
+
const d = new Map(e.objects.map((o) => [o.id, o])), f = new Map(e.floors.map((o) => [o.id, o])), a = /* @__PURE__ */ new Map(), u = (o) => {
|
|
14
|
+
const t = $(o.id), i = a.get(t);
|
|
15
|
+
if (i) return i;
|
|
16
|
+
const s = { id: t, floorId: o.floorId, position: b(e, o), objectId: o.id };
|
|
17
|
+
return a.set(t, s), s;
|
|
18
|
+
};
|
|
19
|
+
for (const o of e.objects) v(o.kind) && u(o);
|
|
20
|
+
const p = [], m = (o, t, i, s, n, r = !1) => {
|
|
21
|
+
if (i.id === s.id) return;
|
|
22
|
+
const c = { id: o, kind: t, aId: u(i).id, bId: u(s).id };
|
|
23
|
+
n && (c.objectId = n), r && (c.directed = !0), p.push(c);
|
|
24
|
+
};
|
|
25
|
+
for (const o of w(e)) {
|
|
26
|
+
if ((o.passage ?? "both") === "none") continue;
|
|
27
|
+
const t = d.get(o.a), i = d.get(o.b);
|
|
28
|
+
if (!t || !i) continue;
|
|
29
|
+
const s = o.openingId ? d.get(o.openingId) : void 0, n = z(t, i, s), r = s && (n === "door" ? I.has(s.kind) : n === "walk" ? !1 : s.kind === n), c = o.passage ?? "both", [l, k] = c === "b-to-a" ? [i, t] : [t, i];
|
|
30
|
+
m(`portal:${o.id}`, n, l, k, r ? s.id : void 0, c !== "both");
|
|
31
|
+
}
|
|
32
|
+
for (const o of e.zones ?? []) {
|
|
33
|
+
if (!o.connects) continue;
|
|
34
|
+
const t = h(e, o).map((n) => d.get(n)).filter((n) => !!n && n.floorId !== null).sort((n, r) => {
|
|
35
|
+
var c, l;
|
|
36
|
+
return (((c = f.get(n.floorId)) == null ? void 0 : c.elevation) ?? 0) - (((l = f.get(r.floorId)) == null ? void 0 : l.elevation) ?? 0);
|
|
37
|
+
}), i = t.some((n) => n.kind === "elevator") ? "elevator" : "stairs", s = o.connects === "up" || o.connects === "down";
|
|
38
|
+
for (let n = 1; n < t.length; n++)
|
|
39
|
+
for (let r = o.connects === "all" ? 0 : n - 1; r < n; r++)
|
|
40
|
+
t[r].floorId !== t[n].floorId && m(
|
|
41
|
+
`zone:${o.id}:${r}:${n}`,
|
|
42
|
+
i,
|
|
43
|
+
o.connects === "down" ? t[n] : t[r],
|
|
44
|
+
o.connects === "down" ? t[r] : t[n],
|
|
45
|
+
t[n].kind === i ? t[n].id : void 0,
|
|
46
|
+
s
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return { nodes: [...a.values()], edges: p };
|
|
50
|
+
}
|
|
51
|
+
const g = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
function R(e) {
|
|
53
|
+
const d = g.get(e);
|
|
54
|
+
if (d) return d;
|
|
55
|
+
const f = M(e);
|
|
56
|
+
return g.set(e, f), f;
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
M as derivedGraph,
|
|
60
|
+
$ as spaceNodeId,
|
|
61
|
+
R as topology
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=topology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.js","sources":["../../../../src/model/topology.ts"],"sourcesContent":["// The navigation graph as a *derived dual* of the plan: spaces are nodes, portals are edges, and a\n// zone's `connects` supplies the vertical ones. This is IndoorGML's Poincaré duality — a k-dimensional\n// object in primal space maps to an (N−k)-dimensional one in dual space, so rooms become points and\n// the surfaces between them become lines.\n//\n// Nothing here is authored or stored. It is recomputed from the geometry, which is the whole point: a\n// hand-kept graph is a second description of what connects to what, and two descriptions drift.\nimport { distance } from './geometry';\nimport { isSpace, type NavEdge, type NavEdgeKind, type NavNode } from './types';\nimport type { Floor, ProjectDocument, SiteObject } from './types';\nimport { spacePoint } from './spaces';\nimport { zoneSpaces } from './ontology';\nimport { TRAVERSABLE } from './passages';\nimport { effectivePortals } from './portals';\n\n/** Node id for a space. Prefixed so a derived graph can never collide with an authored entity id. */\nexport const spaceNodeId = (spaceId: string) => `space:${spaceId}`;\n\n/** A portal's edge kind.\n *\n * Two different floors is a vertical move. Outdoors is *not* a floor, though — it is `null` — so a\n * front door joins two different `floorId`s while being the least vertical thing in the building.\n * Missing that turns every entrance into a flight of stairs. */\nfunction portalKind(a: SiteObject, b: SiteObject, opening?: SiteObject): NavEdgeKind {\n const sameFloor = a.floorId === b.floorId;\n if (!sameFloor && a.floorId !== null && b.floorId !== null)\n return a.kind === 'elevator' || b.kind === 'elevator' ? 'elevator' : 'stairs';\n // Stepping outside: a threshold, which is what door edges are allowed to be even without a leaf.\n if (!sameFloor) return 'door';\n return opening && TRAVERSABLE.has(opening.kind) ? 'door' : 'walk';\n}\n\nexport function derivedGraph(project: ProjectDocument): { nodes: NavNode[]; edges: NavEdge[] } {\n const objects = new Map(project.objects.map(o => [o.id, o]));\n const floors = new Map<string, Floor>(project.floors.map(f => [f.id, f]));\n const nodes = new Map<string, NavNode>();\n const node = (space: SiteObject): NavNode => {\n const id = spaceNodeId(space.id);\n const existing = nodes.get(id);\n if (existing) return existing;\n const made: NavNode = { id, floorId: space.floorId, position: spacePoint(project, space), objectId: space.id };\n nodes.set(id, made);\n return made;\n };\n // Every space gets a node, whether or not anything connects to it. Creating them only as edges\n // demand them makes an unconnected space vanish from the graph entirely — and routing anchors an\n // object with no footprint (a POI, a door) to the *nearest node*, so a missing node is not merely a\n // gap in the graph, it is an entrance that can never be departed from.\n for (const space of project.objects) if (isSpace(space.kind)) node(space);\n const edges: NavEdge[] = [];\n const join = (id: string, kind: NavEdgeKind, a: SiteObject, b: SiteObject, objectId?: string, directed = false) => {\n if (a.id === b.id) return;\n const edge: NavEdge = { id, kind, aId: node(a).id, bId: node(b).id };\n if (objectId) edge.objectId = objectId;\n if (directed) edge.directed = true;\n edges.push(edge);\n };\n\n for (const portal of effectivePortals(project)) {\n if ((portal.passage ?? 'both') === 'none') continue;\n const a = objects.get(portal.a),\n b = objects.get(portal.b);\n if (!a || !b) continue;\n const opening = portal.openingId ? objects.get(portal.openingId) : undefined;\n const kind = portalKind(a, b, opening);\n // The edge names the opening it crosses only when the two agree: a door on a door edge, a lift\n // on a lift edge. A cross-floor portal may still carry a door — a landing door at a half level —\n // but a vertical edge claiming a door as its stairs would make the document invalid, so a\n // mismatched opening is simply not named and the edge stands on its own.\n const bindable =\n opening && (kind === 'door' ? TRAVERSABLE.has(opening.kind) : kind === 'walk' ? false : opening.kind === kind);\n // A one-way portal becomes a one-way edge. Emitting it undirected would let a route come back\n // through a fire exit, which is the whole thing `passage` exists to forbid.\n const passage = portal.passage ?? 'both';\n const [from, to] = passage === 'b-to-a' ? [b, a] : [a, b];\n join(`portal:${portal.id}`, kind, from, to, bindable ? opening.id : undefined, passage !== 'both');\n }\n\n // A zone that declares its own internal connectivity generates the edges its members imply — which\n // is how a 17-storey lift is one line of authoring rather than 136 portals.\n for (const zone of project.zones ?? []) {\n if (!zone.connects) continue;\n const members = zoneSpaces(project, zone)\n .map(id => objects.get(id))\n .filter((o): o is SiteObject => !!o && o.floorId !== null)\n .sort((x, y) => (floors.get(x.floorId!)?.elevation ?? 0) - (floors.get(y.floorId!)?.elevation ?? 0));\n const kind: NavEdgeKind = members.some(m => m.kind === 'elevator') ? 'elevator' : 'stairs';\n // An escalator carries you one way. Members are sorted upwards, so 'up' runs lower → higher and\n // 'down' is the same pairs reversed; both are directed, which is what makes them escalators\n // rather than stairs that happen to be named after a direction.\n const oneWay = zone.connects === 'up' || zone.connects === 'down';\n for (let i = 1; i < members.length; i++)\n for (let j = zone.connects === 'all' ? 0 : i - 1; j < i; j++)\n if (members[j].floorId !== members[i].floorId)\n // The edge only names the object it rides when that object really is the lift or stair.\n // A zone may connect plain rooms — a split-level atrium, a ramp between decks — and\n // claiming a room is an elevator would make the document invalid, not merely untidy.\n join(\n `zone:${zone.id}:${j}:${i}`,\n kind,\n zone.connects === 'down' ? members[i] : members[j],\n zone.connects === 'down' ? members[j] : members[i],\n members[i].kind === kind ? members[i].id : undefined,\n oneWay,\n );\n }\n return { nodes: [...nodes.values()], edges };\n}\n\n// Recomputed on every routing query otherwise, and routing runs per keystroke in the navigate panel.\n// Documents are replaced rather than mutated, so a WeakMap entry retires with the document it\n// describes — the same bargain exteriorWalls strikes.\nconst cache = new WeakMap<ProjectDocument, { nodes: NavNode[]; edges: NavEdge[] }>();\nexport function topology(project: ProjectDocument): { nodes: NavNode[]; edges: NavEdge[] } {\n const hit = cache.get(project);\n if (hit) return hit;\n const built = derivedGraph(project);\n cache.set(project, built);\n return built;\n}\n\n/** Straight-line length of a derived edge, for cost comparisons against an authored graph. */\nexport const edgeSpan = (nodes: Map<string, NavNode>, edge: NavEdge): number => {\n const a = nodes.get(edge.aId),\n b = nodes.get(edge.bId);\n return a && b ? distance(a.position, b.position) : 0;\n};\n"],"names":["spaceNodeId","spaceId","portalKind","a","b","opening","sameFloor","TRAVERSABLE","derivedGraph","project","objects","floors","f","nodes","node","space","id","existing","made","spacePoint","isSpace","edges","join","kind","objectId","directed","edge","portal","effectivePortals","bindable","passage","from","to","zone","members","zoneSpaces","o","x","y","_a","_b","m","oneWay","i","j","cache","topology","hit","built"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const a = [
|
|
2
|
+
"zone",
|
|
3
|
+
"room",
|
|
4
|
+
"parcel",
|
|
5
|
+
"building",
|
|
6
|
+
"office",
|
|
7
|
+
"container",
|
|
8
|
+
"storage",
|
|
9
|
+
"door",
|
|
10
|
+
"window",
|
|
11
|
+
"gate",
|
|
12
|
+
"turnstile",
|
|
13
|
+
"reader",
|
|
14
|
+
"camera",
|
|
15
|
+
"elevator",
|
|
16
|
+
"stairs",
|
|
17
|
+
"poi",
|
|
18
|
+
"fixture",
|
|
19
|
+
"light",
|
|
20
|
+
"landscape",
|
|
21
|
+
"sensor",
|
|
22
|
+
"alarm",
|
|
23
|
+
"equipment",
|
|
24
|
+
"evacuation"
|
|
25
|
+
], r = ["silent", "office", "backrooms", "plant", "baths", "elevator"], t = () => crypto.randomUUID(), o = (e) => ["zone", "room", "parcel", "building", "office", "container", "storage", "evacuation"].includes(e), n = (e) => ["door", "gate", "turnstile", "reader", "camera", "elevator", "sensor", "alarm", "equipment"].includes(e), s = (e) => e === "door" || e === "window" || e === "gate", i = (e) => o(e) || e === "elevator" || e === "stairs";
|
|
26
|
+
export {
|
|
27
|
+
r as AMBIENCE_PRESETS,
|
|
28
|
+
a as OBJECT_KINDS,
|
|
29
|
+
o as isArea,
|
|
30
|
+
n as isDevice,
|
|
31
|
+
s as isOpening,
|
|
32
|
+
i as isSpace,
|
|
33
|
+
t as uid
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=types.js.map
|