In order to make the core of Voxel Seeds more understandable I would like to explain two generic rules which together create the oak tree.
Each tree is build upon two rules - one for the wood and one for the leaves/needles. Thinking of a real automaton there are much more rules, but considering the form explained in About the automaton behind Voxel Seeds allows branching inside a rule. This is why I refer to generic rules.
As mentioned in the last post about the automaton we added a small memory to each living voxel. The first one was a "Tick" counter. This is some stopwatch when to apply the rule next. Each species has its own intervall length determining the growth speed. Further this counter can be manipulated randomly to break the synchronous growth.
The redwood has the mightiest rule allowing a procedural fractal tree. Lets start with the needles first. The rule is called SphericalLeafRule and is used for oak trees and redwoods. The idea is to seed a center voxel of the needle/leaf type and do a flood-fill of a spherical region. How can the euclidean distance to the center be estimated? This question is answered later for the redwood itself. For needles it is sufficient to use some random manhatten like distance. Therefore the "Generation" memory is used. This is monotonously increasing and the growth stops if the number gets too large. Here is some pseudo code of the rule:
1 2 3 |
if( generation < MaxGeneration(Species) ) forall( in 6 neigbourhood ) if( IsEmpty ) new Voxel(generation + Random.Next(1, 3), 0) |
To compute the euclidean distance to some center voxel a little bit more work has to be done. It is possible to store a relative position into a single 32 bit integer value. So one of the two memories could be used to store the center's position relative to the current voxel. If a new voxel is created in the neighbourhood it is easy to add or subtract one from the position to compute the new relative position. With this information each voxel can compute its euclidean distance to some source or destination point.
1 2 3 4 5 6 7 8 9 10 11 |
static public int EncodeRelativeTarget(int tx, int ty, int tz) { return ((tz + 512) * 1024 + ty + 512) * 1024 + tx + 512; } static public void DecodeRelativeTarget(int v, out int tx, out int ty, out int tz) { tx = v % 1024 - 512; ty = (v / 1024) % 1024 - 512; tz = v / 1048576 - 512; } |
Now we have the ingredents for a
RedWoodRule . First of all the "Generation" can be used to distinguish voxels of the same type but with a different growth behavior. The first important type is the kernel voxel c. The trunk and each branch consists of one line of these. When spawning a new kernel voxel it gets an encoded target in the "Resource" memory. Afterward these voxels try to reproduce into target direction. This is done randomly where the most probable chosen direction is that where the difference to the target is largest. During that growth new kernel voxels are created tangential to the chosen direction to build branches. If the growth reaches the target it creates a center voxel for the needles.
The second type is the raw wood r. These have a "Generation" counter larger than 1000 where the difference to 1000 describes the target width of the trunk/branch. For each kernel voxel the width is determined by the distance to its target. If this width is at least one r voxels are spawned in the neighbourhood. These remember there spawner position in the "Resource" field and growth further if they are not too far away. I.e. if the stored width from the "Generation" counter is larger than there actual euclidean distance. In general this creates a sphere for a single kernel voxel filled with wood. The sum of many such neighboured spheres creates a cylinder. The advantage in contrast to a real cylinder is that twists in the kernel line do not cause holes in the surrounding.
1 2 3 4 5 6 7 8 9 10 11 12 |
if( generation < 1000 ) // kernel voxel if( RandomlyCreateBranch ) t = create random target (tangential) spawn new Voxel(0, t) w = width(targetPos) if( w >= 1 ) spawn neighbours Voxel(1000+length, thisPos) spawn new Voxel(0, targetPos') else targetWidth = genration - 1000 if( distanceTo(spawnerPos) < targetWidth ) spawn neighbours Voxel(generation, spawnerPos') |