Wind in the Trees

This page describes part of the acron Python framework. The frequencies come from the engine’s TreeMesher (Procedural Trees), which a C++ game can use directly.

The trees of Trees and Forests move as their wood would, after Habel et al., “Physically Guided Animation of Trees” (EG 2009) and Zhao and Barbič, “Interactive Authoring of Simulation-Ready Plants” (SIGGRAPH 2013). Every branch swings at its own natural frequency, and children ride on their parents. In the same wind, an aspen’s leaves quake while an oak’s limbs heave slowly, and a sapling moves faster than a grown tree.

An aspen and an oak side by side; below, the red line across their crowns recorded over six seconds, one row per frame.

An aspen (left) and an oak in the same 7 m/s wind. Below the picture is the red line across the crowns, one row per frame for six seconds: the aspen’s leaves flutter fast (fine grain) while the oak’s limbs swing slowly (wide waves).

How it works

When the mesher builds a tree, it treats each stem as a cantilever clamped where it grows. Its stiffness is the tip compliance of the tapered wooden tube. Its mass is the wood’s plus everything that grows on it (child branches whole, and the leaves), weighted by the shape of the first mode (Rayleigh’s method). The natural frequency is then f = sqrt(k / m) / 2 pi. A bare uniform rod gives f ~ 0.28 (r / L^2) sqrt(E / rho), so a stem slows as it lengthens and quickens as it thickens. Grown trees come out between about 0.3 and 1 Hz for the trunk, around 1 Hz for the limbs and several hertz for the twigs. Each leaf card gets a flutter frequency from its size.

Each vertex carries its bend weight split over three modes: the trunk’s, its level-1 ancestor’s and its level-2 ancestor’s. It also carries those modes’ frequencies, the damping ratio and its flutter frequency. At a fork, a child starts with exactly its parent’s split, so the two stay joined. Deeper twigs ride their level-2 ancestor’s mode, and their own frequency drives their flutter.

The shader (wind_tree_offset in shaderlib/wind.glsl) swings every mode at its own frequency. The response is a damped oscillator’s under turbulent wind: a lightly damped stem rings cleanly, while a well-damped one wanders. The mode’s phase comes from its frequency and the tree’s random number. A tree scaled by s swings at f / s. Together these mean no two trees in a stand move in step. Wind speed sets the amplitude and the gust slot sets how much of the motion swings rather than leans. In still air nothing moves.

Flutter is a quiver on top of the sway, not a sway of its own. A leaf card turns rigidly about its anchor by a degree or two, about an axis of its own, so the card never stretches. A twig shakes sideways as one piece, so its rings never swell. Each leaf or twig has one phase, and the flutter’s second partial lies below its first, so nothing comes near the Nyquist limit of a 30 Hz frame rate. The flutter grows with the wind and its gusts.

The defaults give roughly these motions (measured on the presets’ prototypes, gust 0.6):

Motion

2 m/s

6 m/s

12 m/s

trunk top, 17–24 m trees

~1 cm

3–5 cm

10–14 cm

level-1 branch tip

2–3 cm

8–12 cm

22–29 cm

leaf tip relative to its anchor

~1 cm

2–3 cm

4–5 cm

The sway runs at about 0.3–0.8 Hz for trunks and 0.1–0.8 Hz for limbs. Leaf flutter runs at about 1.5–2 Hz for oak and pine and 5–9 Hz for birch and aspen. The frequencies do not depend on the wind speed; the amplitudes grow with it. Forest.set_wind_response(flutter=...) scales the flutter.

The species’ wood

A preset’s wind section holds the wood and leaves the wind works on:

Key

Unit

What it does

wood_stiffness

GPa

stiffer wood sways faster and less

wood_density

kg/m^3

heavier (green) wood sways slower

damping

ratio

low rings cleanly, high wanders

leaf_flutter

Hz

the flutter of a 1 m card

Species

Stiffness

Density

Damping

Flutter

oak

11.0

1000

0.07

3.0

maple

10.0

900

0.06

3.5

birch

8.5

850

0.05

6.5

aspen

7.5

800

0.045

9.0

ash

10.5

850

0.06

4.0

pine

8.0

800

0.05

2.0

fir

8.0

750

0.05

2.0

spruce

8.5

700

0.05

2.0

willow

5.0

800

0.10

4.5

palm

3.0

900

0.04

1.4

bush

6.0

800

0.10

5.0

Softer wood also bends further, by sqrt(9 / E) (within 0.6 to 1.8), so a willow sways more than an oak. The section is part of the species’ key: changing it grows new prototypes and a new disk cache.

from acron.trees import Species

willow = Species.load("willow")
print(willow.wind)              # wood_stiffness, ..., leaf_flutter
mesher = willow.make_mesher()               # carries them
freqs = mesher.branch_frequencies(willow.grow(1))   # Hz per branch
print(mesher.flutter_frequency(0.5))        # a 0.5 m card

TreeMesher.beam_frequency(length, radius, stiffness_gpa, density, tip_mass) gives a uniform rod’s frequency. The vertex columns wind2 and wind3 are added to the tree vertex format, which is now 132 bytes; acron.trees.prototypes.FORMAT_VERSION is 4, so disk caches from older versions are ignored and regrown.

A game’s own foliage shader can call wind_tree_offset with the same columns: wind0 to wind3, plus the vertex’s kind (leaf.y: 1 for a leaf card, 0 for bark). The leaf’s anchor in wind1.xyz is the pivot that the card turns about. Grass and other plants without a skeleton keep using wind_offset.

Known limitations

  • The frequencies are computed per level of detail from that level’s (reduced) skeleton, so a far level’s twigs can swing a little differently from the near level’s. The trunk and the limbs barely change.

  • A leaf’s flutter frequency does not change with the wind speed (vortex shedding would): the amplitude does, so the motion never jumps.

  • There is no collision between branches, and no response to anything other than the wind.