scena3d 0.1.0 → 0.4.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/README.md +41 -11
- package/dist/index.cjs +1203 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +534 -3
- package/dist/index.d.ts +534 -3
- package/dist/index.js +1204 -41
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Group, Vector3, Mesh, DirectionalLight, AmbientLight, HemisphereLight, Scene, Object3D, Material } from 'three';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Deterministic seeded randomness — the backbone of SCENA. Same seed,
|
|
@@ -48,8 +48,15 @@ interface Palette {
|
|
|
48
48
|
skyTop: number;
|
|
49
49
|
skyBottom: number;
|
|
50
50
|
fog: number;
|
|
51
|
+
water: number;
|
|
52
|
+
sand: number;
|
|
53
|
+
path: number;
|
|
54
|
+
/** Building plaster/wall color. */
|
|
55
|
+
wall: number;
|
|
56
|
+
/** Building roof color. */
|
|
57
|
+
roof: number;
|
|
51
58
|
}
|
|
52
|
-
declare const PALETTES: Record<'meadow' | 'autumn' | 'dusk', Palette>;
|
|
59
|
+
declare const PALETTES: Record<'meadow' | 'autumn' | 'dusk' | 'winter', Palette>;
|
|
53
60
|
declare const DEFAULT_PALETTE: Palette;
|
|
54
61
|
|
|
55
62
|
/**
|
|
@@ -135,6 +142,65 @@ interface LampOptions {
|
|
|
135
142
|
/** A street lamp: post, head, glowing bulb, optional real PointLight. */
|
|
136
143
|
declare function createLamp(options?: LampOptions): Prop;
|
|
137
144
|
|
|
145
|
+
interface GrassOptions {
|
|
146
|
+
seed?: number;
|
|
147
|
+
/** Blades per tuft. Default 4–6 by seed. */
|
|
148
|
+
blades?: number;
|
|
149
|
+
palette?: Palette;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A tuft of grass blades — pure scatter fodder (zero obstacle footprint,
|
|
153
|
+
* walk straight through). Sways beautifully under `applyWind`.
|
|
154
|
+
*/
|
|
155
|
+
declare function createGrassTuft(options?: GrassOptions): Prop;
|
|
156
|
+
interface BushOptions {
|
|
157
|
+
seed?: number;
|
|
158
|
+
size?: number;
|
|
159
|
+
palette?: Palette;
|
|
160
|
+
}
|
|
161
|
+
/** A low foliage bush: two or three squashed blobs. Small footprint. */
|
|
162
|
+
declare function createBush(options?: BushOptions): Prop;
|
|
163
|
+
|
|
164
|
+
interface HouseOptions {
|
|
165
|
+
seed?: number;
|
|
166
|
+
/** Footprint width (gable side). Default seeded 3.2–4.2. */
|
|
167
|
+
width?: number;
|
|
168
|
+
depth?: number;
|
|
169
|
+
wallHeight?: number;
|
|
170
|
+
palette?: Palette;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A cottage: plastered walls, gabled roof, door, chimney and emissive
|
|
174
|
+
* windows. Pass the house in `createDayCycle`'s `lamps` list and its
|
|
175
|
+
* windows glow at night along with the street lamps. A stone foundation
|
|
176
|
+
* extends below ground so sloped terrain never shows a gap.
|
|
177
|
+
*/
|
|
178
|
+
declare function createHouse(options?: HouseOptions): Prop;
|
|
179
|
+
interface TowerOptions {
|
|
180
|
+
seed?: number;
|
|
181
|
+
height?: number;
|
|
182
|
+
palette?: Palette;
|
|
183
|
+
}
|
|
184
|
+
/** A wooden watchtower: splayed legs, platform with railing, pyramid roof. */
|
|
185
|
+
declare function createTower(options?: TowerOptions): Prop;
|
|
186
|
+
interface WellOptions {
|
|
187
|
+
seed?: number;
|
|
188
|
+
palette?: Palette;
|
|
189
|
+
}
|
|
190
|
+
/** A stone well: ring, posts, little gabled roof, hanging bucket. */
|
|
191
|
+
declare function createWell(options?: WellOptions): Prop;
|
|
192
|
+
interface RuinOptions {
|
|
193
|
+
seed?: number;
|
|
194
|
+
/** Footprint width. Default seeded 3.5–5. */
|
|
195
|
+
size?: number;
|
|
196
|
+
palette?: Palette;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* A ruined building: a partial rectangle of crumbling wall segments with
|
|
200
|
+
* seeded gaps and heights, and tumbled blocks around the floor.
|
|
201
|
+
*/
|
|
202
|
+
declare function createRuin(options?: RuinOptions): Prop;
|
|
203
|
+
|
|
138
204
|
interface TerrainOptions {
|
|
139
205
|
seed?: number;
|
|
140
206
|
/** Square side length. Default 80. */
|
|
@@ -148,6 +214,8 @@ interface TerrainOptions {
|
|
|
148
214
|
octaves?: number;
|
|
149
215
|
/** Flatten low areas into meadows (0–1, higher = flatter valleys). Default 0.55. */
|
|
150
216
|
valleyFlatness?: number;
|
|
217
|
+
/** Blend low bands toward sand below (waterLevel + shore margin). */
|
|
218
|
+
waterLevel?: number;
|
|
151
219
|
palette?: Palette;
|
|
152
220
|
}
|
|
153
221
|
interface Terrain {
|
|
@@ -200,9 +268,282 @@ type FogPreset = 'clear' | 'haze' | 'thick' | 'eerie';
|
|
|
200
268
|
/** Distance fog matched to the palette's fog color. 'clear' removes it. */
|
|
201
269
|
declare function applyFog(scene: Scene, preset: FogPreset, palette?: Palette): void;
|
|
202
270
|
|
|
271
|
+
interface WaterOptions {
|
|
272
|
+
/** World-space water surface height. Default 0.8. */
|
|
273
|
+
level?: number;
|
|
274
|
+
size?: number;
|
|
275
|
+
resolution?: number;
|
|
276
|
+
/** Wave height. Default 0.06. */
|
|
277
|
+
amplitude?: number;
|
|
278
|
+
/** Wave speed multiplier. Default 1. */
|
|
279
|
+
speed?: number;
|
|
280
|
+
palette?: Palette;
|
|
281
|
+
}
|
|
282
|
+
interface Water {
|
|
283
|
+
mesh: Mesh;
|
|
284
|
+
level: number;
|
|
285
|
+
/** Advance the wave animation. Call from your frame loop. */
|
|
286
|
+
update(dt: number): void;
|
|
287
|
+
/** Is ground at this height below the surface? */
|
|
288
|
+
isUnderwater(groundHeight: number): boolean;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A low-poly animated water plane at a fixed level. Pair it with a
|
|
292
|
+
* terrain built using the same `waterLevel` so shores blend to sand, and
|
|
293
|
+
* keep scatter/agents ashore with `aboveWater(terrain, water)`.
|
|
294
|
+
*/
|
|
295
|
+
declare function createWater(options?: WaterOptions): Water;
|
|
296
|
+
/**
|
|
297
|
+
* A scatter mask keeping placements on dry land: true when the terrain
|
|
298
|
+
* at (x, z) sits above the water level plus `margin`.
|
|
299
|
+
*/
|
|
300
|
+
declare function aboveWater(terrain: Terrain, water: Pick<Water, 'level'>, margin?: number): (x: number, z: number) => boolean;
|
|
301
|
+
|
|
302
|
+
interface DayCycleOptions {
|
|
303
|
+
sky?: Sky;
|
|
304
|
+
rig?: LightingRig;
|
|
305
|
+
/** Scene whose fog color should track the cycle. */
|
|
306
|
+
scene?: Scene;
|
|
307
|
+
/** Lamp props/objects whose PointLights + glow bulbs ignite at night. */
|
|
308
|
+
lamps?: Array<{
|
|
309
|
+
object: Object3D;
|
|
310
|
+
} | Object3D>;
|
|
311
|
+
palette?: Palette;
|
|
312
|
+
/** Seconds per full day. Default 60. */
|
|
313
|
+
dayLength?: number;
|
|
314
|
+
/** Initial time: 0 = midnight, 0.25 = dawn, 0.5 = noon, 0.75 = dusk. */
|
|
315
|
+
timeOfDay?: number;
|
|
316
|
+
}
|
|
317
|
+
interface DayCycle {
|
|
318
|
+
timeOfDay: number;
|
|
319
|
+
/** Sun elevation in [-1, 1]; negative = below the horizon. */
|
|
320
|
+
readonly sunElevation: number;
|
|
321
|
+
readonly isNight: boolean;
|
|
322
|
+
/** Advance by dt seconds of real time and re-apply everything. */
|
|
323
|
+
update(dt: number): void;
|
|
324
|
+
/** Jump to a time of day and re-apply everything. */
|
|
325
|
+
set(t: number): void;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* One `timeOfDay` parameter driving the whole environment in lockstep:
|
|
329
|
+
* sun position/color/intensity, sky gradient, ambient level, fog color,
|
|
330
|
+
* and lamps that ignite as the sun drops below the horizon.
|
|
331
|
+
*
|
|
332
|
+
* ```ts
|
|
333
|
+
* const cycle = createDayCycle({ sky, rig, scene, lamps: [lampA, lampB], dayLength: 120 });
|
|
334
|
+
* game.onUpdate((t) => cycle.update(t.delta));
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
declare function createDayCycle(options?: DayCycleOptions): DayCycle;
|
|
338
|
+
|
|
339
|
+
interface WindOptions {
|
|
340
|
+
/** Sway amplitude in world units at the top of a prop. Default 0.06. */
|
|
341
|
+
strength?: number;
|
|
342
|
+
/** Oscillation speed. Default 1.2. */
|
|
343
|
+
frequency?: number;
|
|
344
|
+
/** Local height where sway begins (keeps trunks planted). Default 0.6. */
|
|
345
|
+
anchorHeight?: number;
|
|
346
|
+
}
|
|
347
|
+
interface Wind {
|
|
348
|
+
/** Advance the animation. Call from your frame loop. */
|
|
349
|
+
update(dt: number): void;
|
|
350
|
+
/** Materials that were patched (for debugging/cleanup). */
|
|
351
|
+
materials: Material[];
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Vertex-shader wind sway for scattered vegetation: displacement grows
|
|
355
|
+
* with local height (trunk bases stay planted), phase varies per
|
|
356
|
+
* instance so a forest shimmers instead of marching in step. Works on
|
|
357
|
+
* meshes and InstancedMeshes; patches each unique material once via
|
|
358
|
+
* onBeforeCompile.
|
|
359
|
+
*
|
|
360
|
+
* ```ts
|
|
361
|
+
* const wind = applyWind(forest.group, { strength: 0.08 });
|
|
362
|
+
* game.onUpdate((t) => wind.update(t.delta));
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
declare function applyWind(target: Object3D, options?: WindOptions): Wind;
|
|
366
|
+
|
|
367
|
+
interface PathOptions {
|
|
368
|
+
/** Ribbon width. Default 1.8. */
|
|
369
|
+
width?: number;
|
|
370
|
+
/** Ground height lookup; a number means flat ground. Default 0. */
|
|
371
|
+
surface?: number | ((x: number, z: number) => number);
|
|
372
|
+
/** Samples per world unit of path length. Default 1. */
|
|
373
|
+
samplesPerUnit?: number;
|
|
374
|
+
/** Close the path into a loop. Default false. */
|
|
375
|
+
loop?: boolean;
|
|
376
|
+
/** Extra clearance added to scatter keep-out circles. Default 0.6. */
|
|
377
|
+
keepOutMargin?: number;
|
|
378
|
+
palette?: Palette;
|
|
379
|
+
}
|
|
380
|
+
interface WorldPath {
|
|
381
|
+
mesh: Mesh;
|
|
382
|
+
/** Smoothed centerline draped on the surface — feed straight into a
|
|
383
|
+
* GAMA `Path` for patrols, or use as camera dolly points. */
|
|
384
|
+
route: Vector3[];
|
|
385
|
+
/** Keep-out circles for `scatter()` so nothing grows on the road. */
|
|
386
|
+
keepOut: Array<{
|
|
387
|
+
center: {
|
|
388
|
+
x: number;
|
|
389
|
+
z: number;
|
|
390
|
+
};
|
|
391
|
+
radius: number;
|
|
392
|
+
}>;
|
|
393
|
+
/** Is (x, z) on the path surface? (e.g. to exclude grass) */
|
|
394
|
+
contains(x: number, z: number): boolean;
|
|
395
|
+
loop: boolean;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* A dirt path: a Catmull-Rom-smoothed ribbon draped over the surface.
|
|
399
|
+
* One authored polyline feeds three things at once — the visual ribbon,
|
|
400
|
+
* scatter keep-out, and a patrol route for agents. That's the SCENA
|
|
401
|
+
* handshake applied to level design.
|
|
402
|
+
*
|
|
403
|
+
* ```ts
|
|
404
|
+
* const road = createPath([a, b, c], { surface: terrain.heightAt, loop: true });
|
|
405
|
+
* scene.add(road.mesh);
|
|
406
|
+
* scatter({ ..., keepOut: road.keepOut });
|
|
407
|
+
* agent.addBehavior(new FollowPath(new Path(road.route, road.loop), 1.5));
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
declare function createPath(points: Array<Vector3 | {
|
|
411
|
+
x: number;
|
|
412
|
+
z: number;
|
|
413
|
+
}>, options?: PathOptions): WorldPath;
|
|
414
|
+
|
|
415
|
+
interface VillageOptions {
|
|
416
|
+
seed?: number;
|
|
417
|
+
/** Village center in the XZ plane. Default origin. */
|
|
418
|
+
center?: {
|
|
419
|
+
x: number;
|
|
420
|
+
z: number;
|
|
421
|
+
};
|
|
422
|
+
/** Ring radius the houses settle on. Default 12. */
|
|
423
|
+
radius?: number;
|
|
424
|
+
/** House count. Default 5. */
|
|
425
|
+
houses?: number;
|
|
426
|
+
/** Ground height lookup; a number means flat ground. Default 0. */
|
|
427
|
+
surface?: number | ((x: number, z: number) => number);
|
|
428
|
+
/** Veto candidate spots (e.g. water, roads). Return false to reject. */
|
|
429
|
+
mask?: (x: number, z: number, y: number) => boolean;
|
|
430
|
+
/** Add real PointLights to this many street lamps. Default 3. */
|
|
431
|
+
lampLights?: number;
|
|
432
|
+
/** Add a watchtower at the edge. Default true. */
|
|
433
|
+
tower?: boolean;
|
|
434
|
+
/** Add a ruin outside the ring. Default true. */
|
|
435
|
+
ruin?: boolean;
|
|
436
|
+
palette?: Palette;
|
|
437
|
+
}
|
|
438
|
+
interface Village {
|
|
439
|
+
group: Group;
|
|
440
|
+
/** Every placed prop (houses, well, lamps, crates, tower, ruin). */
|
|
441
|
+
props: Prop[];
|
|
442
|
+
/** World-space steering obstacles — feed GAMA's ObstacleAvoidance. */
|
|
443
|
+
obstacles: Obstacle[];
|
|
444
|
+
/**
|
|
445
|
+
* Props with something to ignite at night (street lamps AND house
|
|
446
|
+
* windows). Pass straight to `createDayCycle({ lamps })`.
|
|
447
|
+
*/
|
|
448
|
+
lamps: Prop[];
|
|
449
|
+
/** One clearing circle covering the village — feed `scatter`'s keepOut. */
|
|
450
|
+
keepOut: Array<{
|
|
451
|
+
center: {
|
|
452
|
+
x: number;
|
|
453
|
+
z: number;
|
|
454
|
+
};
|
|
455
|
+
radius: number;
|
|
456
|
+
}>;
|
|
457
|
+
center: {
|
|
458
|
+
x: number;
|
|
459
|
+
z: number;
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* A seeded hamlet: a well at the center, houses ringed around it facing
|
|
464
|
+
* inward, street lamps between them, crates by the doors, a watchtower on
|
|
465
|
+
* the edge and a ruin beyond. Everything is placed on the given surface
|
|
466
|
+
* (steep or masked spots are rejected and re-rolled), and the result
|
|
467
|
+
* carries the full gameplay handshake: `obstacles` for steering,
|
|
468
|
+
* `keepOut` for scatter, and `lamps` for the day-night cycle — windows
|
|
469
|
+
* and lamps ignite together at dusk.
|
|
470
|
+
*
|
|
471
|
+
* ```ts
|
|
472
|
+
* const village = createVillage({ seed: 5, radius: 10, surface: terrain.heightAt, palette });
|
|
473
|
+
* scene.add(village.group);
|
|
474
|
+
* const cycle = createDayCycle({ sky, rig, scene, lamps: village.lamps, palette });
|
|
475
|
+
* const forest = scatter({ ..., keepOut: village.keepOut });
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
declare function createVillage(options?: VillageOptions): Village;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The kit grid: every kit piece snaps to this cell size (in world units).
|
|
482
|
+
* Shared snap dimensions are what make pieces combinable — a doorway cut
|
|
483
|
+
* in one wall lines up with the corridor on the other side.
|
|
484
|
+
*/
|
|
485
|
+
declare const KIT_UNIT = 2;
|
|
486
|
+
interface KitOptions {
|
|
487
|
+
seed?: number;
|
|
488
|
+
/** Cell size override. Default KIT_UNIT. */
|
|
489
|
+
unit?: number;
|
|
490
|
+
/** Wall height. Default 2.6. */
|
|
491
|
+
wallHeight?: number;
|
|
492
|
+
/** Add real PointLights to this many torches. Default 4. */
|
|
493
|
+
torchLights?: number;
|
|
494
|
+
palette?: Palette;
|
|
495
|
+
}
|
|
496
|
+
interface Kit {
|
|
497
|
+
group: Group;
|
|
498
|
+
/** One obstacle per wall cell — feed GAMA's ObstacleAvoidance. */
|
|
499
|
+
obstacles: Obstacle[];
|
|
500
|
+
/** World positions of 'S' cells (player/NPC spawn points). */
|
|
501
|
+
spawns: Vector3[];
|
|
502
|
+
/** World positions of 'T' cells (torches), lit in placement order. */
|
|
503
|
+
torches: Vector3[];
|
|
504
|
+
/** Is (x, z) over a walkable floor cell? */
|
|
505
|
+
floorAt(x: number, z: number): boolean;
|
|
506
|
+
/** Footprint in world units: { width, depth } centered on the origin. */
|
|
507
|
+
size: {
|
|
508
|
+
width: number;
|
|
509
|
+
depth: number;
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Assemble a dungeon/compound from an ASCII map — each character is one
|
|
514
|
+
* KIT_UNIT × KIT_UNIT cell, centered on the group origin:
|
|
515
|
+
*
|
|
516
|
+
* - `#` wall block (blocks movement, becomes an obstacle)
|
|
517
|
+
* - `.` floor tile
|
|
518
|
+
* - `D` doorway: floor + lintel spanning the gap overhead
|
|
519
|
+
* - `T` floor + standing torch (emissive flame; `createDayCycle` adopts it)
|
|
520
|
+
* - `S` floor + recorded spawn point
|
|
521
|
+
* - ` ` nothing
|
|
522
|
+
*
|
|
523
|
+
* ```ts
|
|
524
|
+
* const fort = assembleKit([
|
|
525
|
+
* '#######',
|
|
526
|
+
* '#..T..#',
|
|
527
|
+
* '#.....D',
|
|
528
|
+
* '#..S..#',
|
|
529
|
+
* '#######',
|
|
530
|
+
* ], { palette });
|
|
531
|
+
* fort.group.position.set(20, terrain.heightAt(20, 5), 5);
|
|
532
|
+
* ```
|
|
533
|
+
*
|
|
534
|
+
* Walls and floors render as two InstancedMeshes regardless of map size.
|
|
535
|
+
*/
|
|
536
|
+
declare function assembleKit(rows: string[], options?: KitOptions): Kit;
|
|
537
|
+
|
|
203
538
|
interface ScatterItem {
|
|
204
539
|
/** Prop factory, called once per visual variant with a seeded Rng. */
|
|
205
540
|
create(rng: Rng): Prop;
|
|
541
|
+
/**
|
|
542
|
+
* Simplified far-distance factory for LOD (e.g. a single cone for a
|
|
543
|
+
* tree). Only used when `ScatterOptions.lod` is set; items without one
|
|
544
|
+
* stay full-detail at every distance.
|
|
545
|
+
*/
|
|
546
|
+
createFar?(rng: Rng): Prop;
|
|
206
547
|
/** Relative frequency among items. Default 1. */
|
|
207
548
|
weight?: number;
|
|
208
549
|
/** Distinct variants generated per item (visual variety). Default 4. */
|
|
@@ -244,6 +585,18 @@ interface ScatterOptions {
|
|
|
244
585
|
}[];
|
|
245
586
|
/** Density-noise feature size in world units. Default 18. */
|
|
246
587
|
clumpScale?: number;
|
|
588
|
+
/**
|
|
589
|
+
* Tile-based LOD: placements are bucketed into square tiles; tiles
|
|
590
|
+
* beyond `distance` from the camera swap full-detail instances for the
|
|
591
|
+
* items' `createFar` variants (with 10% hysteresis so tiles don't
|
|
592
|
+
* flicker at the boundary). Call `result.update(camera)` each frame.
|
|
593
|
+
*/
|
|
594
|
+
lod?: {
|
|
595
|
+
/** Camera distance at which tiles switch to far variants. */
|
|
596
|
+
distance: number;
|
|
597
|
+
/** Tile side in world units. Default 16. */
|
|
598
|
+
tileSize?: number;
|
|
599
|
+
};
|
|
247
600
|
}
|
|
248
601
|
interface Placement {
|
|
249
602
|
position: Vector3;
|
|
@@ -251,6 +604,14 @@ interface Placement {
|
|
|
251
604
|
scale: number;
|
|
252
605
|
itemIndex: number;
|
|
253
606
|
}
|
|
607
|
+
interface ScatterTile {
|
|
608
|
+
center: {
|
|
609
|
+
x: number;
|
|
610
|
+
z: number;
|
|
611
|
+
};
|
|
612
|
+
near: Group;
|
|
613
|
+
far: Group;
|
|
614
|
+
}
|
|
254
615
|
interface ScatterResult {
|
|
255
616
|
/** One InstancedMesh per template part — a handful of draw calls total. */
|
|
256
617
|
group: Group;
|
|
@@ -258,6 +619,12 @@ interface ScatterResult {
|
|
|
258
619
|
/** World-space steering obstacles for everything with a footprint. */
|
|
259
620
|
obstacles: Obstacle[];
|
|
260
621
|
count: number;
|
|
622
|
+
/** LOD tiles (present when `lod` was requested). */
|
|
623
|
+
tiles?: ScatterTile[];
|
|
624
|
+
/** Re-evaluate tile LOD against the camera (present when `lod` was requested). */
|
|
625
|
+
update?(camera: {
|
|
626
|
+
position: Vector3;
|
|
627
|
+
}): void;
|
|
261
628
|
}
|
|
262
629
|
/**
|
|
263
630
|
* Populate an area with seeded, instanced props: "empty plane → forest"
|
|
@@ -285,4 +652,168 @@ interface ScatterResult {
|
|
|
285
652
|
*/
|
|
286
653
|
declare function scatter(options: ScatterOptions): ScatterResult;
|
|
287
654
|
|
|
288
|
-
|
|
655
|
+
/** Prop vocabulary available to manifest scatters. */
|
|
656
|
+
type ScatterPropType = 'tree' | 'rock' | 'bush' | 'grass' | 'crate' | 'fence' | 'lamp';
|
|
657
|
+
interface ManifestScatterItem {
|
|
658
|
+
type: ScatterPropType;
|
|
659
|
+
weight?: number;
|
|
660
|
+
variants?: number;
|
|
661
|
+
scale?: [number, number];
|
|
662
|
+
}
|
|
663
|
+
interface ManifestScatter {
|
|
664
|
+
/** Defaults to the terrain footprint (slightly inset). */
|
|
665
|
+
area?: {
|
|
666
|
+
min: {
|
|
667
|
+
x: number;
|
|
668
|
+
z: number;
|
|
669
|
+
};
|
|
670
|
+
max: {
|
|
671
|
+
x: number;
|
|
672
|
+
z: number;
|
|
673
|
+
};
|
|
674
|
+
};
|
|
675
|
+
density?: number;
|
|
676
|
+
count?: number;
|
|
677
|
+
minSpacing?: number;
|
|
678
|
+
clumpScale?: number;
|
|
679
|
+
items: ManifestScatterItem[];
|
|
680
|
+
/** Reject spots above this ground height (keep peaks bare). */
|
|
681
|
+
maxHeight?: number;
|
|
682
|
+
/** Keep placements ashore. Default true when the scene has water. */
|
|
683
|
+
avoidWater?: boolean;
|
|
684
|
+
/** Keep placements off paths and out of the village. Default true. */
|
|
685
|
+
avoidPaths?: boolean;
|
|
686
|
+
/** Opt into tile-based LOD (see ScatterOptions.lod). */
|
|
687
|
+
lod?: {
|
|
688
|
+
distance: number;
|
|
689
|
+
tileSize?: number;
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* A whole world as plain JSON — every field is data, so manifests can be
|
|
694
|
+
* stored, diffed, sent over the network, or edited in a tool.
|
|
695
|
+
*/
|
|
696
|
+
interface SceneManifest {
|
|
697
|
+
seed?: number;
|
|
698
|
+
palette?: keyof typeof PALETTES;
|
|
699
|
+
terrain?: {
|
|
700
|
+
size?: number;
|
|
701
|
+
resolution?: number;
|
|
702
|
+
amplitude?: number;
|
|
703
|
+
noiseScale?: number;
|
|
704
|
+
octaves?: number;
|
|
705
|
+
valleyFlatness?: number;
|
|
706
|
+
};
|
|
707
|
+
water?: {
|
|
708
|
+
level: number;
|
|
709
|
+
size?: number;
|
|
710
|
+
};
|
|
711
|
+
/** Sky dome. Default true. */
|
|
712
|
+
sky?: boolean;
|
|
713
|
+
lighting?: LightingPreset;
|
|
714
|
+
/** Fog preset, or false for none. Default 'haze'. */
|
|
715
|
+
fog?: FogPreset | false;
|
|
716
|
+
/** Animated day-night cycle. Off unless specified. */
|
|
717
|
+
dayCycle?: {
|
|
718
|
+
dayLength?: number;
|
|
719
|
+
timeOfDay?: number;
|
|
720
|
+
};
|
|
721
|
+
paths?: Array<{
|
|
722
|
+
points: Array<{
|
|
723
|
+
x: number;
|
|
724
|
+
z: number;
|
|
725
|
+
}>;
|
|
726
|
+
width?: number;
|
|
727
|
+
loop?: boolean;
|
|
728
|
+
}>;
|
|
729
|
+
village?: {
|
|
730
|
+
center?: {
|
|
731
|
+
x: number;
|
|
732
|
+
z: number;
|
|
733
|
+
};
|
|
734
|
+
radius?: number;
|
|
735
|
+
houses?: number;
|
|
736
|
+
lampLights?: number;
|
|
737
|
+
tower?: boolean;
|
|
738
|
+
ruin?: boolean;
|
|
739
|
+
};
|
|
740
|
+
scatters?: ManifestScatter[];
|
|
741
|
+
/** Vegetation sway. Default on; false disables, or set the strength. */
|
|
742
|
+
wind?: boolean | {
|
|
743
|
+
strength?: number;
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
interface BuiltScene {
|
|
747
|
+
/** Everything visual, ready to add to a scene (already added if one was passed). */
|
|
748
|
+
group: Group;
|
|
749
|
+
palette: Palette;
|
|
750
|
+
terrain?: Terrain;
|
|
751
|
+
water?: Water;
|
|
752
|
+
sky?: Sky;
|
|
753
|
+
rig: LightingRig;
|
|
754
|
+
cycle?: DayCycle;
|
|
755
|
+
paths: WorldPath[];
|
|
756
|
+
village?: Village;
|
|
757
|
+
scatters: ScatterResult[];
|
|
758
|
+
/** Combined steering obstacles from the village and every scatter. */
|
|
759
|
+
obstacles: Obstacle[];
|
|
760
|
+
/** Ground height (terrain's, or 0 for flat scenes). */
|
|
761
|
+
heightAt(x: number, z: number): number;
|
|
762
|
+
/** Advance water, wind and the day cycle. Call from your frame loop. */
|
|
763
|
+
update(dt: number): void;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Compile a `SceneManifest` into a live world: terrain, water, sky,
|
|
767
|
+
* lighting, fog, paths, a village, scatters and the day cycle — with all
|
|
768
|
+
* the cross-feature wiring the forest demo does by hand applied
|
|
769
|
+
* automatically (scatters stay ashore, off paths and out of the village;
|
|
770
|
+
* the village avoids water and roads; village lamps and windows feed the
|
|
771
|
+
* day cycle).
|
|
772
|
+
*
|
|
773
|
+
* ```ts
|
|
774
|
+
* const world = buildScene(JSON.parse(manifestJson), scene);
|
|
775
|
+
* game.onUpdate((t) => world.update(t.delta));
|
|
776
|
+
* agent.addBehavior(new ObstacleAvoidance(() => world.obstacles));
|
|
777
|
+
* ```
|
|
778
|
+
*/
|
|
779
|
+
declare function buildScene(manifest: SceneManifest, scene?: Scene): BuiltScene;
|
|
780
|
+
|
|
781
|
+
interface Markers {
|
|
782
|
+
/** `spawn_<name>` → world position. */
|
|
783
|
+
spawns: Record<string, Vector3>;
|
|
784
|
+
/** `route_<name>_<index>` → world positions ordered by index. */
|
|
785
|
+
routes: Record<string, Vector3[]>;
|
|
786
|
+
/** `obstacle_<name>` → steering obstacle (radius from the node's scale). */
|
|
787
|
+
obstacles: Obstacle[];
|
|
788
|
+
/** `keepout_<name>` → scatter keep-out circle (radius from scale). */
|
|
789
|
+
keepOut: Array<{
|
|
790
|
+
center: {
|
|
791
|
+
x: number;
|
|
792
|
+
z: number;
|
|
793
|
+
};
|
|
794
|
+
radius: number;
|
|
795
|
+
}>;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Extract gameplay markers from an object tree by naming convention — the
|
|
799
|
+
* bridge between a DCC tool (Blender empties, glTF nodes) and SCENA/GAMA:
|
|
800
|
+
*
|
|
801
|
+
* - `spawn_player`, `spawn_boss` → named spawn points
|
|
802
|
+
* - `route_patrol_0`, `route_patrol_1`, … → ordered patrol routes
|
|
803
|
+
* - `obstacle_statue` → steering obstacle; radius = max(scale.x, scale.z)
|
|
804
|
+
* - `keepout_plaza` → scatter keep-out circle; radius = max(scale.x, scale.z)
|
|
805
|
+
*
|
|
806
|
+
* Blender's duplicate suffixes (`.001`) are stripped, so `route_a_0.001`
|
|
807
|
+
* still parses. Positions are world-space (the tree is updated first).
|
|
808
|
+
*
|
|
809
|
+
* ```ts
|
|
810
|
+
* const gltf = await new GLTFLoader().loadAsync('level.glb');
|
|
811
|
+
* const markers = extractMarkers(gltf.scene);
|
|
812
|
+
* player.position.copy(markers.spawns.player);
|
|
813
|
+
* guard.addBehavior(new FollowPath(new Path(markers.routes.patrol, true), 1.5));
|
|
814
|
+
* scatter({ ..., keepOut: markers.keepOut });
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
817
|
+
declare function extractMarkers(root: Object3D): Markers;
|
|
818
|
+
|
|
819
|
+
export { type BuiltScene, type BushOptions, type CrateOptions, DEFAULT_PALETTE, type DayCycle, type DayCycleOptions, type FenceOptions, type FogPreset, type GrassOptions, type HouseOptions, KIT_UNIT, type Kit, type KitOptions, type LampOptions, type LightingPreset, type LightingRig, type ManifestScatter, type ManifestScatterItem, type Markers, type Obstacle, PALETTES, type Palette, type PathOptions, type Placement, type Prop, Rng, type RockOptions, type RuinOptions, type ScatterItem, type ScatterOptions, type ScatterPropType, type ScatterResult, type ScatterTile, type SceneManifest, type Sky, type SkyOptions, type Terrain, type TerrainOptions, type TowerOptions, type TreeOptions, type Village, type VillageOptions, type Water, type WaterOptions, type WellOptions, type Wind, type WindOptions, type WorldPath, aboveWater, applyFog, applyWind, assembleKit, buildScene, collectObstacles, createBush, createCrate, createDayCycle, createFence, createGrassTuft, createHouse, createLamp, createLightingRig, createPath, createRock, createRuin, createSky, createTerrain, createTower, createTree, createVillage, createWater, createWell, extractMarkers, fractalNoise2, hash2, scatter, valueNoise2 };
|