← back to the tree

Growing the Joshua Tree: A Biologically-Grounded L-System for the Form of Yucca brevifolia and Y. jaegeriana

How the generator on this site actually works — the biology, the grammar, and how well it holds up against real field data.
W0ZRO · joshuatree.art

TL;DR

Nobody's published a procedural growth model for the Joshua tree, or worked out how to render one convincingly — so I built both. The model is a parametric, environment-aware L-system where branching is event-driven, not scheduled: a tip only forks when its apex gets stopped, either by flowering (gated on maturity and climate) or by damage. One grammar, run with two different parameter sets, produces the two recognized species' very different shapes. I calibrated the growth and demographic numbers against published field data, which closes most of the gap against an independent stand-structure survey — though a perceptual study comparing generated trees to photos is still on the to-do list. This page walks through the model, the browser implementation, and both halves of that validation. The structure and demographic parameters are grounded in real botany and field data; photorealistic rendering is still an open, unfitted surface.

1Why the Joshua tree

The Joshua tree (Yucca brevifolia Engelm. and the recently split-off Y. jaegeriana) turns out to be a great subject for a growth model. It's an arborescent monocot, so it has no cambium and no annual rings — you can't just count rings to get its age. Its branching follows one clear, well-documented rule tied to flowering and injury, and wild populations run the whole gamut of life stages, from unbranched juvenile spikes to old many-armed candelabra shapes.

Nobody had built a model specific to this species, or rendered it convincingly, so that's what this page covers. I'm not introducing a new grammar formalism — I'm specializing existing L-system techniques (see the prior art below) to a species that hadn't gotten this treatment, and pairing the growth model with a rendering pass tuned to its actual features. Anything flagged wip or planned below is still unfinished.

2Prior art

L-systems (Lindenmayer systems) for plant modeling got consolidated in Prusinkiewicz and Lindenmayer's The Algorithmic Beauty of Plants (1990) — the book that introduced bracketed, stochastic, context-sensitive, and parametric L-systems, plus the turtle-graphics interpretation in 3D that turns a grammar into geometry. Later work adds the pieces I actually lean on here: parametric L-systems that attach real numbers (length, age, angle) to each symbol; "open" L-systems where the structure reacts to an outside signal like climate (Měch and Prusinkiewicz, 1996); and further generalizations using positional information and continuous formulations (Prusinkiewicz et al., 2001; Prusinkiewicz and Runions, 2012).

None of that prior work targets Yucca brevifolia / jaegeriana specifically, or its rendering. That's the gap this project fills, using the machinery above.

3The biology, briefly

These features of Joshua tree development, drawn from the botanical literature (§References), are what dictate the grammar.

3.1 Branching only happens when the tip stops

A Joshua tree grows as a single unbranched column, and it only forks when something stops that growing tip. Two things can stop it. Flowering uses up the growing point in a terminal cluster of flowers, and once that happens the stem has to branch to keep growing — but flowering only happens if the tree is mature enough and the year had decent rain plus a winter freeze, so it's not an every-year thing. The other trigger is apical damage — from the yucca-boring weevil, frost, or rot — which forces a branch regardless of flowering. A tree that hasn't flowered or been damaged just stays an unbranched pole, which is exactly what most young trees in the field look like.

3.2 Two species, two shapes

When a tip does fork, it splits into two roughly equal daughter branches, occasionally three. The two recognized species (Lenz, 2007) end up with pretty different architectures from that same rule. Western trees (Y. brevifolia) grow taller — commonly several metres, sometimes past ten — and don't branch until after their first flowering, giving them an open, top-heavy, asymmetric crown higher up the trunk. Eastern trees (Y. jaegeriana) are shorter, branch earlier and lower — even before first flowering — and branch more strictly, giving a tighter, more symmetric, often multi-stemmed shape.

Worth noting: the "fork" isn't a true dichotomy at the growing-point level — the apex just dies (§3.1) and one or more side buds take over. Those buds sit in the stem's spiral leaf arrangement, so the new arms come off at spiraling angles instead of all lying in one plane. That's what gives the branching — and the whole crown — its 3D, faintly twisted look.

3.3 Growth, and what it looks like

