API Reference
Use Landscaper as a library. Scatter from code. No UI needed.
Core Classes
ScatterSystem
The main entry point. Pure math — no Three.js dependency. Give it layers and terrain, get back positioned instances.
import { ScatterSystem } from '@poqpoq/landscaper'
const scatter = new ScatterSystem()
// Single layer
const result = scatter.scatter(layer, { terrain, region })
// Multi-layer with cross-exclusion
const results = scatter.scatterLayers(layers, { terrain, region, seed: 42 })
| Method | Returns | Description |
|---|---|---|
scatter(layer, options) | ScatterResult | Scatter a single layer independently |
scatterLayers(layers, options) | Map<string, ScatterResult> | Scatter multiple layers with cross-exclusion |
SpeciesRegistry
Registry of all 26 built-in species. Lookup by ID, OpenSim state, generator type, or biome.
import { SpeciesRegistry } from '@poqpoq/landscaper'
const registry = new SpeciesRegistry()
const oak = registry.getById('oak')
const pines = registry.getByBiome('mountain')
const trees = registry.getByGenerator('ez-tree')
const all = registry.getAll()
console.log(`${registry.count} species registered`)
| Method | Returns | Description |
|---|---|---|
getById(id) | SpeciesDefinition | undefined | Lookup by string ID |
getByState(state) | SpeciesDefinition | undefined | Lookup by OpenSim state (0–20) |
getAll() | SpeciesDefinition[] | All registered species |
getByGenerator(type) | SpeciesDefinition[] | Filter by generator type |
getByBiome(biome) | SpeciesDefinition[] | Filter by biome |
register(species) | void | Add a custom species |
count | number | Total species count (getter) |
ScenePopulator
Glue between scatter results and Three.js. Takes positioned instances and creates meshes in the scene.
import { ScenePopulator, SpeciesRegistry } from '@poqpoq/landscaper'
const populator = new ScenePopulator({
scene: threeScene,
registry: new SpeciesRegistry(),
generators: generatorMap
})
// Populate from scatter results
const stats = populator.populateAll(results)
console.log(`${stats.instanceCount} objects, ${stats.estimatedTriangles} triangles`)
// Clean up
populator.clear()
populator.dispose()
InstancePool
Instanced mesh manager for high-performance rendering. Register geometry+material pairs, then add instances by key.
import { InstancePool } from '@poqpoq/landscaper'
const pool = new InstancePool()
pool.register('oak', oakGeometry, oakMaterial, 500)
pool.add('oak', position, rotation, scale)
// Clear specific species or all
pool.clear('oak')
pool.clearAll()
pool.dispose()
LODBuilder
Creates Three.js LOD objects with multiple detail levels per species. Full detail up close, billboards in the distance.
import { LODBuilder } from '@poqpoq/landscaper'
const lod = new LODBuilder()
const lodObject = lod.build(species, generator, seed)
scene.add(lodObject)
Generators
All generators implement the MeshGenerator interface:
interface MeshGenerator {
readonly type: string
generate(species, seed?): GeneratorOutput
generateSimplified?(species, seed?): GeneratorOutput
dispose(): void
}
interface GeneratorOutput {
object: THREE.Object3D
triangleCount: number
boundingRadius: number
}
| Generator | Output | Triangles |
|---|---|---|
EzTreeAdapter | L-system trees (14 species) | 3,000–5,000 |
RockGenerator | Procedural rocks (5 types) | 200–1,000 |
BillboardGenerator | Crossed-quad sprites | 4 |
PalmGenerator | Palm trees | Planned |
FernGenerator | Ferns | Planned |
GrassGenerator | Grass patches | Planned |
Scatter Algorithms
Standalone functions that generate 2D point distributions:
import { poissonDisk, clustered, densityFunction, grid } from '@poqpoq/landscaper'
// Poisson disk — even natural spacing
const points = poissonDisk({ region, minDistance: 5, maxPoints: 200 })
// Clustered — natural groupings
const points = clustered({ region, totalCount: 200 })
// Density — gradient falloff from center
const points = densityFunction({ region, targetCount: 200 })
// Grid — regular spacing with jitter
const points = grid({ region, spacing: 10, jitter: 0.2 })
Terrain Samplers
Implement TerrainSampler to connect Landscaper to any terrain source:
interface TerrainSampler {
getHeight(x: number, z: number): number
getSlope(x: number, z: number): number
}
| Sampler | Source | Use Case |
|---|---|---|
FlatTerrainSampler | Constant height | Unit tests |
ProceduralTerrainSampler | sin/cos formula | Demo harness |
HeightmapTerrainSampler | Float32Array grid | Real terrain data from Terraformer |
Region Types
// Rectangular bounds
{ type: 'bounds', minX: -100, maxX: 100, minZ: -100, maxZ: 100 }
// Circular
{ type: 'circle', centerX: 0, centerZ: 0, radius: 50 }
// Arbitrary polygon (point-in-polygon via ray casting)
{ type: 'polygon', points: [{ x: 0, z: 0 }, { x: 100, z: 0 }, { x: 50, z: 80 }] }
Minimal Example
import {
ScatterSystem,
SpeciesRegistry,
ProceduralTerrainSampler
} from '@poqpoq/landscaper'
// 1. Create system
const scatter = new ScatterSystem()
const registry = new SpeciesRegistry()
// 2. Define a layer
const layer = {
id: 'forest',
name: 'Oak Forest',
instanceTypes: [
{ speciesId: 'oak', weight: 1.0, scaleMin: 0.8, scaleMax: 1.2 }
],
algorithm: 'poisson',
count: 50,
minDistance: 5,
constraints: [
{ type: 'slope', maxDegrees: 30 },
{ type: 'height', minHeight: 0, maxHeight: 100 }
],
priority: 10
}
// 3. Scatter
const terrain = new ProceduralTerrainSampler()
const region = { type: 'bounds', minX: -100, maxX: 100, minZ: -100, maxZ: 100 }
const result = scatter.scatter(layer, { terrain, region })
console.log(`Placed ${result.instances.length} oaks`)
Pass a seed in scatter options for deterministic results. Same seed = same placement, every time. Essential for networked worlds where all clients must generate identical vegetation.