A generator makes many parts from rules and a seed instead of one line per part: twelve lanterns along a walkway, ninety trees in a forest, three hundred ferns round them, a meadow of grass that is never held as parts at all. This page covers the three kinds (a counted loop, an ordered scatter of rules, and a package that places a population from a parameter record), the seeds and random streams they draw from, and what the parts they make are called. Runtime copies (spawn) are the same mechanism at a different moment and are covered on Designs, copies and spawning; here they appear only where they meet generation.
Three kinds of generator
| Kind | What the world writes | Example |
|---|---|---|
| A counted loop | an each over a range or a fixed list, placing a copy of a design at each turn | the twelve path lanterns; the ten bowling pins in a rack of four rows |
| An ordered scatter | a list of rules taken in turn, each trying candidate spots, keeping some, and keeping clear of what earlier rules placed | the vegetation round the cottage: maples, conifers, rocks, hostas, flowers, ferns, edging stones, the brook's stones, groves |
| A parameter record | one call to a package naming kinds and the maps they read; the package places | the groundcover's twelve kinds of grass, flowers, moss and litter, placed every frame on the graphics card |
These are three levels of one idea: open logic (the loop and the scatter as written above), rules as data a package runs (the groundcover), and in between, rule records a package could run for the scatter too.
A counted loop
The simplest generator is an each in a part's declarations. Each turn declares a copy of a design, with its own number, place and differences. The loop is bounded: its count is written in it or its list is fixed in the content. A part's body may hold part declarations inside the braces of an each or an if, beside Cone statements such as let (Blocks: parts inside each and if).
part island
each pair in 0 ..< 6 {
each (k, side) in [(0, -1), (1, 1)] { // left, then right
let way = land.walkway;
let spot = way.spotAt(way.fromEnd(1.8 m + pair * 2.8 m), across: side * 1.5 m);
part lantern[2 * pair + k]: pathLantern, at: spot // a point on the ground
}
}
Twelve lanterns in six pairs, the first pair 1.8 m back from the walkway's end at the porch steps, the next every 2.8 m, each 1.5 m to its side of the center line, standing on the land. spotAt and fromEnd are questions the walkway answers, as any spine does (Sculpting: what a spine answers); the walkway is laid on the ground, so its spot is a point on the ground and the lantern stands there. A part declared in a loop is one of a numbered family: lantern[...] numbers each copy from the loop's values, the family is read as lantern and one of them as lantern[12].
A loop's copies need a parent. Here it is the island; it could be a group part of the generator's own (part pathLanterns), so that hiding the group hides all twelve. Which is a content choice.
part bowlingPins // a frameless holder: the pins' places are the world's
each k in 0 ..< 10 {
part pin[k]: ninepin, at: pinSpot(k), scale: 0.6 // the rack's k-th spot, on the ground
}
pinSpot(k), a function of the holder's, walks from the apex along the lane by the pin's row and across it by its place in the row, 0.72 m each, and stands the point on the land (Physics: bowling on the lawn). Each pin's spot is also its home, where a reset brings it back after physics has moved it (Frames: a part's home).
An ordered scatter
Every tree, rock, hosta, fern and flower round the cottage comes from one seed and a list of rules taken in order. Each later rule keeps clear of what earlier ones placed; how thick the forest grows is read from the land; the rules draw random numbers in turn from one stream. The scatter is the first place in a world where steps depend on each other's results, so it cannot be written as independent loops.
part island.vegetation // a generated population
field layout: List[placed] = scatter(over: land, keepClearOf: brookDiscs, rules: [
anchors, forest, rocks, hostas, flowers, ferns, edging, brookStones, ringExtras, groves])
field brookDiscs = discsAlongSpine(land.brook, every: 3 m, // 145 discs, two radii
radius: fn(along: meters) meters { if land.brook.indexAt(along) >= 18 { 3.2 m } else { 2.6 m } })
field main = randomStream(seed: 20260520, salt: 1) // drawn in turn by eight rules
field grove = randomStream(seed: 20260520, salt: 7) // the groves' own
field forest = rule(draws: main,
candidates: jitteredGrid(cell: 6 m, x: -230 m ..< 210 m, z: 300 m ..< 520 m),
keep: land.height(here) > 2.6 m && dressed(here) && land.slope(here) < 0.42
&& land.pathDistance(here) > 4.5 m && land.pathDistance(here) < 70 m
&& forestDensity(here) > 0.18,
order: land.pathDistance(here) + uniform(0 m, 10 m), // nearest the walkway first, roughly
spacing: 7 m + 6 m * (1 - forestDensity(here)),
test: spacing / 2, room: spacing / 2 - 0.4 m,
place: if uniform(0, 1) < 0.66 { conifer(variant: below(6), size: 0.75 + 0.55 * uniform(0, 1)) }
else { forestMaple(variant: below(3), size: 0.8 + 0.5 * uniform(0, 1)) },
atMost: 90)
field ferns = rule(draws: main,
candidates: ringsAround(trees(placedEarlier), count: 3 ..= 4, distance: 1.4 m ..= 4.8 m),
keep: land.height(here) > 2.6 m && dressed(here) && land.pathDistance(here) > 1.4 m
&& land.slope(here) < 0.45,
test: 0.5 m, room: 0.75 m, cottageMargin: 0.2 m, capGroup: ferns, atMost: 220)
...
The names scatter, rule and randomStream are illustrations of a plain function and its named options; there is no scatter package. jitteredGrid, ringsAround, discsAlongSpine, uniform and below are defined below. A field that holds a rule or a stream is a named value nothing changes while the world runs. turn: is left out of the places above, so each tree takes the default, a turn drawn uniformly over a whole turn.
The land's questions the rules ask are methods of the land (Terrain): land.pathDistance(here) and land.brookDistance(here), forestDensity(here) and groveDensity(here) (each a fraction), and dressed(here), true inside the dressed area. On the island pathDistance is kept only to 16 m, so the forest's test below 70 m (and the rocks' below 60 m) is always true there; the rule is written as the author meant it, and a land that answered farther would make it bite.
The parts of a rule
- candidates
- where the rule may try, a list of spots made by one of the candidate makers below, or a list written out (next entry but one).
- keep
- which tries survive: a condition over the land, the walkway, the brook and density fields, asked at each spot. It may draw from the stream:
keep: uniform(0, 1) < 0.06 + 2.2 * (land.slope(here) - 0.1)is how the island's rocks favor steep ground. - order
- in what order the kept tries are placed. Because two trees may want the same room, the order decides which wins, so it is part of the meaning.
- spacing, test, room
- a circle that must be clear for a thing to go in (the test), and the smaller circle it then claims for later rules to keep out of (the room). Both may be expressions of a field, so thickness follows where the author painted it.
test: noneskips the test altogether (the island's foundation hostas and flower beds, which stand where the author wrote them). - size
- a size drawn before the test is made once, and the test, the room and the place may all read it:
size: 0.5 + 1.6 * uniform(0, 1) * uniform(0, 1), test: 0.5 * size, room: 0.55 * size. A rule that sets no size before the test draws it inplace. - place
- what goes there:
kind(variant:, turn:, size:, sink:, height:).kindis the design placed.variant:is any expression that gives a variant number (below(6),k % 2 * 3, a species plus an age).turn:is an angle, uniformly drawn over a full turn when left out.size:is the scale.sink:is how far it is pressed into the ground, as a share of its size (rocks 0.2, edging stones 0.025).height:is where its foot stands, the land's height there when left out; a stone in the brook gives the water's. - atMost
- a cap on how many this rule places. A cap shared by several rules is
capGroup: name: every rule naming the group counts the things of the group, wherever placed, and stops when the count reaches its ownatMost. The group is named once,capGroup(ferns, atMost: 220), and a rule that may go beyond it says so (the brook's ferns,capGroup: ferns, atMost: 290). - blocks
blocks: falseon a rule: what it places keeps clear of the things already placed, but later rules ignore it. The island's edging stones are so: they keep clear of the others, and the others stand over them.- cottageMargin
- the room a rule keeps from the cottage's footprint; see "What a scatter means". The default is the cottage's own
clearance. - draws
- which random stream the rule takes its numbers from, or none.
Candidate makers
Each is a plain function that returns a list of spots; all draw from the rule's stream, in the order the table gives, before any test is made.
| Call | The spots |
|---|---|
jitteredGrid(cell: meters, x: range, z: range) | one spot in each square of the grid: the square's corner moved by a draw of cell in x, then in z. The squares are taken a row of x at a time, from the smallest z, and every square draws, whether or not its spot is kept |
ringsAround(things, count: range, distance: range) | for each of the things, count spots (a draw), each at a drawn angle and a drawn distance from it. They are single random spots in a ring, not a ring of spots. A rule that has reached its cap draws nothing for the next thing. The things are those placed when the call began; spots made now are not rings' centers |
randomPointsIn(outline, tries: count) | up to tries spots, each a draw in x then in z inside the outline's box, taken one at a time until the rule's cap is reached or the tries are spent (the island's rocks: 400 tries about where you land, 3000 over the forest) |
spotsAlongSpine(spine, step:, sides:, across:) | spots at distances along the spine, each step after the last (a fixed length, or an expression that draws: 0.7 m + 0.4 m * uniform(0, 1)), standing across from the spine on the side the rule says: bothSides (one on each side at every step), alternatingSides (the side changes at every step) or randomSide (a draw at every step). The island's edging stones, the path rocks, path hostas, flowers and lane ferns are made so |
discsAlongSpine(spine, every: meters, radius: meters or fn(along)) | not spots but fixed discs, one every every meters along the spine, each of the radius (or of the radius the function answers for its distance): what a scatter keeps clear of. The brook's: every 3 m, 2.6 m wide, 3.2 m in the dingle |
A spine answers headingAt(along), the direction it runs at that distance, so a rule can read how sharply the path bends. It accepts distances below 0 and past its end: it extends its two ends straight (Shape tools: what a spine answers).
A rule over a written list takes a list of spots as its candidates. The rule's function gets the number of the spot, k, and may move each spot by a draw: candidates: foundationSpots.map(fn(k, p) { p + (0.3 m * uniform(-1, 1), 0 m, 0.3 m * uniform(-1, 1)) }), a jitter of 0.3 m either way. The ring extras, the specimens and the foundation hostas are so.
What a scatter means
- Rules run in order, each clear of the earlier. A thing placed by a later rule may not overlap anything placed by an earlier one (unless that rule has
blocks: false), nor the room any part states it needs. Putting hand-placed anchors first is how an author says "these matter most". The groundcover package has the same relation between its own kinds (givesWayTo), which suggests it belongs in the model. - A part states the room it needs: the part option
clearance:(Parts: options). A scatter keeps clear of the clearance of every part, so the author states it once, on the part, and not in every rule. The island's: the amphora 1.2 m, the horn-flower 1.6 m, the ball 1 m, the launch spot 1.5 m, the balloon 12 m, each lantern 0.7 m, each end of the bridge 2.2 m, each of the six points of the lawn route 1.5 m. A part with a place is kept clear of by a disc of that radius round it. - The cottage's footprint is its outlines grown by its
clearance:. The cottage names the outlines it stands on, on the plan (footprint, a list of rectangles: the main block with the chimney, the wing, the porch deck and the steps), and statesclearance: 1.2 m. A thing of radiusris clear of it when it lies outside every rectangle grown byrplus the margin on all four sides (a square grow, not a round one). A rule may pass its own margin in place of the part's:cottageMargin: 0.2 m. The island's rules use 0.15 m (the foundation hostas), 0.2 m (the path hostas, the ferns), 0.1 m (the flowers) and 0.6 m (the brook's lip stones); the rest take 1.2 m. The edging stones also ask for 0.6 m but keep clear of the default as well, so their margin is in effect 1.2 m. - The product is a list.
layoutis aList[placed], each entry holding its kind, variant, place, turn, size and the room it claims. Parts are made from it witheach (i, thing) in layout { part tree[i]: thing.kind, ... }, numbered in the list's order. The numbers are stable only while the rules and the seed are: one more rock tried changes every number after it, and so which tree istree[40]. Nothing should keep a number across an edit of the rules. - Candidates may be the results of earlier rules. "Ferns under the trees" is a relation between two kinds in the population, not to anything fixed.
- Density is read from a field. The author paints where the forest is thick once (Terrain), and the rule turns it into trees.
- The scatter is derivation. It reads the walkway, the cottage, the lanterns and the brook; if any moves, the scatter runs again. Everything that needs the vegetation (drawing, collision solids, the groundcover's shade, the navigation mesh, the minimap, the brook's sound) reads the one list.
- It runs at build, once, after the land, on the server and in every browser alike; the list is never sent, only the seed.
- A cap is a requirement, not a resource limit. "A forest of about ninety trees" is the author's; "no more than ninety because of a slow graphics chip" is the engine's. Content should be able to state the first without the second.
Seeds and random streams
Randomness in construction comes only from seeds, so every machine makes the same world from the same description. Two kinds of randomness are in use, and they behave differently under editing:
| A sequential stream | A hash per place | |
|---|---|---|
| How | a state advanced by a fixed step and hashed, started from the seed mixed with a salt; each uniform(...) takes the next number | the number for a spot is a hash of the spot's cell and the kind's seed |
| Example | the scatter (salt 1 for eight rules, salt 7 for the groves) | the groundcover; noise with a seed in open functions |
| One more rock tried | moves every number every later rule draws, and so the whole layout after it | moves nothing else |
| Order of asking | matters | does not: points may be asked in any order, on any core |
Stable editing is a requirement: an author changing the rocks should not find the ferns rearranged. A stream per rule, seeded by the world's seed and the rule's name, meets it; one stream shared by eight rules, as in the example above, does not.
The stream, exactly
A randomStream(seed:, salt:) is defined to the bit, so every machine and every version draws the same numbers. All arithmetic is on 32-bit whole numbers that wrap round (a result past 4 294 967 295 starts again from 0).
pcg(v) = { let s = v * 747796405 + 2891336453;
let w = ((s >> ((s >> 28) + 4)) ^ s) * 277803737;
(w >> 22) ^ w }
start = pcg(seed ^ (salt * 0x85ebca6b)) // the stream's state
next() : state = state + 0x9E3779B9; pcg(state) // the whole number drawn
uniform(0, 1) = (next() >> 8) * 2^-24 // from 0 up to, not including, 1; the top 24 bits
uniform(a, b) = a + (b - a) * uniform(0, 1)
below(n) = next() % n // a whole number from 0 up to n - 1
The island's seed is 20260520. A call to uniform or below in the rule's expressions takes the next number from the rule's stream (draws:), reading arguments left to right. The order of draws within a rule is part of the layout, and is this:
- the candidates, all of them, cell by cell, before any is tested;
- for each candidate in turn, the draws in
keepand inorder, only when the tests before them in the expression pass (an expression stops at its first false test); - a size drawn before the test, when the rule has one;
- then, for each candidate in the placing order that passes its test: the kind (when the rule picks one by a draw), the variant, the turn, the size. A candidate that fails the test draws nothing here, so a later candidate's numbers shift when an earlier one fails.
The same layout then comes from the same seed on every machine. A rule with draws left out takes no numbers, so nothing else moves when it changes (the ring extras).
A part placed by hand may carry a seed of its own: rocks(seed: 12, width: 1.4) (illustration). A loop's copies take one from the loop (random(seed, i)). The packages' generators each take a seed in their parameter record: noiserock's RockParams.seed (a rock is a pure function of its params and seed, so one RockParams and a few seeds make a family), leafrosette's hosta, fern and flower records, and weberpenn's species presets, which take a seed and a height.
The variants: a few grown, many placed
A scatter does not grow each tree it places. Each kind is grown into a few variants at build, and every placed item is a copy of one: specimen maples two, forest maples three, conifers six, rocks six, edging stones four, hostas and ferns two each, flowers six. The growing is the packages' (weberpenn, noiserock, leafrosette); the placing is the scatter's. A copy differs from its variant only by its frame (a turn and a size), so it shares the variant's mesh: sharing nobody can see, which is the browser's resource choice, not meaning (Materials).
part islandSpruce
field height: meters, within: 5 ..= 15
field seed: seed
field age: fraction
part tree: weberpenn.spruceTree(seed, height, bare: 0.04), bark: (maxLevel: 1, subdivide: 1, fatSides: 7, midSides: 4),
tint: weberpenn.ageTint(age, vary: 1, seed: seed)
foliage(self, weberpenn.spruceFoliage(seed, cardScale: cardSize(height)), material: materials.needles)
part forestMaple // illustration: a maple without third-level limbs
field seed: seed
field params = override(weberpenn.redMaple(seed, height: 8.6 m), path: levelParams[2].branches, value: 0)
part tree: params, bark: (maxLevel: 2, subdivide: 1, fatSides: 7, midSides: 4)
foliage(self, weberpenn.mapleFoliage(seed, leafSize: 0.34, detail: 5, sway: true), material: materials.mapleLeaf)
The tree package (weberpenn) grows a skeleton of stems and makes the bark and the foliage as two meshes. Its calls, as it names them:
| Call | What it makes |
|---|---|
redMaple(seed, height) | a four-level maple of the given height |
mapleFoliage(seed, leafSize:, detail:, sway:) | maple leaves as cards; leafSize in meters (0.2 on the specimens, 0.34 on the forest maples); detail the points round a leaf's outline (7 and 5); sway: true makes each leaf carry the pivot its material's fluttering turns it about |
spruceTree(seed, height, bare), firTree(seed, height, bare) | conifers; bare is the fraction of the trunk bare of limbs (0.04 to 0.3) |
spruceFoliage(seed, cardScale), firFoliage(seed, cardScale) | needle branchlets as cards; the island uses cardScale = (height / 20 m) to the 0.45, between 0.35 and 1 |
ageTint(age, vary, seed) | a color factor for needles: from about (2.7, 3.0, 1.0) at age 0 to (0.78, 0.86, 0.92) at age 1, each channel moved by up to 18% of vary by the tree's seed. Its components pass 1 (Values: colors) |
A nested override changes one field deep in a record without writing the rest: override(params, path: levelParams[2].branches, value: 9) is params with the branch count of its third level of limbs set to 9. The path names the fields down to the one changed. bark: is an option of a tree part: the tube settings for the bark, maxLevel (how many levels of limbs are drawn as tubes), subdivide, fatSides and midSides (the sides of the thick and the middle limbs; the thin have 3), with tileMeters, subRadius, fatRadius, midRadius and sink beside them. foliage(self, foliageRecord, material: m) names the material the leaves are drawn in; two trees that name the same material share it. tint: on the tree is the part's tint (Materials: tint). cardSize is a world's own function (Open functions).
Two ways to make many things
The designer chooses, per population:
- Named parts from a loop. A loop that writes
partlines makes real parts, each named from the loop's values (lantern[2 * pair + side], or a tree named by the cell it stands in). Each can carry its own fields, so a felled tree stays felled; regions keep a large population cheap, waking the detail only near the visitor (Worlds, scenes and regions). The names are the designer's, so they stay as stable as the designer makes them. - A tool's bulk product. A tool such as the groundcover below, or an effect's sparks, makes things with no identity at all: they are drawn, not named, and cannot be singled out.
- Ownership follows the path either way. Removing or hiding the generator's part removes or hides the whole population, as for any part.
- Parts made while the world runs fold into the same rule: the set of a part's children may change, and that set is state of the parent, declared like any other; a splat on a wall is
westWall.splat[17], its frame and ownership the parent's, its lifetime a handler or a duration in a named clock.
part boss // illustration
part army // empty at load; filled at run time
on(became(health < 0.5), {
each i in 0 ..< 20 {
spawn(troll(stance: oneOf([guard, charge, flank], seed: i)),
under: army, at: ring(self, 6 m, i / 20)); // army.troll[0] ... troll[19]
}
})
A load-time loop and a runtime spawn differ only in when they run; the spawn is an act and lives in an on(...), the loop is a declaration and is build (Loading).
A population never held as parts
Beneath the scatter lies a second population that is never a list of parts: the groundcover's twelve kinds of grass, flowers, moss, needles, cones and twigs. The world states the kinds and the maps they read; the package works out every frame, on the graphics card, where each tuft stands near the camera, and draws it.
part island.groundcover: groundcover.cover(maps: ground, kinds: [ferns, cones, twigs,
lawn, meadow, tussock, marram, daisies, buttercups, vetch, moss, needles])
field ground = groundcover.maps(square: 1424 m, texel: 2 m,
height: land.height, slope: land.slope,
moisture: moistureMap, exposure: exposureMap, // 0 dry to 1 wet; 0 shade to 1 sun (below)
path: p => land.pathDistance(p) - 0.9 m, // from the walkway's edge, not its middle
water: p => min(shore(widthPerHeight: 11), land.brookDistance(p) - 1.2 m),
canopy: crowns(island.vegetation), // a disc of shade under each tree
object: discs(island.vegetation, cottage.footprint, bridge.ends),
farColor: farColorMap) // the ground's color seen from far (below)
field ferns = groundcover.meshKind(fern, seed: 41, perCluster: 3, range: 50 m, fadeStart: 36 m, maxInstances: 6000,
wind: 0.5, canopy: atLeast(0.3, soft: 0.3), moisture: atLeast(0.25, soft: 0.15),
slope: atMost(35 deg, soft: 10 deg), object: atLeast(0.6 m, soft: 0.4 m), ...)
field lawn = groundcover.grass(seed: 21, wind: 0.8, canopy: atMost(0.55, soft: 0.4),
path: rising(0 m, 0.7 m), ..., givesWayTo: near(ferns, within: 0.8 m, strength: 0.7))
- Rules are data, not code. "Where it grows" is a set of windows on the maps, each with a soft edge; a kind's score at a spot is the product of its windows, so it fades in and out instead of stopping at a line;
givesWayTonames a kind to avoid. - Derivation between populations. The canopy map is painted from the trees the scatter placed; move a tree and the moss under it moves.
- Per viewer, and cosmetic by nature. No blade has an id, a path or state. Where a tuft stands is a hash of its cell and its kind's seed, the same whichever way the camera came; but the graphics card's arithmetic differs slightly from the main processor's, so two machines place a tuft a hair apart. No rule may depend on where a flower stands.
- Content states the detail it needs, as requirements. Each kind says how far it must be drawn (
range:), where its last stretch thins out (fadeStart:), the outer radius of each ring of finer detail (rings:andringEnd:) and the most instances it needs (maxInstances:). The engine may do better than this, never worse; what it does on a weak graphics chip (the island's reach cut to 0.62 and its densities to 0.4) is its own business.
The maps the cover reads
groundcover.maps takes a square of the land, a texel size (2 m), and one map for each signal below. Each is a number for every texel; the distances stop at 64 m.
| Map | Meaning, as the island works it out |
|---|---|
height, slope | meters; degrees |
moisture (0 dry to 1 wet) | from the distance to water: 0.22 + 0.78 * exp(-water / 11 m), times 1 - 0.3 * clamp(slope / 45 deg, 0, 1); then a patchy noise of ±0.17 (0.34 * (noise - 0.5) with a feature size of 28 m), clamped to 0 to 1 |
exposure (0 shade to 1 sun) | how squarely the ground faces the sun, dot(normal, toSun) / toSun.y with toSun = (-0.42, 0.76, 0.5), clamped to 0 to 1, then lessened by shade: times 1 - 0.6 * canopy |
path, water, object | meters to the walkway's edge (its middle less 0.9 m); to the water (the sea by depth times 11, the brook's middle less 1.2 m); to the nearest thing that stands (a trunk's disc of 0.3 of its size, a rock's own radius, the cottage as discs of 1.2 m 1.6 m apart, the bridge ends as discs of 1.4 m) |
canopy (0 open to 1 shaded) | under each tree, density * smoothstep(1 - distance / reach), the larger of overlapping crowns kept. A tree's crown reach and density are read from the tree: a conifer reaches (0.9 + 0.2 * its height) times its size, density 0.92; a forest maple 4.4 times its size, 0.7; a specimen maple 5.6 times its size, 0.75 |
farColor | the color the ground shows from far, a color for every texel: red (0.10 + 0.40 * sand) * dim, green (0.21 + 0.19 * sand) * dim, blue (0.035 + 0.185 * sand) * dim (linear numbers), where sand is 1 below 2.2 m and 0 above 3.0 m (a smooth fall between) and dim is 1 - 0.5 * canopy |
The cover types: options
A kind is made by one of five constructors (grass, flowerKind, patchKind, litterKind, meshKind), each with its own defaults, and then named options. The package's option names are these, with the defaults of the plain constructors:
| Group | Options (default) |
|---|---|
| Counts | seed (1), density (1 an m²), perCluster (1, at most 16), clusterRadius (0), cellFactor (0.5) |
| Windows (each open until set) | height, slope, moisture, exposure, path, water, canopy, object: each is one of the window calls below |
| A patchy gate | patchScale (0), patchThreshold (0.5), patchSoftness (0.15), patchFloor (0) |
| Giving way | givesWayTo: a list of near(kind, within: meters, strength: fraction); a kind may name only an earlier kind. The island's: the lawn 0.8 m at 0.7, the meadow 1.2 m at 0.8, the tussock 1.0 m at 0.8, all round the ferns |
| Size and shape | sizeMin (0.3 m), sizeMax (0.5 m), sizeNoise (0.3), widthMin (0.012 m), widthMax (0.018 m), lean (0.3), bend (0.5), bladesPerTuft (12) |
| Color | baseColor (0.08, 0.2, 0.03), tipColor (0.35, 0.5, 0.12), altColor (0.45, 0.4, 0.1), variation (0.35), dryColor (0.55, 0.5, 0.2), dryAmount (0.25), translucency (0.5), flower (1, 1, 1), flowerCenter (1, 0.8, 0.1), petals (5). The package spells these with an American color; they are linear numbers |
| Wind | wind (1): a multiplier on the gusts the weather gives (Sky and weather). The island's: lawn 0.8, meadow 1, marram 1.3, ferns 0.5, flowers 0.6, moss, needles, cones and twigs 0 |
| Mesh kinds only | alignment (0), sink (0), meshes (one registered shape for each ring), albedoNoise (0) |
| Reach | range (60 m), fadeStart (45 m), rings (1, at most 3), ringEnd ([15, 40, 60] m), ringStep (1.8), maxInstances (100 000), farWeight (0) |
Windows are written atLeast(lo, soft) (1 from lo up, rising over soft below it), atMost(hi, soft) (1 up to hi, falling over soft above), between(lo, hi, soft) (both), rising(zeroAt, fullAt) (0 up to the first number, 1 from the second, a smooth rise between: path: rising(0 m, 0.7 m) is none on the walkway's edge and whole 0.7 m out) and falling(fullAt, zeroAt) (the mirror). A kind's score at a spot is the product of its windows, so any one at 0 rules it out. Every soft is the width of the soft edge, in the window's own unit (slope: atMost(35 deg, soft: 10 deg)).
A cover kind may take any shape, not only the fern: meshKind takes the shapes a world registers, one for each ring of detail. The island's ferns are the leaf-rosette fern, its fallen cones a small lathed tube bellied toward the middle, its twigs a thin bent tube (Shape tools: lathe and sweepScaled).
The island's twelve kinds, with the windows they use and how far they reach:
| Kind | Windows | range, fadeStart, rings (ringEnd), maxInstances |
|---|---|---|
| ferns | canopy atLeast(0.3, 0.3); moisture atLeast(0.25, 0.15); slope atMost(35, 10); path rising(0.5, 1.4); water rising(0.6, 1.4); object atLeast(0.6, 0.4) | 50 m, 36 m, 1, 6000 |
| cones | canopy atLeast(0.5, 0.3); path rising(0.4, 1.1); slope atMost(40, 10); object atLeast(0.3, 0.3) | 24 m, 16 m, 1, 5000 |
| twigs | canopy atLeast(0.4, 0.3); path rising(0.4, 1.1); slope atMost(40, 10); object atLeast(0.3, 0.3) | 30 m, 20 m, 1, 3000 |
| lawn | height atLeast(2.9, 0.6); slope atMost(40, 10); canopy atMost(0.55, 0.4); exposure atLeast(0.1, 0.3); path rising(0, 0.7); water rising(0.15, 0.9); object rising(0, 0.35) | 50 m, 36 m, 3 (7, 20, 50 m), 70 000 |
| meadow | height atLeast(3.2, 0.6); slope atMost(36, 10); canopy atMost(0.3, 0.3); exposure atLeast(0.15, 0.3); path rising(2, 5); water rising(0.5, 1.5); object atLeast(5, 3) | 60 m, 44 m, 3 (9, 26, 60 m), 30 000 |
| tussock | height atLeast(2.8, 0.5); moisture atLeast(0.55, 0.2); slope atMost(30, 8); canopy atMost(0.5, 0.3); water rising(0.4, 1.2); path rising(0.6, 1.5); object atLeast(0.8, 0.5) | 45 m, 32 m, 2 (14, 45 m), 10 000 |
| marram | height between(1.7, 3.3, 0.6); slope atMost(32, 10); path rising(0.5, 1.4); object atLeast(0.5, 0.4) | 55 m, 40 m, 2 (16, 55 m), 14 000 |
| daisies, buttercups | height atLeast(3.2, 0.6); slope atMost(25, 8); canopy atMost(0.15, 0.2); moisture atLeast(0.25, 0.2) (buttercups 0.4, 0.15); path rising(0.5, 1.2); water rising(0.5, 1.3); object atLeast(1.4, 0.8) | 42 m, 30 m, 1, 12 000 |
| vetch | height atLeast(3.2, 0.6); slope atMost(30, 8); canopy atMost(0.3, 0.3); path rising(0.5, 1.2); water rising(0.5, 1.3); object atLeast(1.4, 0.8) | 42 m, 30 m, 1, 9000 |
| moss | canopy atLeast(0.3, 0.3); moisture atLeast(0.3, 0.2); path rising(0.2, 0.8) | 35 m, 24 m, 1, 10 000 |
| needles | canopy atLeast(0.35, 0.3); slope atMost(45, 10); path rising(0.1, 0.7) | 18 m, 12 m, 1, 50 000 |
The tree of parts must therefore allow a subtree that is only a description, made real per viewer.
Other generators in the packages
Packages hold further generators a world could name: morphogen's space colonization (growth toward a cloud of attraction points) and its capsule chains; and boidflock, Reynolds' steering rules for birds, each following its seven nearest neighbors, given the ground height as a function. The birds it flies are not parts in any tree. A flock is a behavior as much as a generator (Package behaviors).
How it connects
- Construction pipelines: a generator is a pipeline whose product is many parts.
- Designs, copies and spawning: copies and
spawn. - Terrain: the land, the walkway and the density fields a scatter reads.
- Open functions: card sizes and crown reaches per tree.
- Control flow:
each, ranges, bounded running. - Physics: solids derived for trunks and rocks from the scatter's list.
Open questions
- One kind of randomness. The model should choose sequential streams or hashes per place, and say the seed includes the rule's name.
- Ordered rules as a relation. "Keep clear of earlier" needs each placed thing to carry a room and the rules an order; the model has pipelines, not populations whose steps depend on each other.
- A population's parent: a group part the generator makes, the part the population stands in, or the content's choice.
- Requirements and caps mixed: a cap is a requirement ("about ninety trees"); what a slow graphics chip may cut is the engine's. Whether a world should be able to state a cap that is only a resource limit is open.
- Stable numbers for a layout's parts: parts numbered by list position change number when a rule is edited. Whether a part should be named by something that survives an edit (the cell it stands in) is open.
- A subtree that is only a description, made real per viewer (the cover): where it sits in the tree.