Procedural generators - SethKitchen/ThreeMojo GitHub Wiki
The procedural generators of three.js's examples/jsm/generators/: a city, a forest, a terrain and a tree. The same seed and parameters give the same geometry and the same placements as three.js r186. The materials are not ported.

examples/sapling.mojo draws this picture.
| Module | three.js |
|---|---|
generators/utils.mojo |
createRandom, part and place of the city, and the shared instanced lifecycle |
generators/tree.mojo |
TreeGenerator |
generators/terrain.mojo |
TerrainGenerator |
generators/forest.mojo |
ForestGenerator |
generators/city.mojo |
CityGenerator |
generators/skyscraper.mojo |
city/SkyscraperGenerator |
generators/sidewalk.mojo |
city/SidewalkGenerator |
generators/street_furniture.mojo |
city/StreetlightGenerator, TrafficlightGenerator, TrashcanGenerator, BenchGenerator, HydrantGenerator, StreetTreeGenerator
|
generators/car.mojo |
city/CarGenerator |
generators/person.mojo |
city/PersonGenerator |
geometries/loft.mojo |
geometries/LoftGeometry |
Each generator makes its own seeded generator, three.js's createRandom. It is Mulberry32, the generator of MathUtils.seededRandom, so generator_random(seed) returns a SeededRandom. It keeps the low 32 bits of the seed. A seed of zero gives the numbers of a seed of one, as in three.js.
The generators compute in Float64 and store Float32, as three.js does. The tests compare positions to a few Float32 steps.
TreeGenerator(parameters).build() grows a tree skeleton and bakes it into one indexed geometry with position and normal. tubes() returns the skeleton before the bake: each branch as a tube of rings.
- The trunk and every branch are tubes. A tube is swept in rings, one ring a step of
section_length. - A child forks from the upper part of its parent. It is thinner by the pipe model and tilted by
branch_angle. The golden angle rolls it round the parent. -
up_pullpulls a child back toward the sky.droopmakes a branch sag. The trunk does not sag. -
gnarlwobbles each step.root_flareswells the base of the trunk.
three.js sets a parameter with a fluent set<Param>. In this port, set the field of parameters and build again.
All tree parameters and list entries must be finite. The section length and radius exponent must be positive. The taper curve must be zero or more. An active root flare needs a positive flare fraction. These checks run before growth.
Finite parameters do not guarantee finite output. tubes() raises Error if a generated ring or branch length is nonfinite, or a generated direction is zero. Its returned frames have finite unit tangents and normals, perpendicular within floating-point precision. Large finite directions use scale-safe normalization.
Internal child sampling keeps three.js's independently normalized tangent and normal. The sampled pair need not be perpendicular. Each child tube starts with a new perpendicular normal.
build() also raises Error if a generated vertex is nonfinite or exceeds the finite Float32 range. A finite skeleton can therefore succeed while its bake fails. Positions that round to zero remain supported. The checks apply to generated values, with no fixed upper bound on finite input parameters.
up_pull, child_start and trunk_clear accept finite values outside zero to one, as the three.js r186 setters do. Positive up_pull extrapolates the direction blend; zero and negative values disable it. Child placement clamps the sampled fraction to zero through 0.999. Other child calculations still use the original fraction. These values remain subject to the generated-output checks.
A zero child count stops branching. A level past the end of a parameter list reuses its last entry.
TerrainGenerator(parameters).build() bakes a mountain range into one indexed geometry in the ground plane, y up. The generator keeps the height grid. Then sample_height and sample_slope read the surface.
- The height is a sum of Perlin octaves from
ImprovedNoise. A steep running slope damps each octave, so ridges stay sharp and valleys stay smooth. - A slow noise warps the sample point first, so the ridges wander.
- Thermal erosion then relaxes each slope past the angle of repose,
talus, overtalus_passespasses. - The seed moves the sample window only, because the permutation of
ImprovedNoiseis fixed.
sample_height(x, z) interpolates the four grid vertices round a point. sample_slope(x, z) is the y of the surface normal: one on level ground. height_at and slope_at do the same in Float64 meters.
ForestGenerator(parameters).build(terrain) scatters trees over a built terrain. It returns a ForestInstances: one blob geometry and one matrix a tree.
- The blob is an icosahedron welded to twelve vertices. It is squashed into a lumpy teardrop, and its
aoattribute runs from zero at the base to one at the crown. - A tree stands only in the altitude band, on ground flatter than
min_slope, and inside a density mask of slow noise. - The draw stops after fourteen tries a tree.
-
instances.valuesholds three.js'scullattribute, andregionholds its color drift.
The forest material culls far trees in a shader. keeps(index, camera) answers the same question on the CPU. A tree is drawn while its random threshold is at least its place in the band from from_distance to to_distance.
CityGenerator(parameters).plan() lays the city out. It gives every tower's parameters and every placement of the furniture, and builds nothing. build() builds everything. It returns a City with one geometry a tower, the sidewalk, and the furniture as Instances.
- The city is a grid of blocks. A block is cut into lots inside a sidewalk strip.
- Each lot takes a skyscraper of its own seed, height and footprint. Only the four corner lots of a block cut a chamfer, toward their own corner.
- The furniture walks each curb edge: streetlights, street trees, a hydrant, sometimes a bench, pedestrians, parked cars and a few cars in the lanes. Two corners take a traffic signal, and every corner takes a litter basket.
- One seeded generator draws the towers lot by lot, then the furniture edge by edge, in three.js's order.
build_proxy(plan) returns three.js's buildProxy: one plain box a tower, for a global illumination bake.
The sidewalk needs a curb with a height. With a curb height of zero, City.sidewalk is none. three.js then builds a sidewalk with no instance.
SkyscraperGenerator(parameters).layout() returns every placement of every piece. build() bakes them into one geometry without an index. Every vertex has a partId for its zone, and the glass has the room behind it, roomCenter and roomSize.
The bake transforms normals as directions. A rigid placement rotates the normal without adding its translation. A scaled placement uses the inverse transpose of its linear part.
- The footprint is a rectangle with one corner cut at 45 degrees. Each edge is a face with a frame:
ualong the edge,vup,nout. - The tower has three tiers: a base, a shaft and a crown. The crown steps back by
setback_depthbays. - The ground floor is a row of shopfronts. On a few seeds it is a pointed-arch arcade,
ARCADE. - The seed fills each style field that is left empty: the footprint, the tier split, the piers, the reveals and the arches.
- The floor and the bay snap to the brick module, three tenths of a meter high and six tenths long.
Each piece is one merged geometry with a partId. build(placements) returns it as Instances, one matrix an instance. Each model stands on y equals zero and faces +z.
| Generator | Parts |
|---|---|
StreetlightGenerator |
metal, the lens |
TrafficlightGenerator |
metal, the red, amber and green lenses |
TrashcanGenerator |
the mesh, the rims, the trash |
BenchGenerator |
wood, iron |
HydrantGenerator |
the body, the bare caps |
StreetTreeGenerator |
the trunk, the leaves, the grate |
SidewalkGenerator |
a slab and a curb, two sets of instances |
CarGenerator().build(cars) deals each car a body: SEDAN, SUV or TAXI. The taxi's yellow takes the taxi. It returns one set of instances a body, with the linear paint of each car in values.
PersonGenerator().build(placements) deals each figure a pose, WALK or STAND, and its proportions. It returns the walking figures, then the standing figures. values holds each figure's seed.
loft(sections, closed, cap_start, cap_end) is three.js's LoftGeometry. It skins a surface through cross sections that have the same number of points.
- A closed section is a ring. Its seam is smooth.
-
ufollows the loft by distance, andvfollows each section by distance. - A cap is a flat polygon with a hard edge. It faces away from the surface.
| Type | Values |
|---|---|
PartId |
The zone of a vertex, zero to eight. part refuses another value. |
BaseStyle |
ARCADE, STOREFRONT
|
BodyType |
SEDAN, SUV, TAXI
|
Pose |
WALK, STAND
|
- The materials: the bark, the canopy, the terrain, the road, the buildings, the concrete, the granite and each piece of furniture. They are TSL node graphs. The generators return geometry and placements only.
-
createBuildingMaterial,buildingColorNode,createRoadMaterial,createTreeMaterialandcreateForestMaterial.pick_building_colorandbuilding_paletteare ported. - The shader sway of the street trees, and the distance fade of the forest's detail.
- The fluent
set<Param>methods ofTreeGenerator. Set the parameter fields. - The lifecycle of three.js's meshes:
dispose, and the reuse of a mesh when a city is built again. Each build returns new values. - The street tree jitters its leaf clumps by a hash of the
Float32positions. The hash turns a difference of one bit into a different jitter. So the lobes of the canopy can differ from three.js's where a primitive rounds a vertex differently.