Tree generator
Grows a procedural tree in three.js: oak, ash, aspen, pine or bush, at any age and season, swaying in the wind in a small world with sky, shadows, grass and rocks.
It's ez-tree by Dan Greenheck (MIT), steered by Patch's block controls. Every tree comes from a species preset, a seed and about 50 options (branch levels, angles, gnarl, twist, leaf count and tint, bark), so the same settings always grow the same tree. The live demo on this page shows one tree; drag in it to look around and scroll to zoom.
Use it in a game
The tree is ez-tree's Tree, a THREE.Group you add to any three.js scene:
Call applyTreeTextures(tree) from ez-tree/app/textures.js before tree.generate(), or it draws grey squares. That also needs urls.js and the @dgreenheck/ez-tree alias from vite.config. It ran the same on three 0.186.1 as on 0.170.
import { Tree } from '@dgreenheck/ez-tree'; // here: ez-tree/lib/index.js
const tree = new Tree();
tree.loadPreset('Oak Medium'); // or Ash, Aspen, Pine (Small, Medium, Large), Bush 1 to 3
tree.options.seed = 1234;
tree.options.leaves.tint = 0xffd9a0; // any option in ez-tree/lib/options.js
tree.generate();
scene.add(tree);
// every frame, for the wind
tree.update(elapsedSeconds);
- Bark and leaf textures load from
textures/(ez-tree/app/textures.js,applyTreeTextures). Ship the bark sets you use; each is about 4 MB at 1K. - The world around it (sky, ground, grass, rocks, clouds) is ez-tree's demo world,
ez-tree/app/environment.js. Use it as a starting point or leave it out. - Forests: generate a few trees per species with different seeds and instance or clone them;
generate()costs about 10 ms for an Oak Medium, but around 100 ms for a big one (a hundred branches off the trunk, three levels deep). Grow those in a worker, asdemo.jsdoes withtree-worker.js, so no frame stalls. demo.jsshows how each control maps onto ez-tree's options (options.jsonsays which path each sets), including the relative ones (age, leaves, gnarl) that carry across species.
Controls
The block page talks to the demo over the patch-block message channel (lib/patch-block.js). Setting species or
size loads that preset and resets the options and seed; the demo then sends every value back in a state.
For people (the block page shows these):
| id | Control | Type | Range | What it does |
|---|---|---|---|---|
age | Age | number | 0.05 to 1, default 1 | Sapling to full grown. The same seed grows the same tree. |
season | Season | choice | Spring, Summer, Autumn, Winter; default Summer | Recolours the leaves. Bare in winter, except pines. |
leaves | Leaves | number | 0 to 2, default 1 | How full the crown is. |
wind | Wind | number | 0 to 1.5, default 0.5 | How hard the wind moves it. It runs in the shader, so it costs nothing. |
On the page, for people: Tree (species), Size (size), Age, Season and Wind, with Grow and New tree. Every other control above is for agents.
Triggers: Grow (grow): Grows the tree from a sapling to its age, in two and a half seconds; New tree (reroll): A new seed: the same species, grown differently.
For agents (60 more, in recipe.json with every range and default):
| id | Control | Type | Range | What it does |
|---|---|---|---|---|
species | Species | choice | Oak, Ash, Aspen, Pine, Bush; default Oak | Loads that species' settings; the size picks small, medium or large. Resets the options below. |
size | Size | choice | Small, Medium, Large; default Medium | Small, medium or large of the species (a bush has three shapes). |
seed | Seed | number | 0 to 65535, default 35729 | The same species, seed and options always grow the same tree. |
gnarl | Gnarl | number | 0 to 2, default 1 | How twisted the branches grow, relative to the species: 0 is straight, 1 the species' own, 2 twice as wild. |
skeleton | Skeleton | toggle | default off | Shows the branch skeleton (trunk pink, branches white, twigs grey) over a faded tree. |
rotate | Turn slowly | toggle | default off | Turns the camera slowly around the tree. |
sun | Sun height | number | 2 to 89 °, default 30 | How high the sun is, in degrees above the horizon. |
type | Kind | choice | Deciduous, Evergreen; default Deciduous | Evergreens keep one leader and taper to a point. |
branch.levels | Branch levels | number | 0 to 3, default 3 | 0 is a bare trunk. |
branch.children.0 | Children: trunk | number | 0 to 100, default 6 | How many children each branch grows (trunk) |
branch.children.1 | Children: branches | number | 0 to 10, default 4 | How many children each branch grows (branches) |
branch.children.2 | Children: twigs | number | 0 to 10, default 3 | How many children each branch grows (twigs) |
branch.angle.1 | Angle: branches | number | 0 to 180, default 54 | Degrees from the parent (branches) |
branch.angle.2 | Angle: twigs | number | 0 to 180, default 58 | Degrees from the parent (twigs) |
branch.angle.3 | Angle: leaf stems | number | 0 to 180, default 32 | Degrees from the parent (leaf stems) |
branch.start.1 | Start: branches | number | 0 to 1, default 0.49 | How far along the parent the children begin (branches) |
branch.start.2 | Start: twigs | number | 0 to 1, default 0.06 | How far along the parent the children begin (twigs) |
branch.start.3 | Start: leaf stems | number | 0 to 1, default 0.12 | How far along the parent the children begin (leaf stems) |
branch.gnarliness.0 | Gnarliness: trunk | number | -0.5 to 0.5, default 0 | How much each section wobbles (trunk) |
branch.gnarliness.1 | Gnarliness: branches | number | -0.5 to 0.5, default -0.1 | How much each section wobbles (branches) |
branch.gnarliness.2 | Gnarliness: twigs | number | -0.5 to 0.5, default -0.15 | How much each section wobbles (twigs) |
branch.gnarliness.3 | Gnarliness: leaf stems | number | -0.5 to 0.5, default 0.09 | How much each section wobbles (leaf stems) |
branch.twist.0 | Twist: trunk | number | -0.5 to 0.5, default -0.23 | Twist around the branch per section (trunk) |
branch.twist.1 | Twist: branches | number | -0.5 to 0.5, default 0.42 | Twist around the branch per section (branches) |
branch.twist.2 | Twist: twigs | number | -0.5 to 0.5, default 0 | Twist around the branch per section (twigs) |
branch.twist.3 | Twist: leaf stems | number | -0.5 to 0.5, default 0 | Twist around the branch per section (leaf stems) |
branch.taper.0 | Taper: trunk | number | 0 to 0.95, default 0.73 | How much thinner the tip is than the base (1 would end in a point, which ez-tree cannot grow on) (trunk) |
branch.taper.1 | Taper: branches | number | 0 to 0.95, default 0.42 | How much thinner the tip is than the base (1 would end in a point, which ez-tree cannot grow on) (branches) |
branch.taper.2 | Taper: twigs | number | 0 to 0.95, default 0.69 | How much thinner the tip is than the base (1 would end in a point, which ez-tree cannot grow on) (twigs) |
branch.taper.3 | Taper: leaf stems | number | 0 to 0.95, default 0.75 | How much thinner the tip is than the base (1 would end in a point, which ez-tree cannot grow on) (leaf stems) |
branch.force.strength | Growth force | number | -0.1 to 0.1, default 0.02 | Pulls the tips up. Below zero they droop. |
branch.length.0 | Length: trunk | number | 0.1 to 100, default 37.24 | Length at full growth (trunk) |
branch.length.1 | Length: branches | number | 0.1 to 100, default 11.08 | Length at full growth (branches) |
branch.length.2 | Length: twigs | number | 0.1 to 100, default 12.39 | Length at full growth (twigs) |
branch.length.3 | Length: leaf stems | number | 0.1 to 100, default 7.16 | Length at full growth (leaf stems) |
branch.radius.0 | Radius: trunk | number | 0.01 to 5, default 1.41 | Radius at full growth (trunk) |
branch.radius.1 | Radius: branches | number | 0.01 to 5, default 0.9 | Radius at full growth (branches) |
branch.radius.2 | Radius: twigs | number | 0.01 to 5, default 0.69 | Radius at full growth (twigs) |
branch.radius.3 | Radius: leaf stems | number | 0.01 to 5, default 1.19 | Radius at full growth (leaf stems) |
branch.sections.0 | Sections: trunk | number | 1 to 20, default 8 | Rings along each branch; more is smoother and costs more (trunk) |
branch.sections.1 | Sections: branches | number | 1 to 20, default 6 | Rings along each branch; more is smoother and costs more (branches) |
branch.sections.2 | Sections: twigs | number | 1 to 20, default 3 | Rings along each branch; more is smoother and costs more (twigs) |
branch.sections.3 | Sections: leaf stems | number | 1 to 20, default 1 | Rings along each branch; more is smoother and costs more (leaf stems) |
branch.segments.0 | Sides: trunk | number | 3 to 16, default 7 | Sides around each branch (trunk) |
branch.segments.1 | Sides: branches | number | 3 to 16, default 5 | Sides around each branch (branches) |
branch.segments.2 | Sides: twigs | number | 3 to 16, default 3 | Sides around each branch (twigs) |
branch.segments.3 | Sides: leaf stems | number | 3 to 16, default 3 | Sides around each branch (leaf stems) |
leaves.type | Leaf | choice | Ash, Aspen, Oak, Pine; default Oak | Which leaf texture the cards use. |
leaves.count | Leaves per twig | number | 0 to 100, default 18 | |
leaves.size | Leaf size | number | 0 to 10, default 2.5 | |
leaves.size_variance | Leaf size variance | number | 0 to 1, default 0.7 | |
leaves.angle | Leaf angle | number | 0 to 100, default 42 | Degrees from the twig. |
leaves.start | Leaf start | number | 0 to 1, default 0.16 | How far along a twig leaves begin. |
leaves.billboard | Leaf cards | choice | Single, Double; default Double | Double crosses two cards so the crown looks full from every side. |
leaves.alpha_test | Leaf cut-out | number | 0 to 1, default 0.5 | |
leaves.tint | Leaf tint | color | default #d5d5cd | Multiplied into the leaf texture (seasons other than summer replace it). |
bark.type | Bark | choice | 11 options; default Bark 001 | ambientCG bark set (CC0). |
bark.tint | Bark tint | color | default #fff3d1 | |
bark.textured | Bark texture | toggle | default on | Off shows the bare tint. |
bark.flat_shading | Flat shading | toggle | default off |
Build it
npm install
npm run build # writes recipe.json and options.json from ez-tree's presets, then dist/ (56 files, ~49 MB)
dist/ is the live demo exactly as released, with index.html at the top. Every file loads from the release
itself: ez-tree's root paths are rewritten to relative ones (urls.js), and its rocks' Draco decoder, which
upstream loads from Google's CDN, is copied from three.js into dist/draco/.
| Path | What |
|---|---|
demo.js, tree-worker.js, index.html, urls.js | The demo page and the code that steers ez-tree (Patch, MIT); trees grow in tree-worker.js, off the page's thread |
recipe.json, options.json, make-recipe.mjs | The release recipe (mode browser with its block section) and the option map, both written by make-recipe.mjs |
ez-tree/lib, ez-tree/app | ez-tree's library and the demo-world modules the demo uses, unedited, from commit dcf309b |
public/ | The textures and models the demo loads (in Git LFS) |
lib/patch-block.js | Patch's block demo helper, as served at patch.gg/patch-block.js: the block message protocol |
build.mjs, vite.config.mjs | The build |
About 5 MB loads at start; the other bark sets load only when chosen (bark.type).
Credits and licences
- ez-tree by Dan Greenheck, MIT, © 2024 Daniel Greenheck (
ez-tree/LICENSE): the tree library, its presets, the demo world, the leaf and ground textures and the grass, flower and rock models. Credit line: "ez-tree by Dan Greenheck (MIT), github.com/dgreenheck/ez-tree". - Bark textures (
public/textures/bark/): ambientCG, CC0 1.0 (public/textures/LICENSE.mdlists each set's source). - three.js: MIT, the three.js authors.
- Draco decoder (copied into
dist/draco/by the build): Google, Apache 2.0, as three.js ships it (LICENSE-draco.txt). - The demo page and the code that steers ez-tree: Patch, MIT.
Code: MIT (LICENSE). Art: a mix. The bark is CC0; the leaf and ground textures and the models come with ez-tree
under its MIT licence (its repo states no separate terms for the ground textures and models), so the project lists
its art licence as MIT.