@jgengine/core 0.12.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 +26 -0
- 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 +4 -2
- package/dist/combat.js +3 -1
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +2 -2
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +38 -5
- package/dist/game/progression.d.ts +2 -2
- package/dist/game/progression.js +4 -2
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +25 -1
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/scene.d.ts +2 -1
- package/dist/runtime/context/scene.js +39 -1
- package/dist/runtime/gameContext.js +50 -4
- package/dist/runtime/gameContextTypes.d.ts +37 -4
- package/dist/runtime/headlessRunner.d.ts +13 -0
- package/dist/runtime/headlessRunner.js +14 -0
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/colliders.d.ts +21 -0
- package/dist/scene/colliders.js +62 -0
- package/dist/scene/entityStats.js +9 -1
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.js +5 -2
- 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/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityKind.d.ts +2 -2
- package/dist/world/cityKind.js +4 -4
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +35 -2
- package/dist/world/features.js +5 -2
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/{pathNetwork.d.ts → streetGenerator.d.ts} +37 -37
- package/dist/world/{pathNetwork.js → streetGenerator.js} +9 -9
- package/dist/world/streets.d.ts +74 -1
- package/dist/world/streets.js +218 -0
- package/dist/world/terrain.d.ts +20 -1
- package/dist/world/terrain.js +41 -2
- package/package.json +1 -1
- package/skills/jgengine/SKILL.md +1 -0
- package/skills/jgengine/api.md +13 -5
- package/skills/jgengine-combat/SKILL.md +15 -1
- package/skills/jgengine-combat/api.md +39 -2
- package/skills/jgengine-combat/capabilities.md +17 -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-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +22 -3
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +35 -5
- package/skills/jgengine-gameplay/capabilities.md +18 -0
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +9 -0
- package/skills/jgengine-world/api.md +52 -24
- package/skills/jgengine-world/capabilities.md +8 -4
- package/skills/jgengine-world/reference.md +1 -1
package/dist/world/features.d.ts
CHANGED
|
@@ -291,6 +291,34 @@ export interface SkyEnvironmentConfig {
|
|
|
291
291
|
/** Raymarched volumetric cloud layer over the sky dome. Off by default — omit for no clouds. */
|
|
292
292
|
volumetricClouds?: VolumetricCloudsConfig;
|
|
293
293
|
}
|
|
294
|
+
/** One road a `building({ along })` frontage lines with buildings: centerline + full width. */
|
|
295
|
+
export interface BuildingFrontageRoad {
|
|
296
|
+
/** Road centerline vertices in world XZ; at least two points. */
|
|
297
|
+
path: readonly EnvironmentVec2[];
|
|
298
|
+
/** Road full width in world units; lots set back from `width / 2`. */
|
|
299
|
+
width: number;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Street-aware placement for `building()`: instead of a grid scattered around `position`, lots are
|
|
303
|
+
* stepped along each road's frontage, offset by a consistent setback (a curb + sidewalk strip), and
|
|
304
|
+
* turned so every building FRONT faces its road. `footprint` (`w` = frontage width, `d` = depth) and
|
|
305
|
+
* `spacing` (along-road gap) are shared with the grid mode. Deterministic and bounded by `maxLots`.
|
|
306
|
+
*/
|
|
307
|
+
export interface BuildingFrontageConfig {
|
|
308
|
+
/** Roads whose frontage is lined with lots. */
|
|
309
|
+
roads: readonly BuildingFrontageRoad[];
|
|
310
|
+
/** Sidewalk strip between the curb (`width / 2`) and the building front, world units. Default 3. */
|
|
311
|
+
setback?: number;
|
|
312
|
+
/** Place lots on both sides of each road. Default true. */
|
|
313
|
+
bothSides?: boolean;
|
|
314
|
+
/** Clip region; lots whose center falls outside are dropped. Omit to keep every lot. */
|
|
315
|
+
area?: {
|
|
316
|
+
center?: EnvironmentVec2;
|
|
317
|
+
halfExtents: EnvironmentVec2;
|
|
318
|
+
};
|
|
319
|
+
/** Hard cap on emitted lots so a long network stays bounded. Default 400. */
|
|
320
|
+
maxLots?: number;
|
|
321
|
+
}
|
|
294
322
|
export interface BuildingEnvironmentConfig {
|
|
295
323
|
count?: number;
|
|
296
324
|
position?: EnvironmentVec2;
|
|
@@ -303,6 +331,11 @@ export interface BuildingEnvironmentConfig {
|
|
|
303
331
|
/** Explicit per-part-kind hex colors; any part left unset falls back to the resolved `style` palette. */
|
|
304
332
|
palette?: BuildingPaletteOverrides;
|
|
305
333
|
seed?: string;
|
|
334
|
+
/**
|
|
335
|
+
* Street-aware placement: derive lots along the given roads' frontage (facing each street, set
|
|
336
|
+
* back by a sidewalk strip) instead of the grid around `position`. See {@link BuildingFrontageConfig}.
|
|
337
|
+
*/
|
|
338
|
+
along?: BuildingFrontageConfig;
|
|
306
339
|
}
|
|
307
340
|
export type PadSize = readonly [number, number] | {
|
|
308
341
|
radius: number;
|
|
@@ -344,7 +377,7 @@ export type OceanEnvironmentDescriptor = {
|
|
|
344
377
|
} & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
|
|
345
378
|
export type BuildingEnvironmentDescriptor = {
|
|
346
379
|
kind: "building";
|
|
347
|
-
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette">;
|
|
380
|
+
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along">;
|
|
348
381
|
export type PadEnvironmentDescriptor = {
|
|
349
382
|
kind: "pad";
|
|
350
383
|
} & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY" | "elevation">;
|
|
@@ -509,7 +542,7 @@ export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironment
|
|
|
509
542
|
/** Declare a road ribbon for an `environment()` world; the shell drapes and renders it over the terrain. */
|
|
510
543
|
export declare function road(config: RoadEnvironmentConfig): RoadEnvironmentDescriptor;
|
|
511
544
|
export declare function island(config: TerrainIslandConfig): TerrainIslandDescriptor;
|
|
512
|
-
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
|
|
545
|
+
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`. */
|
|
513
546
|
export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
|
|
514
547
|
export declare function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor;
|
|
515
548
|
/** Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain. */
|
package/dist/world/features.js
CHANGED
|
@@ -228,11 +228,13 @@ export function island(config) {
|
|
|
228
228
|
const { kind: _kind, ...descriptor } = terrain(terrainConfig);
|
|
229
229
|
return { ...descriptor, kind: "island", origin };
|
|
230
230
|
}
|
|
231
|
-
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
|
|
231
|
+
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`. */
|
|
232
232
|
export function building(config = {}) {
|
|
233
233
|
return withOptional({
|
|
234
234
|
kind: "building",
|
|
235
|
-
|
|
235
|
+
// In frontage mode the road network sizes the district; `count` becomes an upper bound, so a
|
|
236
|
+
// caller that just says `along` gets a filled street rather than a single grid building.
|
|
237
|
+
count: config.count ?? (config.along === undefined ? 1 : config.along.maxLots ?? 400),
|
|
236
238
|
footprint: config.footprint ?? { w: 8, d: 8 },
|
|
237
239
|
stories: config.stories ?? [1, 4],
|
|
238
240
|
storyHeight: config.storyHeight ?? 3,
|
|
@@ -242,6 +244,7 @@ export function building(config = {}) {
|
|
|
242
244
|
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
243
245
|
...(config.position === undefined ? {} : { position: config.position }),
|
|
244
246
|
...(config.palette === undefined ? {} : { palette: config.palette }),
|
|
247
|
+
...(config.along === undefined ? {} : { along: config.along }),
|
|
245
248
|
});
|
|
246
249
|
}
|
|
247
250
|
export function pad(config) {
|
package/dist/world/roads.d.ts
CHANGED
|
@@ -29,12 +29,29 @@ export interface RoadRibbonOptions {
|
|
|
29
29
|
* into a mesh, and tests can assert on it directly.
|
|
30
30
|
*/
|
|
31
31
|
export declare function buildRoadRibbon(path: readonly RoadPoint[], width: number, sampleHeight: (x: number, z: number) => number, options?: RoadRibbonOptions): RoadRibbon;
|
|
32
|
+
/** A circular exclusion zone: dashes whose midpoint falls inside are dropped (e.g. junction patches). */
|
|
33
|
+
export interface DashExclusion {
|
|
34
|
+
center: RoadPoint;
|
|
35
|
+
radius: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a flat, ground-draped disc patch centered on `center` — the welded junction surface that
|
|
39
|
+
* covers crossing road ribbons so they read as one intersection instead of stacking/z-fighting.
|
|
40
|
+
* A triangle fan of `segments` sides (default 16), every vertex draped at `sampleHeight + elevation`.
|
|
41
|
+
* Pure geometry; the shell meshes the result with the asphalt material.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildJunctionPatch(center: RoadPoint, radius: number, sampleHeight: (x: number, z: number) => number, options?: {
|
|
44
|
+
elevation?: number;
|
|
45
|
+
segments?: number;
|
|
46
|
+
}): RoadRibbon;
|
|
32
47
|
/**
|
|
33
48
|
* Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line,
|
|
34
49
|
* `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back
|
|
35
|
-
* through {@link buildRoadRibbon} with a thin width to mesh the dashes.
|
|
50
|
+
* through {@link buildRoadRibbon} with a thin width to mesh the dashes. Pass `exclude` circles
|
|
51
|
+
* (junction patches) to interrupt the center line through intersections — any dash whose midpoint
|
|
52
|
+
* lands inside an exclusion is dropped.
|
|
36
53
|
*/
|
|
37
|
-
export declare function dashSegments(path: readonly RoadPoint[], dashLength?: number, gapLength?: number): readonly (readonly RoadPoint[])[];
|
|
54
|
+
export declare function dashSegments(path: readonly RoadPoint[], dashLength?: number, gapLength?: number, exclude?: readonly DashExclusion[]): readonly (readonly RoadPoint[])[];
|
|
38
55
|
/** Result of {@link nearestOnPath}: closest point on the centerline plus distance and tangent. */
|
|
39
56
|
export interface RoadSample {
|
|
40
57
|
/** Closest point on the centerline. */
|
package/dist/world/roads.js
CHANGED
|
@@ -102,12 +102,48 @@ export function buildRoadRibbon(path, width, sampleHeight, options = {}) {
|
|
|
102
102
|
}
|
|
103
103
|
return { positions, indices };
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a flat, ground-draped disc patch centered on `center` — the welded junction surface that
|
|
107
|
+
* covers crossing road ribbons so they read as one intersection instead of stacking/z-fighting.
|
|
108
|
+
* A triangle fan of `segments` sides (default 16), every vertex draped at `sampleHeight + elevation`.
|
|
109
|
+
* Pure geometry; the shell meshes the result with the asphalt material.
|
|
110
|
+
*/
|
|
111
|
+
export function buildJunctionPatch(center, radius, sampleHeight, options = {}) {
|
|
112
|
+
const elevation = options.elevation ?? 0.08;
|
|
113
|
+
const segments = Math.max(3, Math.floor(options.segments ?? 16));
|
|
114
|
+
if (radius <= 0)
|
|
115
|
+
return { positions: new Float32Array(0), indices: new Uint32Array(0) };
|
|
116
|
+
const [cx, cz] = center;
|
|
117
|
+
// Center vertex + one ring vertex per segment.
|
|
118
|
+
const positions = new Float32Array((segments + 1) * 3);
|
|
119
|
+
positions[0] = cx;
|
|
120
|
+
positions[1] = sampleHeight(cx, cz) + elevation;
|
|
121
|
+
positions[2] = cz;
|
|
122
|
+
for (let i = 0; i < segments; i += 1) {
|
|
123
|
+
const angle = (i / segments) * Math.PI * 2;
|
|
124
|
+
const x = cx + Math.cos(angle) * radius;
|
|
125
|
+
const z = cz + Math.sin(angle) * radius;
|
|
126
|
+
const base = (i + 1) * 3;
|
|
127
|
+
positions[base] = x;
|
|
128
|
+
positions[base + 1] = sampleHeight(x, z) + elevation;
|
|
129
|
+
positions[base + 2] = z;
|
|
130
|
+
}
|
|
131
|
+
const indices = new Uint32Array(segments * 3);
|
|
132
|
+
for (let i = 0; i < segments; i += 1) {
|
|
133
|
+
indices[i * 3] = 0;
|
|
134
|
+
indices[i * 3 + 1] = i + 1;
|
|
135
|
+
indices[i * 3 + 2] = ((i + 1) % segments) + 1;
|
|
136
|
+
}
|
|
137
|
+
return { positions, indices };
|
|
138
|
+
}
|
|
105
139
|
/**
|
|
106
140
|
* Split a centerline into dash sub-polylines for lane markings: `dashLength` of painted line,
|
|
107
141
|
* `gapLength` of asphalt, repeated along the path's arc length. Feed each returned sub-path back
|
|
108
|
-
* through {@link buildRoadRibbon} with a thin width to mesh the dashes.
|
|
142
|
+
* through {@link buildRoadRibbon} with a thin width to mesh the dashes. Pass `exclude` circles
|
|
143
|
+
* (junction patches) to interrupt the center line through intersections — any dash whose midpoint
|
|
144
|
+
* lands inside an exclusion is dropped.
|
|
109
145
|
*/
|
|
110
|
-
export function dashSegments(path, dashLength = 3, gapLength = 3) {
|
|
146
|
+
export function dashSegments(path, dashLength = 3, gapLength = 3, exclude = []) {
|
|
111
147
|
if (path.length < 2 || dashLength <= 0 || gapLength < 0)
|
|
112
148
|
return [];
|
|
113
149
|
const cumulative = [0];
|
|
@@ -136,7 +172,28 @@ export function dashSegments(path, dashLength = 3, gapLength = 3) {
|
|
|
136
172
|
const end = Math.min(total, start + dashLength);
|
|
137
173
|
if (end - start < 1e-6)
|
|
138
174
|
break;
|
|
139
|
-
|
|
175
|
+
const a = pointAt(start);
|
|
176
|
+
const b = pointAt(end);
|
|
177
|
+
if (exclude.length > 0) {
|
|
178
|
+
const mx = (a[0] + b[0]) / 2;
|
|
179
|
+
const mz = (a[1] + b[1]) / 2;
|
|
180
|
+
let excluded = false;
|
|
181
|
+
for (let e = 0; e < exclude.length; e += 1) {
|
|
182
|
+
const zone = exclude[e];
|
|
183
|
+
const dx = mx - zone.center[0];
|
|
184
|
+
const dz = mz - zone.center[1];
|
|
185
|
+
if (dx * dx + dz * dz <= zone.radius * zone.radius) {
|
|
186
|
+
excluded = true;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (excluded) {
|
|
191
|
+
if (period <= 0)
|
|
192
|
+
break;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
dashes.push([a, b]);
|
|
140
197
|
if (period <= 0)
|
|
141
198
|
break;
|
|
142
199
|
}
|
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
/** A path vertex in the volume-local XZ frame. */
|
|
2
|
-
export type
|
|
2
|
+
export type StreetVec2 = readonly [number, number];
|
|
3
3
|
/** Road hierarchy, widest to narrowest — shared by the city fabric and the renderer. */
|
|
4
|
-
export type
|
|
4
|
+
export type StreetLevel = "boulevard" | "avenue" | "street" | "lane";
|
|
5
5
|
/** A path feature spanning part of an edge/street: a bridge deck over a gap or a tunnel bore under a ridge. */
|
|
6
|
-
export type
|
|
6
|
+
export type StreetFeatureKind = "bridge" | "tunnel";
|
|
7
7
|
/** The generator's chosen topology family: an open street `net`, or a closed `circuit` loop. */
|
|
8
|
-
export type
|
|
8
|
+
export type StreetNetworkMode = "net" | "circuit";
|
|
9
9
|
/** One graph node: a junction, a dead end, or a mid-street bend, with its connection count. */
|
|
10
|
-
export interface
|
|
10
|
+
export interface StreetNode {
|
|
11
11
|
id: number;
|
|
12
12
|
x: number;
|
|
13
13
|
z: number;
|
|
14
14
|
degree: number;
|
|
15
15
|
}
|
|
16
16
|
/** One atomic node-to-node edge — the fabric graph consumes these (welds at shared node coords). */
|
|
17
|
-
export interface
|
|
17
|
+
export interface StreetEdge {
|
|
18
18
|
id: number;
|
|
19
19
|
/** Endpoint node ids. */
|
|
20
20
|
a: number;
|
|
21
21
|
b: number;
|
|
22
22
|
/** Sampled centerline; `points[0]` sits exactly on node `a`, the last on node `b`. */
|
|
23
|
-
points:
|
|
23
|
+
points: StreetVec2[];
|
|
24
24
|
width: number;
|
|
25
|
-
level:
|
|
25
|
+
level: StreetLevel;
|
|
26
26
|
/** True when this edge belongs to the main closed loop (circuit mode). */
|
|
27
27
|
loop: boolean;
|
|
28
28
|
}
|
|
29
29
|
/** A feature span carried by a street: a `[from, to]` index window into the street's `points`. */
|
|
30
|
-
export interface
|
|
31
|
-
kind:
|
|
30
|
+
export interface StreetFeatureSpan {
|
|
31
|
+
kind: StreetFeatureKind;
|
|
32
32
|
from: number;
|
|
33
33
|
to: number;
|
|
34
34
|
/** Ground height at each bank/portal — the deck/floor reference the renderer drapes to. */
|
|
35
35
|
bankHeight: number;
|
|
36
36
|
}
|
|
37
37
|
/** One chained through-street: a maximal run of edges through degree-2 nodes, for rendering + furniture. */
|
|
38
|
-
export interface
|
|
38
|
+
export interface Street {
|
|
39
39
|
id: number;
|
|
40
40
|
/** Ordered node ids the chain visits; first === last when `loop`. */
|
|
41
41
|
nodes: number[];
|
|
42
42
|
/** Smoothed, turn-clamped centerline. */
|
|
43
|
-
points:
|
|
43
|
+
points: StreetVec2[];
|
|
44
44
|
width: number;
|
|
45
|
-
level:
|
|
45
|
+
level: StreetLevel;
|
|
46
46
|
/** Closed chain (a circuit lap or an inner ring). */
|
|
47
47
|
loop: boolean;
|
|
48
48
|
/** Cul-de-sac turning bulb when the street ends at a dangling node. */
|
|
49
|
-
bulb?:
|
|
49
|
+
bulb?: StreetVec2;
|
|
50
50
|
/** Bridge/tunnel spans along this street, if any. */
|
|
51
|
-
features:
|
|
51
|
+
features: StreetFeatureSpan[];
|
|
52
52
|
}
|
|
53
53
|
/** One crossing of three or more streets: patch center/radius plus outgoing arm directions. */
|
|
54
|
-
export interface
|
|
54
|
+
export interface StreetJunction {
|
|
55
55
|
x: number;
|
|
56
56
|
z: number;
|
|
57
57
|
radius: number;
|
|
58
|
-
level:
|
|
58
|
+
level: StreetLevel;
|
|
59
59
|
/** Outgoing arm directions (radians) with the crossing street width, for crosswalks/patches. */
|
|
60
60
|
arms: {
|
|
61
61
|
angle: number;
|
|
@@ -63,7 +63,7 @@ export interface PathJunction {
|
|
|
63
63
|
}[];
|
|
64
64
|
}
|
|
65
65
|
/** One dangling street end kept as a cul-de-sac: node position plus the heading pointing off the road. */
|
|
66
|
-
export interface
|
|
66
|
+
export interface StreetDeadEnd {
|
|
67
67
|
node: number;
|
|
68
68
|
x: number;
|
|
69
69
|
z: number;
|
|
@@ -72,29 +72,29 @@ export interface PathDeadEnd {
|
|
|
72
72
|
width: number;
|
|
73
73
|
}
|
|
74
74
|
/** A resolved path feature in world-of-the-volume space: a bridge deck or tunnel bore centerline. */
|
|
75
|
-
export interface
|
|
76
|
-
kind:
|
|
77
|
-
points:
|
|
75
|
+
export interface StreetFeature {
|
|
76
|
+
kind: StreetFeatureKind;
|
|
77
|
+
points: StreetVec2[];
|
|
78
78
|
width: number;
|
|
79
79
|
bankHeight: number;
|
|
80
80
|
}
|
|
81
81
|
/** The fully-resolved network in volume-local coords. */
|
|
82
|
-
export interface
|
|
83
|
-
mode:
|
|
84
|
-
nodes:
|
|
82
|
+
export interface StreetNetwork {
|
|
83
|
+
mode: StreetNetworkMode;
|
|
84
|
+
nodes: StreetNode[];
|
|
85
85
|
/** Atomic edges — feed to the block/parcel fabric. */
|
|
86
|
-
edges:
|
|
86
|
+
edges: StreetEdge[];
|
|
87
87
|
/** Chained through-streets — feed to the renderer, furniture, and analysis. */
|
|
88
|
-
streets:
|
|
89
|
-
junctions:
|
|
90
|
-
deadEnds:
|
|
91
|
-
bridges:
|
|
92
|
-
tunnels:
|
|
88
|
+
streets: Street[];
|
|
89
|
+
junctions: StreetJunction[];
|
|
90
|
+
deadEnds: StreetDeadEnd[];
|
|
91
|
+
bridges: StreetFeature[];
|
|
92
|
+
tunnels: StreetFeature[];
|
|
93
93
|
/** Independent cycle count (E − V + components): 0 = pure tree, ≥1 = has loops. */
|
|
94
94
|
loops: number;
|
|
95
95
|
}
|
|
96
96
|
/** Fully-defaulted slider set the generator reads. */
|
|
97
|
-
export interface
|
|
97
|
+
export interface StreetNetworkRules {
|
|
98
98
|
seed: string;
|
|
99
99
|
/** 1 = regular lattice nodes; 0 = organic scatter. */
|
|
100
100
|
gridness: number;
|
|
@@ -124,7 +124,7 @@ export interface PathNetworkRules {
|
|
|
124
124
|
boulevards: number;
|
|
125
125
|
}
|
|
126
126
|
/** Ground sampler + feature toggles enabling bridges/tunnels; omit for a flat, feature-free network. */
|
|
127
|
-
export interface
|
|
127
|
+
export interface StreetNetworkContext {
|
|
128
128
|
/** Volume-local ground height sampler. */
|
|
129
129
|
heightAt?: (x: number, z: number) => number;
|
|
130
130
|
/** Ground below this (local) height is a gap streets bridge over. */
|
|
@@ -143,17 +143,17 @@ export interface PathNetworkContext {
|
|
|
143
143
|
* strictly reduces the angle. Endpoints never move, so a graph node stays welded.
|
|
144
144
|
* @internal
|
|
145
145
|
*/
|
|
146
|
-
export declare function clampTurns(points: readonly
|
|
146
|
+
export declare function clampTurns(points: readonly StreetVec2[], minRad: number, maxRad: number): StreetVec2[];
|
|
147
147
|
/** Decide which topology family the sliders call for. Circuit wins when loops dominate and both
|
|
148
148
|
* branching and mesh connectivity are low — exactly the "race track" corner of the slider space.
|
|
149
149
|
* @internal */
|
|
150
|
-
export declare function
|
|
150
|
+
export declare function streetNetworkMode(rules: StreetNetworkRules): StreetNetworkMode;
|
|
151
151
|
/**
|
|
152
152
|
* Resolve a full path network from its rules inside a volume of half-extents `hx`×`hz`. Deterministic
|
|
153
|
-
* per `(rules.seed, hx, hz, context)`. Pass a {@link
|
|
153
|
+
* per `(rules.seed, hx, hz, context)`. Pass a {@link StreetNetworkContext} with a ground sampler to turn
|
|
154
154
|
* water gaps into bridges and ridges into tunnels. Coordinates are volume-local; the caller maps to
|
|
155
155
|
* world space.
|
|
156
156
|
*
|
|
157
|
-
* @capability
|
|
157
|
+
* @capability street-generator build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
158
158
|
*/
|
|
159
|
-
export declare function
|
|
159
|
+
export declare function generateStreets(rules: StreetNetworkRules, hx: number, hz: number, context?: StreetNetworkContext): StreetNetwork;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The unified, seed-driven procedural
|
|
2
|
+
* The unified, seed-driven procedural STREET GENERATOR — one engine that grows an entire street/track
|
|
3
3
|
* graph inside a box volume and answers to sliders instead of hardcoded geometry. It is deliberately
|
|
4
4
|
* genre-agnostic: a city street net and a closed race circuit are the *same* engine at opposite
|
|
5
5
|
* slider extremes, not two code paths. Drop a volume, pick a seed, and turn the dials:
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
* BRIDGE deck; a span buried under a ridge becomes a TUNNEL bore. Both are path FEATURES on a
|
|
17
17
|
* continuous edge, so a circuit stays closed as it crosses water or pierces a hill.
|
|
18
18
|
*
|
|
19
|
-
* Output is two coupled views of the graph: atomic node-to-node {@link
|
|
19
|
+
* Output is two coupled views of the graph: atomic node-to-node {@link StreetEdge}s (fed straight into
|
|
20
20
|
* the block/parcel fabric — a race circuit is many edges between distinct nodes, never one fragile
|
|
21
|
-
* self-loop) and chained {@link
|
|
21
|
+
* self-loop) and chained {@link Street}s (through-streets for rendering, furniture, and
|
|
22
22
|
* junctions). Pure deterministic math — same rules + seed + volume ⇒ identical network — with bounded
|
|
23
23
|
* work caps so a huge volume can never generate unbounded content. Local (volume-centered) coords;
|
|
24
24
|
* the caller rotates/translates into world space.
|
|
25
25
|
*
|
|
26
|
-
* @capability
|
|
26
|
+
* @capability street-generator seed-driven procedural streets and race circuits from one slider-driven engine
|
|
27
27
|
*/
|
|
28
28
|
import { seededStreams } from "../random/rng.js";
|
|
29
29
|
const TAU = Math.PI * 2;
|
|
@@ -369,7 +369,7 @@ function buildCircuit(rules, hx, hz, streams) {
|
|
|
369
369
|
/** Decide which topology family the sliders call for. Circuit wins when loops dominate and both
|
|
370
370
|
* branching and mesh connectivity are low — exactly the "race track" corner of the slider space.
|
|
371
371
|
* @internal */
|
|
372
|
-
export function
|
|
372
|
+
export function streetNetworkMode(rules) {
|
|
373
373
|
const score = rules.loopiness * (1 - rules.branching * 0.7) * (1 - rules.connectivity * 0.7);
|
|
374
374
|
return score >= 0.45 ? "circuit" : "net";
|
|
375
375
|
}
|
|
@@ -396,15 +396,15 @@ function levelForStreets(chains, rules, rng) {
|
|
|
396
396
|
}
|
|
397
397
|
/**
|
|
398
398
|
* Resolve a full path network from its rules inside a volume of half-extents `hx`×`hz`. Deterministic
|
|
399
|
-
* per `(rules.seed, hx, hz, context)`. Pass a {@link
|
|
399
|
+
* per `(rules.seed, hx, hz, context)`. Pass a {@link StreetNetworkContext} with a ground sampler to turn
|
|
400
400
|
* water gaps into bridges and ridges into tunnels. Coordinates are volume-local; the caller maps to
|
|
401
401
|
* world space.
|
|
402
402
|
*
|
|
403
|
-
* @capability
|
|
403
|
+
* @capability street-generator build a deterministic street/track graph (nodes, edges, streets, junctions, bridges, tunnels) from sliders
|
|
404
404
|
*/
|
|
405
|
-
export function
|
|
405
|
+
export function generateStreets(rules, hx, hz, context = {}) {
|
|
406
406
|
const streams = seededStreams(`pathnet:${rules.seed}:${Math.round(hx)}:${Math.round(hz)}`);
|
|
407
|
-
const mode =
|
|
407
|
+
const mode = streetNetworkMode(rules);
|
|
408
408
|
const raw = mode === "circuit" ? buildCircuit(rules, hx, hz, streams) : buildNet(rules, hx, hz, streams);
|
|
409
409
|
const rawNodes = raw.nodes;
|
|
410
410
|
const rawEdges = raw.edges.filter((e) => e.a !== e.b);
|
package/dist/world/streets.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RoadEnvironmentDescriptor } from "./features.js";
|
|
2
|
-
import { type RoadPoint } from "./roads.js";
|
|
2
|
+
import { type DashExclusion, type RoadPoint } from "./roads.js";
|
|
3
3
|
/**
|
|
4
4
|
* Offset a centerline sideways by a signed distance along its local perpendicular — the
|
|
5
5
|
* building block for lanes, curb lines, and sidewalk paths. Positive offsets fall on the
|
|
@@ -107,3 +107,76 @@ export declare function roadSurfaceSampler(roads: readonly RoadEnvironmentDescri
|
|
|
107
107
|
* band, `fraction` (0..1) picks how far along. The canonical pedestrian spawn helper.
|
|
108
108
|
*/
|
|
109
109
|
export declare function sidewalkPoint(road: RoadEnvironmentDescriptor, side: "left" | "right", fraction: number): RoadPoint | null;
|
|
110
|
+
/**
|
|
111
|
+
* The two curb lines of a road — offset polylines running along the outer edge of the asphalt on
|
|
112
|
+
* each side. Mesh these with a thin darker ribbon to give the road a curb/edge strip instead of a
|
|
113
|
+
* hard asphalt-to-terrain seam. `inset` (default 0) nudges the line inward off the raw edge so the
|
|
114
|
+
* strip straddles the border.
|
|
115
|
+
*/
|
|
116
|
+
export declare function curbPaths(road: RoadEnvironmentDescriptor, inset?: number): readonly [readonly RoadPoint[], readonly RoadPoint[]];
|
|
117
|
+
/** A single arm of a junction: the road width plus the outward direction the road leaves the center. */
|
|
118
|
+
export interface RoadApproach {
|
|
119
|
+
/** Unit vector pointing from the junction center outward along the road arm. */
|
|
120
|
+
direction: readonly [number, number];
|
|
121
|
+
/** Drivable width of the road on this arm. */
|
|
122
|
+
width: number;
|
|
123
|
+
}
|
|
124
|
+
/** A welded road intersection: a patch center/radius plus the arms that feed it. */
|
|
125
|
+
export interface RoadJunction {
|
|
126
|
+
/** World XZ center of the intersection. */
|
|
127
|
+
center: RoadPoint;
|
|
128
|
+
/** Radius of the covering patch (covers the crossing ribbons). */
|
|
129
|
+
radius: number;
|
|
130
|
+
/** Representative surface elevation (max of the participating roads). */
|
|
131
|
+
elevation: number;
|
|
132
|
+
/** Representative asphalt color (the widest participating road's color). */
|
|
133
|
+
color: string;
|
|
134
|
+
/** Outward arms leaving the junction — one per road direction that continues past the center. */
|
|
135
|
+
approaches: readonly RoadApproach[];
|
|
136
|
+
}
|
|
137
|
+
/** Options for {@link findRoadJunctions}. */
|
|
138
|
+
export interface RoadJunctionOptions {
|
|
139
|
+
/** Roads weld only when their elevations differ by at most this much. Default 0.12. */
|
|
140
|
+
elevationBand?: number;
|
|
141
|
+
/** Crossing points closer than this merge into a single junction. Default 6. */
|
|
142
|
+
mergeDistance?: number;
|
|
143
|
+
/** Hard cap on junctions returned so detection stays bounded. Default 256. */
|
|
144
|
+
maxJunctions?: number;
|
|
145
|
+
/** Extra scale on the patch radius past the widest arm's half-width. Default 1.15. */
|
|
146
|
+
radiusScale?: number;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Detect welded intersections across a set of roads: wherever two road centerlines cross (or a road
|
|
150
|
+
* terminates on another) within the same elevation band, emit a {@link RoadJunction} whose patch
|
|
151
|
+
* covers the crossing ribbons. Nearby crossings merge into one junction. Deterministic and bounded —
|
|
152
|
+
* O(roads² · segments²) with a `maxJunctions` cap — so a scene builds its intersections once.
|
|
153
|
+
*/
|
|
154
|
+
export declare function findRoadJunctions(roads: readonly RoadEnvironmentDescriptor[], options?: RoadJunctionOptions): readonly RoadJunction[];
|
|
155
|
+
/** Circular marking-exclusion zones for the junctions, sized to hide the center line under each patch. */
|
|
156
|
+
export declare function junctionExclusions(junctions: readonly RoadJunction[], pad?: number): readonly DashExclusion[];
|
|
157
|
+
/** Options for {@link junctionMarkings}. */
|
|
158
|
+
export interface JunctionMarkingOptions {
|
|
159
|
+
/** Emit a stop-line bar across each approach. Default true. */
|
|
160
|
+
stopLines?: boolean;
|
|
161
|
+
/** Emit crosswalk bars at each approach. Default true. */
|
|
162
|
+
crosswalks?: boolean;
|
|
163
|
+
/** Number of zebra bars per crosswalk. Default 5. */
|
|
164
|
+
crosswalkBars?: number;
|
|
165
|
+
/** Clear gap outside the patch before markings start. Default 0.6. */
|
|
166
|
+
gap?: number;
|
|
167
|
+
/** Depth (along travel) of the crosswalk band. Default 2.4. */
|
|
168
|
+
crosswalkDepth?: number;
|
|
169
|
+
}
|
|
170
|
+
/** Thin marking polylines for a junction: stop-line bars and crosswalk bars, ready for {@link buildRoadRibbon}. */
|
|
171
|
+
export interface JunctionMarkings {
|
|
172
|
+
/** One bar per approach, perpendicular to travel just outside the patch. */
|
|
173
|
+
stopLines: readonly (readonly RoadPoint[])[];
|
|
174
|
+
/** Continental (parallel-to-travel) zebra bars, `crosswalkBars` per approach. */
|
|
175
|
+
crosswalkBars: readonly (readonly RoadPoint[])[];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Data-driven junction entry markings: a stop-line bar across each approach and a band of crosswalk
|
|
179
|
+
* bars just outside the welded patch. Each returned polyline is a thin centerline meant to be meshed
|
|
180
|
+
* with {@link buildRoadRibbon} at a small width. Fully skippable per-feature via the options.
|
|
181
|
+
*/
|
|
182
|
+
export declare function junctionMarkings(junction: RoadJunction, options?: JunctionMarkingOptions): JunctionMarkings;
|