Growth is slow but pretty steady for a desert plant. Multi-year field studies land close to 3 cm yr-1 — 3.12 (Esque et al. 2015), 3.3 (Cornett 2018), 3.75 (Gilliland et al. 2006) — with the exact number depending on that year's rain. At that rate, a stem hits the ~2 m mark of reproductive maturity around 60 years old, which lines up with the independently-estimated 50–70 year generation time (Esque et al. 2015). You'll also see higher short-term rates reported — 5.9–7.6 cm yr-1 for young unbranched plants (FEIS) — but that's a good-years-and-surviving-seedlings bias, the fast tail of a rain-driven process, not its lifetime average. So the model anchors to the multi-decade averages and the generation-time number instead.

Growth isn't constant forever, though. Old Joshua trees are stocky and densely crowned, not tall bare poles — the height clearly plateaus. So in the model, elongation follows an asymptotic (von Bertalanffy) curve toward a species height ceiling: a young stem grows ~3.7 cm yr-1 and slows as it nears that ceiling (~14 m for the taller western, lower for the eastern), pinned so it still passes the ~2 m / 60 yr maturity point. Because the whole tree slows with age, the bare stem between late forks stays short, so an old crown fills in as a compact head rather than stretching into long poles.

There's no way to just count rings and get an age, either — the trunk is a fibrous monocot stem with no growth rings — which is a big part of why a calibrated model is actually useful here. Visually: stiff, dagger-shaped grey-green leaves form rosettes at the tips. Dead leaves hang on and cloak younger stems in a "beard"; older trunks eventually shed them down to bare grey bark, so how shaggy a stem looks tracks roughly with its age. Flowers show up as big creamy clusters at the branch ends.

4The model

I modeled the tree as a parametric, environment-aware L-system where one rewrite step equals one year — so a specimen's age is literally just how many times the grammar has been rewritten. The alphabet keeps the growing apex separate from the inert structure it leaves behind. Symbols carry a couple of parameters: branch order n, apex age a (years since that tip was born, which is different from the tree's age t), and internode length . The turtle commands follow the usual Algorithmic Beauty of Plants convention — / rolls about the heading, & pitches about the left axis, [ ] save and restore state:

