@jgengine/core 0.11.0 → 0.13.0
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/CHANGELOG.md +52 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +60 -0
- package/dist/runtime/context/scene.js +329 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +7 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +79 -0
- package/dist/scene/colliders.js +170 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +19 -15
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-driven modular build-piece snap system: a catalog of piece types, each declaring named
|
|
3
|
+
* sockets (local position + outward facing + a generic "kind"), plus a connection-rule table saying
|
|
4
|
+
* which socket kinds may mate. Given a placed piece and a candidate type it resolves every valid
|
|
5
|
+
* snap — the world transform that lands the candidate's socket exactly on the placed piece's socket,
|
|
6
|
+
* facing back into it. Genre-agnostic: foundation/wall/ceiling/pillar/edge snapping falls out of the
|
|
7
|
+
* catalog data, with no game-specific piece names baked in. Pure and serializable; composes with
|
|
8
|
+
* `world/footprintGrid` occupancy (via an injected `isFree` predicate — never imported) and with
|
|
9
|
+
* `scene/modelSockets` named attach points (via {@link pieceSocketsFromModel}).
|
|
10
|
+
*/
|
|
11
|
+
/** Length-prefixed composite key (avoids delimiter collisions without any control characters). */
|
|
12
|
+
function pairKey(a, b) {
|
|
13
|
+
return `${a.length}:${a}:${b}`;
|
|
14
|
+
}
|
|
15
|
+
/** Rotate a local offset about +Y by `yaw` radians (affects X/Z, leaves Y). */
|
|
16
|
+
function rotateY(v, yaw) {
|
|
17
|
+
if (yaw === 0)
|
|
18
|
+
return [v[0], v[1], v[2]];
|
|
19
|
+
const cos = Math.cos(yaw);
|
|
20
|
+
const sin = Math.sin(yaw);
|
|
21
|
+
return [v[0] * cos - v[2] * sin, v[1], v[0] * sin + v[2] * cos];
|
|
22
|
+
}
|
|
23
|
+
function cloneSocket(socket) {
|
|
24
|
+
return {
|
|
25
|
+
name: socket.name,
|
|
26
|
+
kind: socket.kind,
|
|
27
|
+
position: [socket.position[0], socket.position[1], socket.position[2]],
|
|
28
|
+
...(socket.yaw === undefined ? {} : { yaw: socket.yaw }),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function clonePiece(piece) {
|
|
32
|
+
return {
|
|
33
|
+
type: piece.type,
|
|
34
|
+
sockets: piece.sockets.map(cloneSocket),
|
|
35
|
+
...(piece.footprint === undefined ? {} : { footprint: { w: piece.footprint.w, d: piece.footprint.d } }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The grid cells a piece `footprint` centered on `position` (world units) covers, honoring `yaw`
|
|
40
|
+
* (snapped to the nearest quarter turn). Mirrors `world/footprintGrid`'s `cellsFor` so a game can
|
|
41
|
+
* feed the result straight into `grid.isFree`/`grid.reserve` after committing a resolved snap —
|
|
42
|
+
* without this module importing the grid.
|
|
43
|
+
*
|
|
44
|
+
* @capability build-footprint-cells grid cells a snapped build piece footprint covers (footprintGrid occupancy bridge)
|
|
45
|
+
*/
|
|
46
|
+
export function footprintCells(position, footprint, yaw = 0, cellSize = 1) {
|
|
47
|
+
const quarterTurns = Math.round(yaw / (Math.PI / 2));
|
|
48
|
+
const turned = (((quarterTurns % 2) + 2) % 2) === 1;
|
|
49
|
+
const cols = Math.max(1, Math.round((turned ? footprint.d : footprint.w) / cellSize));
|
|
50
|
+
const rows = Math.max(1, Math.round((turned ? footprint.w : footprint.d) / cellSize));
|
|
51
|
+
const originCol = Math.round(position[0] / cellSize - cols / 2);
|
|
52
|
+
const originRow = Math.round(position[2] / cellSize - rows / 2);
|
|
53
|
+
const cells = [];
|
|
54
|
+
for (let row = 0; row < rows; row += 1) {
|
|
55
|
+
for (let col = 0; col < cols; col += 1)
|
|
56
|
+
cells.push({ col: originCol + col, row: originRow + row });
|
|
57
|
+
}
|
|
58
|
+
return cells;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Turn a model's named attach points (from `scene/modelSockets`' `readNamedSockets`) into
|
|
62
|
+
* {@link SocketDef}s, classifying each into a connection `kind` via `kindOf` and, optionally, an
|
|
63
|
+
* outward `yaw` via `yawOf`. Lets an artist author snap sockets as empties in a GLB instead of by
|
|
64
|
+
* hand in the catalog. Pure and structural — no three.js or scene import.
|
|
65
|
+
*
|
|
66
|
+
* @capability piece-sockets-from-model derive build-piece snap sockets from a model's named attach points
|
|
67
|
+
*/
|
|
68
|
+
export function pieceSocketsFromModel(sockets, kindOf, yawOf) {
|
|
69
|
+
return sockets.map((socket) => ({
|
|
70
|
+
name: socket.name,
|
|
71
|
+
kind: kindOf(socket.name),
|
|
72
|
+
position: [socket.offset[0], socket.offset[1], socket.offset[2]],
|
|
73
|
+
...(yawOf === undefined ? {} : { yaw: yawOf(socket.name) }),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build a modular snap catalog from piece definitions and a connection-rule table. Resolves the set
|
|
78
|
+
* of valid snap transforms when hovering a candidate piece near a placed one: for every compatible
|
|
79
|
+
* socket pair it computes the world transform that seats the candidate's socket onto the placed
|
|
80
|
+
* socket, oriented to face back into it. Deterministic ordering (nearest a cursor when given, else
|
|
81
|
+
* declaration order) and an optional occupancy predicate keep it drop-in for a build-mode controller.
|
|
82
|
+
*
|
|
83
|
+
* @capability build-socket-catalog data-driven named-socket snap catalog for modular base building
|
|
84
|
+
*/
|
|
85
|
+
export function createBuildSocketCatalog(config) {
|
|
86
|
+
const cellSize = config.cellSize ?? 1;
|
|
87
|
+
const pieces = new Map();
|
|
88
|
+
const order = [];
|
|
89
|
+
for (const piece of config.pieces) {
|
|
90
|
+
const clone = clonePiece(piece);
|
|
91
|
+
pieces.set(clone.type, clone);
|
|
92
|
+
order.push(clone.type);
|
|
93
|
+
}
|
|
94
|
+
const compatible = new Set();
|
|
95
|
+
const rules = [];
|
|
96
|
+
for (const rule of config.rules) {
|
|
97
|
+
compatible.add(pairKey(rule.a, rule.b));
|
|
98
|
+
if (rule.mutual !== false)
|
|
99
|
+
compatible.add(pairKey(rule.b, rule.a));
|
|
100
|
+
rules.push({ a: rule.a, b: rule.b, ...(rule.mutual === undefined ? {} : { mutual: rule.mutual }) });
|
|
101
|
+
}
|
|
102
|
+
const canConnect = (candidateKind, placedKind) => compatible.has(pairKey(candidateKind, placedKind));
|
|
103
|
+
const worldSockets = (placed) => {
|
|
104
|
+
const def = pieces.get(placed.type);
|
|
105
|
+
if (def === undefined)
|
|
106
|
+
return [];
|
|
107
|
+
const baseYaw = placed.transform.yaw ?? 0;
|
|
108
|
+
const origin = placed.transform.position;
|
|
109
|
+
return def.sockets.map((socket) => {
|
|
110
|
+
const rotated = rotateY(socket.position, baseYaw);
|
|
111
|
+
return {
|
|
112
|
+
name: socket.name,
|
|
113
|
+
kind: socket.kind,
|
|
114
|
+
position: [origin[0] + rotated[0], origin[1] + rotated[1], origin[2] + rotated[2]],
|
|
115
|
+
yaw: baseYaw + (socket.yaw ?? 0),
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
cellSize,
|
|
121
|
+
has(type) {
|
|
122
|
+
return pieces.has(type);
|
|
123
|
+
},
|
|
124
|
+
piece(type) {
|
|
125
|
+
const def = pieces.get(type);
|
|
126
|
+
return def === undefined ? null : clonePiece(def);
|
|
127
|
+
},
|
|
128
|
+
types() {
|
|
129
|
+
return order.slice();
|
|
130
|
+
},
|
|
131
|
+
canConnect,
|
|
132
|
+
worldSockets,
|
|
133
|
+
resolveSnaps(placed, candidateType, options = {}) {
|
|
134
|
+
const candidateDef = pieces.get(candidateType);
|
|
135
|
+
if (candidateDef === undefined)
|
|
136
|
+
return [];
|
|
137
|
+
const targets = worldSockets(placed);
|
|
138
|
+
const cursor = options.cursor;
|
|
139
|
+
const isFree = options.isFree;
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const target of targets) {
|
|
142
|
+
for (const socket of candidateDef.sockets) {
|
|
143
|
+
if (!canConnect(socket.kind, target.kind))
|
|
144
|
+
continue;
|
|
145
|
+
// Seat the candidate socket onto the target: same world point, facing opposite.
|
|
146
|
+
const candidateYaw = target.yaw + Math.PI - (socket.yaw ?? 0);
|
|
147
|
+
const rotated = rotateY(socket.position, candidateYaw);
|
|
148
|
+
const positionX = target.position[0] - rotated[0];
|
|
149
|
+
const positionY = target.position[1] - rotated[1];
|
|
150
|
+
const positionZ = target.position[2] - rotated[2];
|
|
151
|
+
const position = [positionX, positionY, positionZ];
|
|
152
|
+
if (isFree !== undefined && candidateDef.footprint !== undefined) {
|
|
153
|
+
const cells = footprintCells(position, candidateDef.footprint, candidateYaw, cellSize);
|
|
154
|
+
if (!cells.every((cell) => isFree(cell)))
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const distance = cursor === undefined
|
|
158
|
+
? 0
|
|
159
|
+
: Math.hypot(target.position[0] - cursor[0], target.position[1] - cursor[1], target.position[2] - cursor[2]);
|
|
160
|
+
out.push({
|
|
161
|
+
candidateType,
|
|
162
|
+
targetSocket: target.name,
|
|
163
|
+
candidateSocket: socket.name,
|
|
164
|
+
transform: { position, yaw: candidateYaw },
|
|
165
|
+
point: [target.position[0], target.position[1], target.position[2]],
|
|
166
|
+
distance,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Nearest-first when a cursor is given; Array.sort is stable so equal distances keep
|
|
171
|
+
// declaration order. Without a cursor the array is already in declaration order.
|
|
172
|
+
if (cursor !== undefined)
|
|
173
|
+
out.sort((x, y) => x.distance - y.distance);
|
|
174
|
+
return out;
|
|
175
|
+
},
|
|
176
|
+
toJSON() {
|
|
177
|
+
return {
|
|
178
|
+
cellSize,
|
|
179
|
+
pieces: order.map((type) => clonePiece(pieces.get(type))),
|
|
180
|
+
rules: rules.map((rule) => ({
|
|
181
|
+
a: rule.a,
|
|
182
|
+
b: rule.b,
|
|
183
|
+
...(rule.mutual === undefined ? {} : { mutual: rule.mutual }),
|
|
184
|
+
})),
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Street-aware building placement: derive building LOTS along ROAD FRONTAGE. Given road segments
|
|
3
|
+
* (centerline + width) and a district area, walk each road by arc length and emit lots on both
|
|
4
|
+
* sides at a consistent setback — a curb + sidewalk strip — with each lot carrying the yaw that
|
|
5
|
+
* makes the building FRONT face its road. Unlike `cityBlocks` (which needs a CLOSED street network
|
|
6
|
+
* to extract planar block faces), this works on a sparse set of segments — the common authoring
|
|
7
|
+
* case — so buildings line the streets instead of scattering on plinths around a center point.
|
|
8
|
+
*
|
|
9
|
+
* Pure seeded math over `cityGeometry` `Vec2` primitives: no rendering, deterministic from the
|
|
10
|
+
* inputs, bounded by `maxLots`.
|
|
11
|
+
*
|
|
12
|
+
* @capability world-environment lots aligned to and facing road frontage, with a setback sidewalk strip
|
|
13
|
+
*/
|
|
14
|
+
import type { Vec2 } from "./cityGeometry.js";
|
|
15
|
+
import type { WorldBounds } from "./features.js";
|
|
16
|
+
/** One road the frontage placer lines with buildings: a centerline polyline and its full width. */
|
|
17
|
+
export interface RoadFrontage {
|
|
18
|
+
/** Road centerline vertices in world XZ; at least two points. */
|
|
19
|
+
path: readonly Vec2[];
|
|
20
|
+
/** Road full width in world units; lots set back from `width / 2`. */
|
|
21
|
+
width: number;
|
|
22
|
+
}
|
|
23
|
+
/** Rectangular clip region lots must fall within (world center + half-extents). */
|
|
24
|
+
export interface LotArea {
|
|
25
|
+
/** World-space center of the region; default `[0, 0]`. */
|
|
26
|
+
center?: Vec2;
|
|
27
|
+
/** Half-extents along world x and z. */
|
|
28
|
+
halfExtents: Vec2;
|
|
29
|
+
}
|
|
30
|
+
/** Options for {@link deriveBuildingLots}. */
|
|
31
|
+
export interface BuildingLotOptions {
|
|
32
|
+
/** Roads whose frontage is lined with lots. */
|
|
33
|
+
roads: readonly RoadFrontage[];
|
|
34
|
+
/** Lot footprint: `w` is frontage width (along the road), `d` is depth (into the block). Default `{ w: 12, d: 10 }`. */
|
|
35
|
+
footprint?: WorldBounds;
|
|
36
|
+
/** Gap between neighbouring lots along a frontage, world units. Default 2. */
|
|
37
|
+
spacing?: number;
|
|
38
|
+
/** Sidewalk strip between the curb (`width / 2`) and the building front, world units. Default 3. */
|
|
39
|
+
setback?: number;
|
|
40
|
+
/** Place lots on both sides of each road. Default true. */
|
|
41
|
+
bothSides?: boolean;
|
|
42
|
+
/** Clip region; lots whose center falls outside are dropped. Omit to keep every lot. */
|
|
43
|
+
area?: LotArea;
|
|
44
|
+
/**
|
|
45
|
+
* Seed forwarded by callers for parity with other feature configs. Lot GEOMETRY is fully
|
|
46
|
+
* determined by the roads and dials (so a layout is identical without it); per-building variation
|
|
47
|
+
* (floors, massing) is seeded downstream where the lots become buildings.
|
|
48
|
+
*/
|
|
49
|
+
seed?: string;
|
|
50
|
+
/** Hard cap on emitted lots so a long network stays bounded. Default 400. */
|
|
51
|
+
maxLots?: number;
|
|
52
|
+
}
|
|
53
|
+
/** One placed building lot: where a building stands and how it is turned to face its road. */
|
|
54
|
+
export interface PlacedBuildingLot {
|
|
55
|
+
/** Lot (building footprint) center in world XZ. */
|
|
56
|
+
center: Vec2;
|
|
57
|
+
/** Building yaw (radians) so its FRONT (+z local face) points at the road. */
|
|
58
|
+
rotationY: number;
|
|
59
|
+
/** Footprint of the lot: `w` along the frontage, `d` into the block. */
|
|
60
|
+
footprint: WorldBounds;
|
|
61
|
+
/** Index into the input `roads`. */
|
|
62
|
+
road: number;
|
|
63
|
+
/** Which side of the road: +1 = left normal, -1 = right normal. */
|
|
64
|
+
side: 1 | -1;
|
|
65
|
+
/** Distance from the lot's front face to the road centerline (= `width / 2 + setback`). */
|
|
66
|
+
frontDistance: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Yaw that turns the building FRONT (local +z) to face the road. The lot center sits at
|
|
70
|
+
* `roadPoint + outwardNormal * d`, so the road lies along `-outwardNormal` from the center. In the
|
|
71
|
+
* engine's Y-rotation convention a local +z of `(0, 1)` maps to world `(sin y, cos y)`; setting
|
|
72
|
+
* that equal to `-outwardNormal` gives `y = atan2(-nx, -nz)`.
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
export declare function facingRotation(outwardNormal: Vec2): number;
|
|
76
|
+
/**
|
|
77
|
+
* Derive street-aligned building lots from road frontage. Lots are stepped along each road at
|
|
78
|
+
* `footprint.w + spacing`, offset to each side by `width / 2 + setback + footprint.d / 2`, and
|
|
79
|
+
* turned to face the road. Cross-road overlaps are rejected by a bounded nearest-neighbour test, so
|
|
80
|
+
* two roads meeting at a corner don't stack buildings. Deterministic: identical inputs (including
|
|
81
|
+
* seed) always yield the identical list.
|
|
82
|
+
* @capability building-lots derive street-facing building lots from road frontage
|
|
83
|
+
*/
|
|
84
|
+
export declare function deriveBuildingLots(options: BuildingLotOptions): PlacedBuildingLot[];
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const DEFAULT_FOOTPRINT = { w: 12, d: 10 };
|
|
2
|
+
/** Cumulative arc length of a polyline. */
|
|
3
|
+
function polylineLength(path) {
|
|
4
|
+
let sum = 0;
|
|
5
|
+
for (let i = 0; i + 1 < path.length; i += 1) {
|
|
6
|
+
sum += Math.hypot(path[i + 1][0] - path[i][0], path[i + 1][1] - path[i][1]);
|
|
7
|
+
}
|
|
8
|
+
return sum;
|
|
9
|
+
}
|
|
10
|
+
/** Point + unit tangent at arc length `s` along a polyline (clamped to its ends). */
|
|
11
|
+
function stationAt(path, s) {
|
|
12
|
+
let acc = 0;
|
|
13
|
+
for (let i = 0; i + 1 < path.length; i += 1) {
|
|
14
|
+
const a = path[i];
|
|
15
|
+
const b = path[i + 1];
|
|
16
|
+
const len = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
17
|
+
if (len < 1e-9)
|
|
18
|
+
continue;
|
|
19
|
+
if (acc + len >= s || i + 2 === path.length) {
|
|
20
|
+
const t = Math.max(0, Math.min(1, (s - acc) / len));
|
|
21
|
+
return {
|
|
22
|
+
p: [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
|
|
23
|
+
tangent: [(b[0] - a[0]) / len, (b[1] - a[1]) / len],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
acc += len;
|
|
27
|
+
}
|
|
28
|
+
const a = path[0];
|
|
29
|
+
return { p: [a[0], a[1]], tangent: [1, 0] };
|
|
30
|
+
}
|
|
31
|
+
function inArea(area, x, z) {
|
|
32
|
+
if (area === undefined)
|
|
33
|
+
return true;
|
|
34
|
+
const [cx, cz] = area.center ?? [0, 0];
|
|
35
|
+
return Math.abs(x - cx) <= area.halfExtents[0] && Math.abs(z - cz) <= area.halfExtents[1];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Yaw that turns the building FRONT (local +z) to face the road. The lot center sits at
|
|
39
|
+
* `roadPoint + outwardNormal * d`, so the road lies along `-outwardNormal` from the center. In the
|
|
40
|
+
* engine's Y-rotation convention a local +z of `(0, 1)` maps to world `(sin y, cos y)`; setting
|
|
41
|
+
* that equal to `-outwardNormal` gives `y = atan2(-nx, -nz)`.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function facingRotation(outwardNormal) {
|
|
45
|
+
return Math.atan2(-outwardNormal[0], -outwardNormal[1]);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Derive street-aligned building lots from road frontage. Lots are stepped along each road at
|
|
49
|
+
* `footprint.w + spacing`, offset to each side by `width / 2 + setback + footprint.d / 2`, and
|
|
50
|
+
* turned to face the road. Cross-road overlaps are rejected by a bounded nearest-neighbour test, so
|
|
51
|
+
* two roads meeting at a corner don't stack buildings. Deterministic: identical inputs (including
|
|
52
|
+
* seed) always yield the identical list.
|
|
53
|
+
* @capability building-lots derive street-facing building lots from road frontage
|
|
54
|
+
*/
|
|
55
|
+
export function deriveBuildingLots(options) {
|
|
56
|
+
const footprint = options.footprint ?? DEFAULT_FOOTPRINT;
|
|
57
|
+
const spacing = Math.max(0, options.spacing ?? 2);
|
|
58
|
+
const setback = Math.max(0, options.setback ?? 3);
|
|
59
|
+
const bothSides = options.bothSides ?? true;
|
|
60
|
+
const maxLots = Math.max(0, Math.floor(options.maxLots ?? 400));
|
|
61
|
+
const step = Math.max(1, footprint.w + spacing);
|
|
62
|
+
const sides = bothSides ? [1, -1] : [1];
|
|
63
|
+
// Minimum center separation for cross-road de-dup: half the footprint diagonal, a touch under
|
|
64
|
+
// the along-road step so same-road neighbours are never rejected.
|
|
65
|
+
const minSep = Math.min(step * 0.95, Math.hypot(footprint.w, footprint.d) * 0.5);
|
|
66
|
+
const minSep2 = minSep * minSep;
|
|
67
|
+
const lots = [];
|
|
68
|
+
const accepted = [];
|
|
69
|
+
for (let r = 0; r < options.roads.length && lots.length < maxLots; r += 1) {
|
|
70
|
+
const road = options.roads[r];
|
|
71
|
+
if (road.path.length < 2)
|
|
72
|
+
continue;
|
|
73
|
+
const total = polylineLength(road.path);
|
|
74
|
+
if (total < 1e-6)
|
|
75
|
+
continue;
|
|
76
|
+
const front = road.width / 2 + setback;
|
|
77
|
+
const offset = front + footprint.d / 2;
|
|
78
|
+
// Center the run so lots sit symmetrically and endpoints keep a half-step margin.
|
|
79
|
+
const count = Math.max(1, Math.floor(total / step));
|
|
80
|
+
const startS = (total - (count - 1) * step) / 2;
|
|
81
|
+
for (let i = 0; i < count && lots.length < maxLots; i += 1) {
|
|
82
|
+
const s = startS + i * step;
|
|
83
|
+
const station = stationAt(road.path, s);
|
|
84
|
+
const [tx, tz] = station.tangent;
|
|
85
|
+
// Left normal of the tangent (CCW): (-tz, tx). Right side negates it.
|
|
86
|
+
for (const side of sides) {
|
|
87
|
+
if (lots.length >= maxLots)
|
|
88
|
+
break;
|
|
89
|
+
const nx = -tz * side;
|
|
90
|
+
const nz = tx * side;
|
|
91
|
+
const cx = station.p[0] + nx * offset;
|
|
92
|
+
const cz = station.p[1] + nz * offset;
|
|
93
|
+
if (!inArea(options.area, cx, cz))
|
|
94
|
+
continue;
|
|
95
|
+
let clash = false;
|
|
96
|
+
for (const [ax, az] of accepted) {
|
|
97
|
+
const dx = ax - cx;
|
|
98
|
+
const dz = az - cz;
|
|
99
|
+
if (dx * dx + dz * dz < minSep2) {
|
|
100
|
+
clash = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (clash)
|
|
105
|
+
continue;
|
|
106
|
+
lots.push({
|
|
107
|
+
center: [cx, cz],
|
|
108
|
+
rotationY: facingRotation([nx, nz]),
|
|
109
|
+
footprint: { w: footprint.w, d: footprint.d },
|
|
110
|
+
road: r,
|
|
111
|
+
side,
|
|
112
|
+
frontDistance: front,
|
|
113
|
+
});
|
|
114
|
+
accepted.push([cx, cz]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return lots;
|
|
119
|
+
}
|
|
@@ -72,6 +72,12 @@ export interface GeneratedBuilding {
|
|
|
72
72
|
floorHeight: number;
|
|
73
73
|
bounds: Aabb;
|
|
74
74
|
parts: readonly BuildingPartPlacement[];
|
|
75
|
+
/**
|
|
76
|
+
* Optional building yaw (radians) about `center`, applied by the renderer to the whole massing —
|
|
77
|
+
* used by street-aware placement to turn a building's front to face its road. Absent/0 keeps the
|
|
78
|
+
* axis-aligned grid orientation.
|
|
79
|
+
*/
|
|
80
|
+
rotationY?: number;
|
|
75
81
|
}
|
|
76
82
|
export interface BuildingLot {
|
|
77
83
|
id: string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block-first city fabric for the `city` studio: turns the synthesized street network into a
|
|
3
|
+
* planar graph, extracts its faces as CLOSED BLOCK POLYGONS (a curved street yields a curved
|
|
4
|
+
* block edge, a diagonal one a diagonal edge), insets each block by its bordering roads'
|
|
5
|
+
* half-widths into curb and land rings (the sidewalk band lives between them), classifies every
|
|
6
|
+
* block (buildable, park, plaza, field, buffer sliver), subdivides buildable block frontage into
|
|
7
|
+
* POLYGONAL PARCELS with street references and corner status, and computes per-parcel BUILDABLE
|
|
8
|
+
* polygons after setbacks. Buildings then derive from parcel geometry instead of nearest-road
|
|
9
|
+
* guesses. Pure seeded math over `cityGeometry` primitives — no rendering, bounded work.
|
|
10
|
+
*
|
|
11
|
+
* @capability city-district road-derived block polygons, parcel subdivision, buildable footprints
|
|
12
|
+
*/
|
|
13
|
+
import { type Vec2 } from "./cityGeometry.js";
|
|
14
|
+
/** How a block (a face of the road graph) is used. */
|
|
15
|
+
export type CityBlockKind = "buildable" | "park" | "plaza" | "field" | "buffer";
|
|
16
|
+
/** One closed block: a face of the road network, inset to its curb and land boundaries. */
|
|
17
|
+
export interface CityBlockLocal {
|
|
18
|
+
id: string;
|
|
19
|
+
/** Face polygon on road CENTERLINES (CCW) — the raw graph face. */
|
|
20
|
+
face: Vec2[];
|
|
21
|
+
/** Street index bordering each face edge (edge i = face[i]→face[i+1]); -1 = district boundary. */
|
|
22
|
+
faceStreets: number[];
|
|
23
|
+
/** Pavement edge: face inset by each bordering road's half width (+ curb margin). */
|
|
24
|
+
curb: Vec2[];
|
|
25
|
+
/** Land edge: curb inset further by the sidewalk band where the bordering street has one. */
|
|
26
|
+
polygon: Vec2[];
|
|
27
|
+
kind: CityBlockKind;
|
|
28
|
+
area: number;
|
|
29
|
+
}
|
|
30
|
+
/** One street-frontage reference of a parcel. */
|
|
31
|
+
export interface CityParcelFrontage {
|
|
32
|
+
/** Index into the resolved city's street list. */
|
|
33
|
+
street: number;
|
|
34
|
+
/** Frontage chord endpoints on the parcel boundary. */
|
|
35
|
+
edgeStart: Vec2;
|
|
36
|
+
edgeEnd: Vec2;
|
|
37
|
+
/** Unit tangent along the frontage chord. */
|
|
38
|
+
tangent: Vec2;
|
|
39
|
+
}
|
|
40
|
+
/** How a parcel is used after occupancy/fitting rolls. */
|
|
41
|
+
export type CityParcelKind = "built" | "vacant" | "yard";
|
|
42
|
+
/** One polygonal parcel subdivided out of a block's street frontage. */
|
|
43
|
+
export interface CityParcelLocal {
|
|
44
|
+
id: string;
|
|
45
|
+
block: number;
|
|
46
|
+
polygon: Vec2[];
|
|
47
|
+
/** Buildable polygon after front/side/rear setbacks; [] when setbacks consume the parcel. */
|
|
48
|
+
buildable: Vec2[];
|
|
49
|
+
frontage: CityParcelFrontage[];
|
|
50
|
+
area: number;
|
|
51
|
+
/** Usable depth from the frontage chord to the parcel rear. */
|
|
52
|
+
depth: number;
|
|
53
|
+
isCorner: boolean;
|
|
54
|
+
kind: CityParcelKind;
|
|
55
|
+
}
|
|
56
|
+
/** Minimal street shape the fabric needs (matches cityKind's LocalStreet). */
|
|
57
|
+
export interface FabricStreet {
|
|
58
|
+
points: readonly (readonly [number, number])[];
|
|
59
|
+
width: number;
|
|
60
|
+
level: "boulevard" | "avenue" | "street" | "lane";
|
|
61
|
+
sidewalk: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Sidewalk band width by street hierarchy (base = the district's street-level width). */
|
|
64
|
+
export declare function sidewalkWidthFor(level: FabricStreet["level"], base: number): number;
|
|
65
|
+
/** One extracted face: closed centerline polygon + the street index along each polygon edge. */
|
|
66
|
+
export interface GraphFace {
|
|
67
|
+
polygon: Vec2[];
|
|
68
|
+
streets: number[];
|
|
69
|
+
}
|
|
70
|
+
/** Everything block extraction needs from the district rules. */
|
|
71
|
+
export interface FabricParams {
|
|
72
|
+
streetWidthBase: number;
|
|
73
|
+
/** Base sidewalk band width in meters at "street" hierarchy level. */
|
|
74
|
+
sidewalkBase: number;
|
|
75
|
+
/** Curb strip between pavement edge and sidewalk, meters. */
|
|
76
|
+
curbMargin: number;
|
|
77
|
+
}
|
|
78
|
+
/** A block's derived rings plus the sidewalk band (outer = curb line, inner = land line). */
|
|
79
|
+
export interface BlockRings {
|
|
80
|
+
face: Vec2[];
|
|
81
|
+
faceStreets: number[];
|
|
82
|
+
curb: Vec2[];
|
|
83
|
+
land: Vec2[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extract closed blocks from the street network: planar faces of the graph, each inset to its
|
|
87
|
+
* curb ring (pavement edge) and land ring (behind the sidewalk band). Faces that collapse under
|
|
88
|
+
* the inset come back with an empty `land` and are the caller's slivers/buffers.
|
|
89
|
+
*/
|
|
90
|
+
export declare function extractBlocks(streets: readonly FabricStreet[], hx: number, hz: number, params: FabricParams): {
|
|
91
|
+
blocks: BlockRings[];
|
|
92
|
+
deadEnds: {
|
|
93
|
+
pts: Vec2[];
|
|
94
|
+
width: number;
|
|
95
|
+
}[];
|
|
96
|
+
};
|
|
97
|
+
/** Arc-length station lookup along a closed ring. */
|
|
98
|
+
export declare class RingWalker {
|
|
99
|
+
readonly ring: readonly Vec2[];
|
|
100
|
+
readonly cum: number[];
|
|
101
|
+
readonly total: number;
|
|
102
|
+
constructor(ring: readonly Vec2[]);
|
|
103
|
+
/** Point, unit tangent, and inward normal (CCW ring: interior on the left) at arc length `s`. */
|
|
104
|
+
at(s: number): {
|
|
105
|
+
p: Vec2;
|
|
106
|
+
tangent: Vec2;
|
|
107
|
+
normal: Vec2;
|
|
108
|
+
edge: number;
|
|
109
|
+
};
|
|
110
|
+
/** Ring vertex indices whose arc position lies strictly inside (s0, s1) (wrapping). */
|
|
111
|
+
verticesBetween(s0: number, s1: number): number[];
|
|
112
|
+
/** Interior angle (radians) at ring vertex i — π on straight runs, < π at convex corners. */
|
|
113
|
+
interiorAngle(i: number): number;
|
|
114
|
+
}
|
|
115
|
+
/** Inputs for one parcel strip cut from a block ring. */
|
|
116
|
+
export interface ParcelCut {
|
|
117
|
+
/** Arc-length start/end stations along the block's land ring. */
|
|
118
|
+
s0: number;
|
|
119
|
+
s1: number;
|
|
120
|
+
/** Strip depth from the frontage inward. */
|
|
121
|
+
depth: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Cut one polygonal parcel out of a block land ring: the frontage arc between two stations,
|
|
125
|
+
* extruded inward along the station normals, clipped back to the block. Returns null when the
|
|
126
|
+
* strip collapses (over-tight corner, sliver arc).
|
|
127
|
+
*/
|
|
128
|
+
export declare function cutParcel(walker: RingWalker, blockRing: readonly Vec2[], cut: ParcelCut): Vec2[] | null;
|
|
129
|
+
/**
|
|
130
|
+
* Conform a polygon to a containing ring: sample every edge (a straight edge can bulge across a
|
|
131
|
+
* concave notch even when its endpoints hug the boundary), then pull each escaping point to its
|
|
132
|
+
* nearest boundary point. Callers re-run this after any operation that adds mid-edge vertices.
|
|
133
|
+
*/
|
|
134
|
+
export declare function conformPolygonToRing(poly: readonly Vec2[], ring: readonly Vec2[]): Vec2[];
|
|
135
|
+
/**
|
|
136
|
+
* Buildable polygon for a parcel: the parcel clipped by the front setback (a half-plane pushed in
|
|
137
|
+
* from the frontage chord), side gaps along both cut lines, and a rear margin. Works on any
|
|
138
|
+
* parcel polygon — no vertex correspondence needed.
|
|
139
|
+
*/
|
|
140
|
+
export declare function buildablePolygon(parcel: readonly Vec2[], frontA: Vec2, frontB: Vec2, frontSetback: number, sideGap: number, rearMargin: number, depth: number): Vec2[];
|
|
141
|
+
/**
|
|
142
|
+
* Carve dead-end road corridors (cul-de-sac lanes pruned from the face graph) out of a polygon:
|
|
143
|
+
* for every corridor segment crossing it, keep the polygon piece on the side where `keep` lies.
|
|
144
|
+
*/
|
|
145
|
+
export declare function carveCorridors(poly: Vec2[], keep: Vec2, corridors: readonly {
|
|
146
|
+
pts: readonly Vec2[];
|
|
147
|
+
width: number;
|
|
148
|
+
}[], margin: number): Vec2[];
|
|
149
|
+
/** Deterministic sliver verdict shared by classification and tests. */
|
|
150
|
+
export declare function isSliverBlock(land: readonly Vec2[], minArea: number, minWidth: number): boolean;
|
|
151
|
+
/** Sample deterministic points inside a polygon by seeded rejection over its bounds. */
|
|
152
|
+
export declare function pointsInPolygon(ring: readonly Vec2[], count: number, rng: () => number, margin?: number): Vec2[];
|