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 })
MethodReturnsDescription
scatter(layer, options)ScatterResultScatter 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`)
MethodReturnsDescription
getById(id)SpeciesDefinition | undefinedLookup by string ID
getByState(state)SpeciesDefinition | undefinedLookup 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)voidAdd a custom species
countnumberTotal 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
}
GeneratorOutputTriangles
EzTreeAdapterL-system trees (14 species)3,000–5,000
RockGeneratorProcedural rocks (5 types)200–1,000
BillboardGeneratorCrossed-quad sprites4
PalmGeneratorPalm treesPlanned
FernGeneratorFernsPlanned
GrassGeneratorGrass patchesPlanned

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
}
SamplerSourceUse Case
FlatTerrainSamplerConstant heightUnit tests
ProceduralTerrainSamplersin/cos formulaDemo harness
HeightmapTerrainSamplerFloat32Array gridReal 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`)
ℹ Deterministic Results

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.