# Alphabet — A is the only symbol that rewrites; the rest is
# inert structure it lays down as it grows.
A(n, a)    apex — the growing tip (the only symbol that rewrites)
I(ℓ, n)    internode — draws forward by length ℓ (one year's growth)
B(n)       terminal inflorescence — a bloom ended this apex
X          dead tip — apex killed, no successful branch
[ ]        push / pop turtle state (a branch)
/(φ)       roll by φ about the heading  — sets a fork's azimuth
&(β)       pitch by β about the left axis — leans an arm outward

Each year gets a climate signal, bloom(t) ∈ {0,1}, marking whether it was a "bloom year" — enough rain plus a freeze. Real Joshua trees flower in roughly one year out of every four, range-wide (Yoder et al. 2024), and that's the rate I tuned the signal to fire at. Given that signal, every apex gets rewritten by one of these rules:

# One step = one year. Each apex A(n,a) is rewritten by the FIRST
# rule whose guard holds. Guards read the tree age t, the climate
# signal bloom(t), and N — the count of living apices this year.

# thinning — crowded crowns shed arms
A(n,a) → X                     w.p. μ = min(μ_max, k·(N − N₀)⁺)

# flower & fork — a bloom year lands on a mature apex
A(n,a) → I(ℓ,n) B(n) fork(n)   if bloom(t) ∧ mature,  w.p. p_flower

# damage — a rare yearly hazard, sometimes lethal
A(n,a) → I(ℓ,n) X              w.p. p_damage · p_death
A(n,a) → I(ℓ,n) fork(n)        w.p. p_damage · (1 − p_death)

# juvenile fork — eastern only, branches before its first bloom
A(n,a) → I(ℓ,n) fork(n)        if juvenile ∧ ¬mature ∧ a ≥ a_juv,  w.p. p_juv

# elongate — otherwise, grow one internode and age a year
A(n,a) → I(ℓ,n) A(n, a+1)      otherwise

# the fork: a pseudo-dichotomy following the leaf spiral. Each
# daughter rolls to the next golden-angle azimuth (Φ = 137.5°)
# and pitches out by the divergence angle β.
fork(n)  ≜  [ /(Φ) &(β) A(n+1,0) ]  [ /(2Φ) &(β) A(n+1,0) ]
            ( + a third arm [ /(3Φ) &(β) A(n+1,0) ] w.p. p_tri )

# mature ⟺ t ≥ first-bloom age ∧ a ≥ min apex age
# (x)⁺ = max(x, 0);  angles carry a small per-fork jitter

That first rule — thinning — is the thing that keeps a tree from branching forever over a lifespan that can stretch toward a thousand years: each apex's yearly odds of dying scale with how many living tips the tree already has, N (once the crown passes a small crowding threshold N₀). So a lone pole is basically immortal, but a crowded old crown keeps shedding arms as it goes. Skip this rule and the crown just doubles every fork, forever; keep it and the arm count levels off — a few dozen by mid-life, up to around 100 on the oldest trees — which matches the full, rounded canopy old Joshua trees are described as having (Gilliland 2006; FEIS), while the tallest trees plateau near the documented 15 m max. This only bounds the crown of a single tree — whole-stand old-tree mortality is a separate, fitted piece (§6.1). I tuned this coefficient to match that documented mature shape, since there's no dataset of arm counts to fit it against directly (see the provenance table below).

Maturity depends on both the tree's age and the apex's own age. The divergence angle β — the pitch in &(β) — is drawn per-fork from a species-specific mean and spread; the azimuth φ — the roll in /(φ) — advances by the golden angle Φ = 137.5° each fork, so successive arms follow the leaf spiral into 3D (§3.2) instead of laying flat in one plane; and a small phototropic nudge bends each daughter back toward vertical as the years go on, which is what produces that candelabra curve. Occasionally, at low probability, a fork splits three ways instead of two. The eastern parameter set adds one extra pathway — branching before maturity — to capture that species' earlier, lower branching habit; otherwise the two species share the same grammar and just differ in the numbers (first-flowering age, branch angle and symmetry, growth cap).

The climate signal and every random choice the grammar makes come from a seeded generator, drawn in a fixed order — so a specimen is completely determined by (seed, species, climate, age), and always reproduces exactly. That's what makes shareable specimen URLs (and the validation work below) possible.

4.1 Where the numbers come from

Not every number in here is fit to data, and it matters which ones are. The table below marks each quantity as data (a cited field measurement), botanical (a mechanism that's documented qualitatively and encoded here as structure), tuned (a free parameter set by eye), or — for the rendering rows at the bottom — reference (a look matched from a photograph). Crown shape and timing are grounded in data; the actual branching rates are mostly tuned, because the one quantitative dataset I have (Thomas 2026) records stem heights, not branch counts.

QuantityValueBasisSource
Elongation rate≈3.1–3.75 cm/yrdataEsque 2015; Gilliland 2006; Cornett 2018
Maturity height / age~2 m / ~60 yrdataThomas 2026; Esque 2015
Height ceiling (W / E)~14 / ~11 m asymptotetunedgrowth saturates (von Bertalanffy); ≤15 m documented max (FEIS)
Leaf length (W / E)~28 / ~17 cmdataFNA; FEIS; Lenz 2007
Leaf width; rosette span0.7–1.5 cm; 0.3–0.5 mdataFNA; FEIS
Leaf colourglaucous blue-greendataFNA
Panicle size / season / colour30–55 cm, spring, creamdataFNA
Size-class fit targetsadult 27% / 57%dataThomas 2026
Establishment bottlenecklow seedling survivaldataReynolds 2012
Juvenile size-dependent survival~19% over 22 yr; worst <25 cmdataEsque 2015
Branching trigger (flower / damage)event-drivenbotanicalLenz 2007; FEIS
Dichotomy; W-after / E-before floweringstructuralbotanicalLenz 2007; FEIS
Flowering as climate mastingbloom-year gatebotanicalYoder 2024
Branch divergence angle (W / E)26° / 19°tuneddirection from Lenz; magnitude by eye
Branching azimuth (3-D spread)golden-angle spiralbotanicalpseudo-dichotomy; buds in the leaf spiral
Flowering prob. / bloom yearp_flower ≈ 0.20tunedconsistent with FEIS fork spacing (0.6–0.9 m)
Damage hazard; death fraction0.004/yr; 0.22tuned
Refractory; trifurcation; tropism12 yr; ~0.05; ~0.12tuned
Bloom-year frequency≈25%/yr (~1 in 4)dataYoder 2024, 120-yr hindcast; climate control spans ~10–40%
Self-thinning coefficientk ≈ 0.0006–0.0008tunedtuned to the documented extensive/rounded old-crown form; no arm-count data
Stand age distributionjuvenile ramp + Weibull old-agedatafitted to per-stem heights (Thomas & Winkler 2025); juvenile size threshold Esque 2015 / FEIS
Rendering (Phase II) — surface appearance, not the model. Matched to reference photographs, not fitted to data.
Arm thickness (near-constant)~6% taper/forkbotanicalarborescent monocot; no thin twigs
Every stem-end foliatedrosette / dead tuftbotanicalfield observation; never a bare pole
Arms avoid intersectingspace-colonization steeringbotanicalshade avoidance; Runions 2007
Bark surfacephoto + normal mapreferencePlantCatalog, CC-BY-4.0
Leaf surfacephoto leaf cardreferencePlantCatalog, CC-BY-4.0
Bloom surfacephoto paniclereferencePlantCatalog, CC-BY-4.0
Stem curvature, flare, swellby eyetuned
Beard density/colour; leaf countby eyetunedmatched to photos
Studio backdrop and lightingfixed key + fill, no environmenttunedstaging/look choice
Figure 1 — the interactive model.
[live generator embedded here in the published version; screenshot in the PDF build]
Figure 1. The model, as an interactive instrument. Dragging the age slider replays a specimen's whole life (pole → first fork → widening crown) against a rail of dated life events; the climate slider thins crowns down toward bare poles or fills them out into full candelabra shapes. figure to be inserted

5How it's built

The grammar gets derived right in the browser and interpreted by a 3D turtle that emits geometry directly: runs of yearly internodes between forks collapse into tapered stems, living apices turn into leaf rosettes, and terminated tips become either flower clusters or withered dead tufts. The whole thing is a small, modular browser app — a pure, dependency-free model engine underneath a Three.js rendering layer — with species, age, and climate as live controls. Every specimen is a deterministic function of (seed, species, climate, age) (§4), and each one gets its own shareable URL. The rendering side has grown a lot past a minimal state since (§7), though its look is matched to photos rather than fit to data.

6Does it hold up? partial

The whole empirical claim here is that these generated trees are faithful, not just plausible-looking. I check that two ways: a quantitative fit against a real stand-structure survey (§6.1 — largely done, with the leftover residuals called out), and a perceptual discrimination study (§6.2 — still to run).

6.1 Checking the numbers: stand structure

My main quantitative target is Thomas's (2026) eastern-Mojave stand-structure survey: 627 stems across 57 plots, binned into four height classes (≤ 25 cm; 25 cm–1 m; 1–2 m; > 2 m) as a stand-in for life stage. I anchor to three things from it: the field growth rates (§3.3), the ~2 m / ~60 yr maturity threshold, and each population's size-class proportions.

Fitting happened in two passes. Growth rate and maturity timing came first (an earlier draft had overestimated growth by roughly 2×). That left one residual: the model was over-predicting the western adult fraction against the summary size classes — it was hanging onto too many old trees, which actually matches the survey's own read that the western population skews unusually young. So next I fit an age-dependent mortality curve to the full per-stem height data (Thomas & Winkler 2025) instead of just the summary bins: a per-species survival curve with two parts — a Weibull old-age hazard, and a juvenile survival ramp that climbs from a low seedling floor up to full survival by the ~1 m establishment size (the vulnerability threshold from Esque 2015 and the FEIS review). That old-age fit turns up a short-lived western population (characteristic lifespan ~45 yr, ~2%/yr hazard) and a much longer-lived eastern one (~195 yr); the juvenile floor is moderate in the west but very low in the aging, poorly-recruiting east. Together, these close the residual:

QuantityWestern — model / fieldEastern — model / field
Size classes (seedling/juvenile/young/mature, %)5/31/35/28 / 5/31/36/272/16/25/58 / 4/17/22/57
Adult fraction (>2 m)28% / 27%58% / 57%
Mean height1.55 m / 1.46 m2.47 m / 2.17 m
Tallest observed4.7 m / ~4.5 m5.3 m / ~5.2 m

Two residuals are still open. Eastern mean height still runs about 14% too tall — linear elongation over-ages the very tallest stems. And eastern's thinnest size class is under-represented in the model (2% vs. the field's 4%); a single snapshot survey can't tell low recruitment apart from low survival, so the fitted juvenile floor is standing in for both at once.

The dataset only records stem heights, not branch counts, so it constrains growth, demography, and maturity timing, but not the branching rates directly. There is one independent check on branching, though: the average spacing between successive forks along a branch — which comes out of the growth rate combined with the flowering frequency — lands at 0.73 m (western) and 0.71 m (eastern) in generated trees. That's inside the 0.6–0.9 m the FEIS review reports for real branches, and it's consistent with holding the per-apex flowering probability near 0.20. Arm counts per tree are still unmeasured, so they stay on the perceptual track (§6.2) for now. The youngest size class is also naturally depleted in the field by a documented establishment bottleneck (Reynolds et al. 2012) — the juvenile survival ramp above approximates that effect rather than modeling recruitment mechanistically.

6.2 Checking by eye: expert discrimination

As an independent check, I want to run a discrimination task: people who actually know Joshua trees well try to tell generated silhouettes apart from photos of real ones. If they can't reliably tell them apart, that's good evidence the model looks right; if they consistently get fooled — or un-fooled — by specific things, that tells me exactly where the model departs from life. This one is planned, haven't run it yet.

7Rendering in progress

Stems aren't smooth tubes anymore. Each run of internodes between forks gets swept as an irregular tube along the turtle's path — flaring at the root, tapering toward the tip, with a bit of seeded curvature — so a pole actually reads as a living trunk instead of a pipe. Arms stay nearly as thick as the trunk the whole way out, since an arborescent monocot doesn't grow thin twigs — each fork only sheds a few percent of radius. At every fork, each child stem grows a short way back into its parent, so the crotch reads as one continuous piece instead of two tubes awkwardly meeting at a corner.

Two rules in the renderer — not the grammar — then shape the crown the way a living tip actually would. First, each tip steers away from nearby branches that aren't its own, so arms don't grow back through each other — the same shade-avoidance response that bends a real tip toward open light, and the idea behind the space-colonization algorithm (Runions et al. 2007). Second, each tip leans outward as it climbs, so rosettes spread out into a broad candelabra instead of bunching up over the trunk. Stems are textured with a photographic Yucca bark image and normal map, tinted by wood age — younger wood browner, weathered wood greyer.

Every living apex carries a dense rosette of dagger-shaped leaves, textured from a photographic leaf card and tinted olive-green, fading from a green tip to a straw base along the blade — the two species use different leaf lengths (§3.3). Leaves don't radiate from a single point; they clothe the last stretch of stem, with bases spreading back down the branch axis, dense near the tip and the lowest leaves sticking out the sides. The whole cluster is oriented along the branch's heading, so a rosette reads as an elongated brush pointing out along its arm, not a ball stuck on top of a stick. Every stem-end has foliage — a green rosette where the apex is alive, a browning, drooping one where it was just killed — because a Joshua tree is never really a bare pole. Older dead arms, once they've broken off, just get dropped from the render entirely. The "beard" is a downswept sheath of dead leaves whose density tracks wood age: shaggy on young wood, shed down to bare bark on the old trunk. Flowering shows up, for the bloom year only, as a compact ovoid cluster — a photographic bloom texture over a lumpy greenish-cream shape (FNA) — coming off along the branch's heading rather than pointing straight up. All those thousands of leaves and beard blades get merged per-tip and per-stem to keep a specimen's mesh count sane; grove-scale instancing is still planned.

The specimen is staged like a studio photo, not a scene — it floats alone against a plain backdrop, lit by one fixed, flattering key light plus a soft fill, so the tree's form is the entire subject. No ground, sky, or time-of-day to worry about tuning.

One caveat worth being upfront about: the surface look here is matched to reference photos and tuned by eye. Unlike the growth model (§4–§6), none of it is fit to data, and it's not meant to be — rendering is Phase II. The bark and leaf textures are actual photographs, not procedural, used under CC-BY-4.0 (PlantCatalog; credited in the repository). What's left is grove-scale work — instancing and scatter — if the staging ever moves from a single specimen to a populated environment. in progress

8Where this falls short

The main limitations are already called out above: the quantitative fit (§6.1) covers growth, maturity, and demography, but branching rates rest on one independent cross-check rather than a direct fit — there's just no arm-count dataset to fit against. Rendering is unfitted by design, and the perceptual validation (§6.2) hasn't run yet. A few modeling choices are also still genuinely open:

Open questions

References

  1. Brahmsteadt, T. R. (2025). Yucca brevifolia, Yucca jaegeriana, Joshua tree. Fire Effects Information System, U.S. Department of Agriculture, Forest Service, Rocky Mountain Research Station, Missoula Fire Sciences Laboratory.
  2. Lenz, L. W. (2007). Reassessment of Yucca brevifolia and recognition of Y. jaegeriana as a distinct species. Aliso.
  3. Hess, W. J., & Robbins, R. L. (2002). Yucca brevifolia. In Flora of North America vol. 26, p. 426. (Leaf and inflorescence morphology: leaves 15–35 × 0.7–1.5 cm; panicles 3–5.5 dm, greenish white to cream.)
  4. Thomas, K. A. (2026). A snapshot of Joshua tree (Yucca brevifolia and Y. jaegeriana) stand structure in the eastern Mojave Desert of California. bioRxiv 2026.01.04.697576 — primary validation dataset.
  5. Thomas, K. A., & Winkler, D. E. (2025). Stand structure for Joshua trees in the eastern Mojave Desert of California, 2022 plot data. U.S. Geological Survey data release, doi:10.5066/P13JNZ58 — per-stem heights for full-distribution fitting.
  6. Esque, T. C., et al. (2015). Direct and indirect effects of environmental variability on growth and survivorship of prereproductive Joshua trees. American Journal of Botany 102:85–91. (Growth 3.12 cm/yr; generation time 50–70 yr; 22-yr survival ~19%, strongly size-dependent, lowest below 25 cm.)
  7. Gilliland, K. D., Huntly, N. J., & Anderson, J. E. (2006). Age and population structure of Joshua trees in the northwestern Mojave Desert. Western North American Naturalist 66:202–208. (Growth 3.75 cm/yr.)
  8. Cornett, J. W. (2018). Eastern Joshua tree growth rates and survivability on Cima Dome. In Against the Current (Desert Symposium). (Growth 3.3 cm/yr.)
  9. Reynolds, M. B. J., DeFalco, L. A., & Esque, T. C. (2012). Short seed longevity, variable germination, and infrequent establishment provide a narrow window for Yucca brevifolia recruitment. American Journal of Botany 99:1647–1654. (Establishment bottleneck.)
  10. Yoder, J. B., et al. (2024). Reconstructing 120 years of climate change impacts on Joshua tree flowering. Ecology Letters 27:e14478. Data release: Dryad, doi:10.5061/dryad.9kd51c5rr (CC0). (Masting; range-wide flowering roughly one year in four — anchors bloom-year frequency.)
  11. Prusinkiewicz, P., & Lindenmayer, A. (1990). The Algorithmic Beauty of Plants. Springer-Verlag.
  12. Měch, R., & Prusinkiewicz, P. (1996). Visual models of plants interacting with their environment. Proc. SIGGRAPH.
  13. Prusinkiewicz, P., Mündermann, L., Karwowski, R., & Lane, B. (2001). The use of positional information in the modeling of plants. Proc. SIGGRAPH.
  14. Prusinkiewicz, P., & Runions, A. (2012). Computational models of plant development and form. New Phytologist.
  15. Runions, A., Lane, B., & Prusinkiewicz, P. (2007). Modeling trees with a space colonization algorithm. Eurographics Workshop on Natural Phenomena. (Self-avoiding growth toward available space; the principle behind the arm steering in §7.)

Model structure and demographic parameters are grounded in published botany and field data (residuals noted, §6.1); perceptual validation (§6.2) hasn't run yet; rendering is matched to reference photographs, not data. Back to the generator ↗