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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Vector3, Group, Mesh, DirectionalLight, AmbientLight, HemisphereLight, Scene } from 'three';
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
- export { type CrateOptions, DEFAULT_PALETTE, type FenceOptions, type FogPreset, type LampOptions, type LightingPreset, type LightingRig, type Obstacle, PALETTES, type Palette, type Placement, type Prop, Rng, type RockOptions, type ScatterItem, type ScatterOptions, type ScatterResult, type Sky, type SkyOptions, type Terrain, type TerrainOptions, type TreeOptions, applyFog, collectObstacles, createCrate, createFence, createLamp, createLightingRig, createRock, createSky, createTerrain, createTree, fractalNoise2, hash2, scatter, valueNoise2 };
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 };