The
Transformation List
To render a single rib we need to know where to render the inform,
at what size and at what orientation. To use this information we
need to be able to generate a 4x4 matrix at any point along the
parametric line.
Remembering
that matrices concatenate right to left, the horn we defined earlier
generates this list of operations:
T
= bend * (stack * (twist (grow * identity)))
= bend * stack * twist * grow
* identity
(As
a side note, one oddity of the horn generation algorithm presented
in the book “Evolutionary Art” is that it seems the programmers
have altered the order of scale operations to always prepend the
transformation queue. Every other transformation is appended to
the left hand end of a transformation queue, e.g.
M
= rotate * M
whereas
all scale operations (the grow function) are prepended on the right
hand side:
M
= M * scale
This
detail is never discussed in the book but turns out to be necessary
to get the diagrams to match the declarations in the book.)
For
simplicity’s sake we’ll leave off the identity matrix from the far
right and just assume it’s there. If we parameterize this list of
transforms w.r.t. the iterator m, the equation expands to this:
T(m)
= bend(80*m) * stack(8.0*m) * twist(40*m, 1) * grow(0.9^m)
Each
of the transforms has to generate a 4x4 matrix which again expands
the equation into the more familiar matrix form (ignoring details
of the coefficients):
T(m) = B * S
* T * G
=
c s 0 0
s c 0 0
0 0 1 0
0 0 0 1

1
0 0 0
* 0 1 0 0
0 0 1 0
0 t 0 1

c
0 s 0
* 0 1 0 0
s 0 c 0
t 0 t 1

s
0 0 0
* 0 s 0 0
0 0 s 0
0 0 0 1 
We
are going to be wanting to move and orient the whole horn to some
position in world space to allow us to construct composite objects,
so we will have to prepend a modeling transformation we shall call
the “root” transform (positioned after the identity):
T(m) = B * S * T * G * root
And
finally we will have to transform the objects from world to screen
space to generate the final rendering matrix M:
M
= world_to_screen * T(m) * root
This
description is slightly simplified as we have to take into consideration
transforming lighting normals for the primitives. Normals are direction
vectors transformed by the transpose of the inverse of a matrix,
but as every matrix in this list is simple rotation, translation
or scale then this matrix is the same as the modeling matrix T(m)
except for a scale factor. We can use the transpose of the adjoint
(the inverse not yet divided by the determinant) and renormalize
each normal vector after transformation or build a special lighting
matrix that ignores translations and scales, retaining only the
orthogonal operations that change orientation (i.e. record only
rotations and reflections).
void
Horn::render(const Matrix &root,
const
Matrix &world_to_screen);
Chaining
Horns and Build
One operation we’re going to want to do is to chain horns together
to allow us to generate long strings of primitives to model the
backbones and tentacles.
This
is where the root transformation comes into play. To chain horns
together we need to transform the root of a horn N+1 by the end
transformation of the previous horn N. We do this by getting the
horn generation function to return the final matrix of the current
horn for use later:
void
Horn::render(Matrix &newroot,
const
Matrix &root,
const
Matrix &world_to_screen);
Another
detail of horn generation arises in animations. As the number of
ribs increases all that happens is that ribs become more closely
packed – if you want the horn to appear to grow in length by adding
horns you will have to respecify the stack
command in the transformation list to correctly extend your horn
for more ribs. The way around this problem is to tell the iterator
how many ribs this list of transforms was designed for, allowing
it to generate the correct transformations for more ribs. This is
done using the build command:
myhorn
:= horn
ribs (9)
build (5)
torus (2.1, 0.4)
stack (11);
This
declaration declares a horn where the first 5 ribs are stacked 11
units high, but the horn is designed to transform 9 ribs the remaining
four ribs will be generated with interpolation values greater than
1.0.
Initially
the horn has a build
member variable set to 0.0,
telling the interpolator to generate interpolation values of 0..1
using ribs steps. If the build
variable is not equal to 0.0, the interpolator will map 0..1 using
build steps, allowing requests for fewer or greater ribs to work
with the correct spacing:
Matrix
T;
float base = (build == 0.0) ? ribs : build;
for(int i=0; i
float m = i / base;
calc_matrix(T, m);
...
}
Branch
and Ribcage
In order to construct the Lobster there are two more structures
to generate – the head and the ribcage.
The
head is an instance of a branch object – horns are transformed so
that their near end is at the origin and their far ends spread out
over the surface of a sphere using a spiral pattern along the sphere’s
surface. You can control the pitch and angles of the spiral to produce
a wide range of interesting patterns.
The
branch algorithm just produces a bunch of root transformations by:
 Generate
a point on a unit sphere.

Calculating the vector from the origin of the sphere to that point
on the surface.
 Return
a 4x4 transform matrix that rotates the Object space yaxis to
point in that direction.
To
correctly orient horns twist could be taken into consideration by
rotating horns so that the xaxis lies along the tangent to the
spiral, but this is unimportant for the look of most Lifeforms.
(To do branches correctly we could use a quaternion slerp along
the spiral, but we’ll leave that as an exercise). The function declaration
looks like this:
branch(const
Matrix &root,
const Horn &horn, //
inform to instance
const int number, //
number of informs to instance
const float angle, //
solid angle of the sphere to use
const float pitch); //
pitch of the spiral
The
ribcage algorithm takes two inputs – a horn for the shape of the
rib and a second horn to use as the backbone. To place a horn on
the ribcage the ribcage algorithm queries the backbone horn for
transformations at specific points along it’s length and uses those
transforms as the root for instancing each ribcage horn.
Assuming
for the moment that the backbone is a simple stack up the yaxis.
Ribs must stick out at 90 degrees from the backbone so we introduce
a 90 degree rotation about the zaxis, followed by a 90 degree rotation
about the yaxis. Also ribs come in pairs so the second rib must
be a reflection of the first about the yz plane. This can all be
done by adding in a single reflection matrix. Think of the resulting
composite transform as repositioning the rib flat onto the xz plane
pointing in the correct direction before moving it along the backbone:
rootrib(m)
= Tbackbone(m) * reflection * root
where
reflection
= 
1
0 0 0 
0 0 –1 0 
0 –1 0 0 
 0 0 0 1  
1
0 0 0 
and 0 0 –1 0 
 0 1 0 0 
 0 0 0 1  
Note
that this reflection must be applied to the shading normals too
otherwise lighting for the ribcage will be wrong.
To
help this process we must be able to randomly access transformations
along a horn, hence the generalised calc_transform() member function
in the Horn class.
The
Basic Rendering Process Here, finally, is an outline of the basic
rendering process for a Lobster. We will take this as the starting
point for our investigation of procedural rendering and start focussing
on how to implement this process more efficiently on the Playstation
2 using the EECore, vector units and GS together.
ribcage(const
Matrix &root,
const
Horn &horn, // inform to use as ribs
const
Horn &backbone, // inform to query as the backbone
const
int number); // number of pairs of ribs to generate
The
Basic Rendering Process
Here, finally, is an outline of the basic rendering process for
a Lobster. We will take this as the starting point for our investigation
of procedural rendering and start focussing on how to implement
this process more efficiently on the Playstation 2 using the EECore,
vector units and GS together.