On a graph, a position vector is represented by a line with an arrow at the end. The line begins at the origin (0,0) and ends at wherever the position vector is pointing at (3,4). The arrowhead is at the end of the line.
A 3D object is generally made up of lots of triangles and quadrangles, and the positions of the corners of these polygons is specified by a 3-tuple. For example, if one of the corners of a triangle is 100 units along, 200 units up and 50 units 'in', then we would describe this corner as being at position (100,200,50). A 3D object is made up of a list of the positions of these corners (more commonly known as vertices), and a list of how to connect them to make up the various polygons. A vertex is described by a 3-tuple specifying its position along the X, Y and Z axis.
So how do you get from a 3D point to a 2D point? There are lots of ways, and they're all very different.
^ Y axis (negative)
| <--(TV Screen)
|
E-------Z-Axis-----O----------R--Z-Axis----> (positive)
\_____
| |
\______
| |
\D______ |
| \___P
|
V Y Axis (positive)
Now 'E' is where we pretend your eye is. We're assuming you're a pirate with a pegleg and a parrot, so you've only got one eye. Every game assumes this, it makes the maths a lot simpler. The eye sits on the Z axis at a fixed distance away from the screen. Let's say it's 1000 units away.
Now P is our point in 3D. In my ASCII art diagram we can't see how far along P is on the X axis, since we're looking at it from the side. But we'll ignore it for now and come back to it later. But let's say P is at X=100, Y=200, Z =250 (100,200,250). You might have noticed that, contrary to the 2D graphs you drew at school, the Y axis is pointing down, not up. This is the way the Playstation handles it.
So we have this point P and we need to turn it into a 2D point on the screen so we can use it for a vertex (corner) of a polygon. Now imagine putting your eye where 'E' is, and looking towards P. We want a point on the TV screen which looks (to our eye) as if it is at point P. And this new point on the TV screen is point D. So we have to figure out how to calculate point D from point P.
To do this, we use the principle of similar triangles. This says that if two different sized triangles share the same internal angles, then one triangle is just a scaled up version of the other. This can help you work out unknown lengths of things. We have two similar triangles here: E-D-O and E-P-R. They share the same internal angles (and overlap a bit too). What do we know about triangle EDO? Well, we know where E and O are, so we know the length of the line EO (its 1000). But we don't know the length of DO, and that's what we want to find out. What do we know about triangle EPR? We can work out the length between E and R (1000 + P.z = 1000 + 250 = 1250), and the length of RP is just the Y component (200).
So according to the similar triangles principle:
Length DO Length RP
--------- = ---------
Length EO Length RE
We know 3 of the terms already. Rearranging the equation a bit, we get the unknown to one side.
Length DO = Length RP * Length EO
---------------------
Length RE
So the Y component of point D is going to be:
Y.screen = 200 * 1000 / (1000 + 250) = 160
That gives us how far along the Y component on the screen is going to be. What about the X? Well, in a similar fashion, we calculate the X. If you imagine the diagram above represents the X axis instead of the Y, and re-apply all the equations accordingly, you'll get a new X value for the screen too. So:
ScreenX = (P.X * Distance from Origin to Eye) / (Distance from Origin
to Eye + P.Z);
ScreenY = (P.Y * Distance from Origin to Eye) / (Distance from Origin
to Eye + P.Z);
These two equations are called the perspective transformation. It transforms a 3D point into a 2D one that the GPU in the Playstation can understand. The perspective transformation has to be applied to every point in the object just before the object is drawn. The processor then uses these 2D points to decide where to put the 2D polygons.
The perspective transformation is the last step we apply before drawing.
The same applies to 3D objects, except we adjust 3 components (X,Y and Z) instead of just 2 (X and Y). We specify in a direction vector (which looks exactly like a position vector) how far we want to move our object. Say we wanted to move it 100 along, 0 up and 200 'in', the direction vector would be (100,0,200). If we call this vector 'T', then the functions for translation are:
newX = oldX + T.X;
newY = oldY + T.Y;
newZ = oldZ + T.Z;
Once again, we have to apply this to every point in the object if we want the entire object to move.
And similarly, if we wanted to halve the size of our object, we'd just move all the points further in towards the origin so that they were only half the distance away.
This transformation is called scaling, and assuming you want to make the object n times as big, the functions required to do it are:
newX = n*oldX;
newY = n*oldY;
newZ = n*oldZ;
You have to apply this to every point in the 3D object in order to see the results. So far, easy peasy.
Assuming you want to rotate a 3D point around the Z axis by t degrees, the formula is:
newX = x* cos(t) + y *sin(t)
newY = x * -sin(t) + y*cos(t)
newZ = z
Since the value of Z doesn't change when you're rotating around the Z axis, there's no special formula for it. The formulae for rotating around the other axes is similar:
Y axis:
new X = x* cos(t) + z*sin(t)
new Y = y
new Z = x * -sin(t) + z*cos(t)
X axis:
new X = x
new Y = z* -sin(t) + y*cos(t)
new Z = z* cos(t) + y *sin(t)
Most applications want to rotate about all three axes. To do so, you've got to first rotate about one axis, then rotate the result about the next axis, and then rotate the result about the last axis. The order that you do the rotation is important too. Rotating about the Z, then the Y, then the X axis has different results than if you rotate about the X, the Y, then the Z axis.
As an example, take a ballpoint pen and hold it in front of you as if it is lying on the X axis. Now rotate it 90 degrees about the Z, then 90 degrees about the X. The pen should now be aligned with the Z axis. Hold it in front of you along the X axis again, and rotate it 90 degrees along the X axis, the 90 degrees along the Z axis. The pen should be aligned along the Y axis! See how the order of rotation is important?
Thes equations rotate points about a particular axis. Sometimes we want points rotated about different axis. The equations for those are a lot more complicated, so we won't go into them here. The equations above will suffice most of the time.
Once again, we apply these transformation to all points in the 3D object.
We've also described the equations necessary to do all these, and none of them looked very nice. You can write programs which use all those equations above, but eventually your programs will become very difficult to understand. We don't want to think of them as sets of equations, because that's too confusing and the maths gets horrible very quickly when you want to do something complex. We just want to think of them as transformations. This is where matrices come in.
A matrix is a 2 dimensional array of numbers that represent a transformation (rotation, scaling, translation, perspective, etc). A matrix is called an mxn matrix when it has m rows and n columns. When you multiply a vector (3D point) with a 3x3 matrix, the result is another vector that has been transformed by the matrix. You don't have to know how you multiply the vector with a 3x3 matrix, the libraries and the GTE in the Playstation do that for you.
For example, if I have a matrix M which represents a scaling matrix that scales by a factor of 2, and I have a bunch of 3D points to transform stored in an array of SVECTORs V (where V[i] = (point.X, point.Y, point.Z)) then I can write a simple loop which uses the GTE to calculate the transformed points:
For i = 0; i < length(V); i++
V[i] = V[i] * M; // GTE operation.
Of course, this is just psuedocode. At the end of this, all the points V[i] will have been scaled by 2.
Very important! Multiplying a 3D point V by a matrix will get different results depending on the order of multiplication (V * M does not equal M * V!). In the following examples, we're assuming that V is on the far left of the equation.
a 0 0
0 a 0
0 0 a
is a scaling matrix - if you multiply a vector by this matrix, the vector will be scaled by factor 'a'.
The rotation matrices are the interesting ones. To rotate around the Z axis:
cos(t) sin(t) 0
-sin(t) cos(t) 0
0 0
1
The Y axis:
cos(s) 0 -sin(s)
0 1 0
sin(s) 0 cos(s)
and finally the X axis:
1 0
0
0 cos(r) -sin(r)
0 sin(r) cos(r)
When you multiply a vector V (which represents a 3D point) by a matrix M, the result of V * M is another vector V' which has been transformed by M. You can plug in this new vector V' into another transformation, and get back yet another vector.
Just out of interest, if you want to do absolutely nothing to your point (i.e. what you put in is what you get back), then you can use the identity matrix. If you multiply a vector V by the identity matrix I, you get back V. It's a bit pointless really, since the original point doesn't change, but later on in life you'll need to know what the identity matrix is:
1 0 0
0 1 0
0 0 1
Back to the examples: Say we wanted to scale all our points by a factor of 3, then rotate it about the Z axis by 90 degrees. First we create the necessary matrices S (for scaling) and Rz (for rotation about Z). Using the 'templates' described above:
3 0 0
S = 0 3 0
0 0 3
cos(90) sin(90) 0
0 1 0
Rz = -sin(90) cos(90) 0 = -1 0 0
0
0 1 0 0 1
So for all our points in the object we apply S and then Rz. Say V is a single point:
V' = V * S
V'' = V' * Rz
or more succintly:
V'' = V * S * Rz
V'' will contain the new point firstly transformed by scaling, then transformed by a rotation by 90 degrees about the Z axis.
As mentioned above, the ORDER you do transformations is important. Translating a point and then scaling it will have drastically different effects than if you scaled it and then translated it.
As you may have gathered, we have to apply a transformations to each point in the object to get a result. And we know that the GTE can multiply an arbitrary 3x3 matrix by a 3D point to get us another 3D point. That's an advantage - the GTE is really good (i.e. fast) at multiplying a matrix by a 3D point. Much faster than the CPU, in fact. And since we can represent rotation and scaling by a 3x3 matrix, we're going to get a speed increase if we use matrices rather than getting the CPU to 'manually' do the equations given at the start. But that's not the real reason we use matrices.
(Pedant alert: The GTE is sort of halfway between a 3x3 matrix multiplier and a 4x4 multiplier. It can do any 3x3 matrix multiplication, but only special cases of 4x4 multiplication, and we'll see why don't care about that later)
The reason is embedded in the lines:
V' = V * S
V'' = V' * Rz
and
V'' = V * S * Rz
As all of you know, the key to making a fast program is keeping the number of mathematical operations to a minimum. And you may have been disturbed by the fact that in the above example we were doing two matrix multiplies for every point! This is baaaaad, since we didn't have to.
For example, say we wanted to perform the following fictional piece of code:
a = getA();
b = getB();
for(i = 0; i < 1000; i++) {
d[i] = c[i] * b * a;
}
Assuming your C compiler is terrible and can't see the obvious optimisation, you'd be doing 2 multiplies 1000 times, which is hard (and more importantly, slow) work for the CPU. A better way of writing it would be:
a = getA();
b = getB();
q = b * a;
for(i = 0; i < 1000; i++) {
d[i] = c[i] * q;
}
That's only one multiply, so it's a lot quicker. In a similar way, instead of doing this:
S = createScalingMatrix();
Rz = createRotationAboutZMatrix();
For(every Point in the Object) {
TempPoint = Point * S;
NewPoint = TempPoint * Rz;
}
we'd rather do this:
S = createScalingMatrix(scaleFactor);
Rz = createRotationAboutZMatrix(angle);
Q = S * Rz;
For(every Point) {
NewPoint = Point * Q;
}
A lot quicker, right? Now imagine you're not doing just 2 transformations, but 10! Instead of doing 10 transformations for every point, you can always initially calculate Q, the combination of all those transformations, and apply just Q instead. This is a huge speed saving! If you were doing the same with the original equations, you could do some simplification of the maths, but it would be error prone and difficult. Matrices are a great way to organise sets of transformations.
That's the cool thing about transformations represented by matrices - you can combine all sorts of transformations together into one matrix. If you have a transformation in matrix A and another transformation in matrix B, and matrix C is A * B, then multiplying a point V by A and then multiplying the result by B is the SAME as multiplying V by matrix C:
C = A * B;
V * C = V * A * B
and an even more extreme example:
F = A * B * C * D * E
V * F = V * A * B * C * D * E
and so on.
A very important point that I will repeat again: If A and B are matrices (read: transformations), then A * B does NOT always equal B * A. This is why the order you do your transformations is so important. The first transformation should be on the LEFT, and the last transformation should be on the RIGHT. The vector V is always multiplied from the far left, as in the above examples. (Sidenote for pedants: It is possible to represent these equations where the vector to multiply is on the right and the matrix is on the left. To do this, you have to mirror (tech term: transpose) the transformation matrix given above about it's diagonal line from top left to bottom right). Most graphics books use the format I'm using, where the vector to be transformed is on the far left of the equation.
The really cool thing about matrices is that you can multiply two matrices together to form a new matrix which is the combination of the original two. So once we create our 3 rotation matrices (one each for X, Y and Z rotation), we can multiply them all together to create one new matrix, and then multiply that with our points instead. It doesn't just have to be rotation matrices either - we can combine scaling, translation and perspective transformations in there too.
As an example, the Playstation function RotMatrixX() will take an angle and create a matrix which is a rotation by that angle about the X axis. Similar functions are RotMatrixY and RotMatrixZ. The function RotMatrix() will create a single matrix that is the combination of a rotation about the Z followed by a rotation about the Y followed by a rotation about the X. You pass in a blank matrix and a vector containing the 3 angles (for X, Y and Z), and it creates the 3 different rotation matrices described above (Rx, Ry and Rz) and multiplies them together to get Rzyx in your blank matrix. Once again, order is important. Multiplying points by this new matrix Rzyx is the same as if you'd rotated them about the Z axis first, then the Y axis, then the X. RotMatrix() doesn't handle any different orders of transformation, only Z-then-Y-then-X. You will have to make your own version of RotMatrix if you want a different order (You can use the RotMatrixX/Y/Z functions though!).
RotMatrix psuedocode (SVECTOR angles, MATRIX outputMatrix) {
Matrix XRotate, YRotate, ZRotate;
CreateXRotationMatrix(angles.vx, &XRotate);
CreateYRotationMatrix(angles.vy, &YRotate);
CreateZRotationMatrix(angles.vz, &ZRotate);
outputMatrix = ZRotate * YRotate * XRotate;
// Matrix multiplication, not normal multiplication!
}
"This buggers things up a bit" they thought. But because they were Clever People, they eventually found a way around it. Instead of using 3x3 matrices, they'd use 4x4 matrices. A 4x4 matrix has 4 rows and 4 columns instead of 3 rows and 3 columns. The Clever People found an easy way to convert the existing 3x3 scaling matrices into 4x4 matrices - the new bits of the array were filled with zeroes, and the bottom right corner has a 1 in it. For example, the new scaling matrix is:
a 0 0 0
0 a 0 0
0 0 a 0
0 0 0 1
The bits in bold are the new numbers. The old 3x3 rotation and scaling matrices have these new bits tacked on, and there you have it, a 4x4 version of the same thing.
The Clever People figured out that the translation matrix would look like this:
1 0 0 0
0 1 0 0
0 0 1 0
x y z 1
This matrix will move a 3D point x units to the right, y units up and z units 'in'.
Assuming E is the distance from the eye to the origin, the perspective transformation matrix looks like this:
1 0 0 0
0 1 0 0
0 0 1 -1/E
0 0 0 1
Nice and simple!
However, you can't multiply a 4x4 matrix by a 3D point (don't worry about why, you just can't).In order to multiply a 3-tuple vector by a 4x4 matrix, we simply concatenate a 1 on the end of the vector! So if you had a 3D point (100,200,300), you simply make it (100,200,300,1). Sorted.
So now we have the complete set - a set of 4x4 matrices which can perform rotation, scaling, translation and perspective transformations. We can combine them in any order we like (perspective last, remember).
"Now wait a minute", I hear you say. "I was looking at libps.h, and the Matrix structure they had there was 3x3, with 3 't' entries on the end for translation. That is not 4x4 matrix, it's a 4x3!" And you are correct. When the Sony engineers were trying to optimize for space, they realized that 4th column of most of the 4x4 matrices were all zeroes, with a 1 at the bottom. Since this is always true of a scaling, rotation, and translation (but NOT a perspective) transformation matrix, and is also true of any combination of scaling, rotation and translation (but NOT a perspective) transformation matrix, they decided not to store the last column. Simple as that.
The GTE knows this too, and automatically 'fills in the gaps' of the 4th column with the correct numbers (0's and a 1) when you load a matrix into the GTE. This means the GTE can't multiply an arbitrary 4x4 matrix, but we don't care as long as we're only doing rotation, scaling, translation or perspective.
Let's look at the MATRIX structure:
typedef struct {
short m[3][3];
short t[3];
} MATRIX;
When you perform the GsSetLs(&myMatrix) call, what it does is load in that specified matrix (myMatrix) into the GTE. The m[3][3] fills in the first 3 rows of the first three columns, and the t[3] fills in the last row. The GTE knows that the last column is going to be all zeroes and a one, so it fills those in internally.
Here's a neat optimisation trick. When you combine the perspective transformation matrix with any of the other 4x4 transformation matrices described above, the resulting matrix will be exactly the same as the original, except the second to bottom term in the 4th column will be -1/E. The GTE knows this, so it fills that term in with -1/E too (you previously specified E to the GTE when you called GsSetProjection()). So this is the same as combining the matrix you've loaded in with the perspective transform.
After all that loading, you'll probably be calling GsSortObject4() with your 3D object. The Playstation will use the matrix you specified (which has been loaded into the GTE) to transform all the points in your 3D object. The results are also going to be 3D points. It uses the X and Y parts of the result as the screen coordinates, and the Z part of the result to decide how far in/out the point is (used for clipping and for inserting polygons into the Ordering Table).
So the biggest question is: "How do I get the myMatrix MATRIX all set up?"
With a 3D system, the blank canvas looks the same, but the origin (0,0,0) is in the middle of the screen, not the top left. Positive Y is still in the down direction though. And once again, by translating the 3D coordinates of your object, you can place the 3D object on the screen. This is just a simple translation transformation. So to move a 3D object in 3D space, we need to apply the translation matrix.
What we have just done is changed between coordinate systems. A coordinate system consists of an origin and 3D points which are defined relative to that origin. We define our 3D objects to have their own coordinate system, and all the 3D points in that object are defined relative to the center of that coordinate system. We have also created the world coordinate system (where the origin is in the middle of the screen). We need to convert between our objects local coordinate system to the world coordinate system. With 2D games, this is a simple matter of translation. With 3D systems, converting between coordinate systems can mean a translation, rotation, scaling or all 3! It usually consists of just translation though.
Most of the time, if we perform an appropriate translation on our local coordinate system, we're effectively converting that object's coordinate system to the world coordinate system. All objects have to be converted to the world coordinate system before they can be drawn.
You can think of the world coordinate system as the mother of all the local coordinate systems. You are converting between the local coordinate system and it's parent, the world coordinate system. It's possible for the local coordinate systems to have children too (we'll go into this later), so for now it's best to think that we're converting between a child and it's parent coordinate system. The world coordinate system is the ancestor of all coordinate systems, and has no parents.
The GsCOORDUNIT2 structure contains a matrix that will be applied to your object to get it into its parent coordinate system (not necessarily world). This matrix is called matrix. The matrix.m[3][3] part of matrix contains the 9 upper left values of the 4x4 matrix that will be applied to your object before translation. The translation itself is stored in matrix.t[3]. This means that you can do as much rotation and scaling as you like (specified in matrix.m[3][3]) to your object before it finally gets translated by the amount in matrix.t[3]. If you don't want to do any rotation or scaling, you have to have the identity matrix in there (told you it would be useful), where all the diagonal entries from top left to bottom right in matrix.m[3][3] are 1 and everything else is 0.
A B C 0 = m[0][0], m[0][1], m[0][2], 0
D E F 0 = m[1][0], m[1][1], m[1][2], 0
G H I 0 = m[2][0], m[2][1], m[2][2], 0
J K L 1 = t[0] , t[1] , t[2],
1
A-I are 9 values which will contain your 3x3 rotation/scaling matrix. J-L contain the translation which follows the rotation/scaling. If you want translation followed by rotation, you'll have to perform the matrix multiplication yourself - this would involve setting up two separate matrices (one for translation, one for rotation) and multiplying them together. This new matrix would contain the transformations in the order you desire.
When the libraries come across a GsCOORDUNIT2 structure, they have to create a matrix which converts this coordinate system to world. If your GsCOORDUNIT2 structure has a super value of WORLD (which indicates that this coordinate system's parent is world), then nothing much needs to be done. It simply copies your matrix into the workm matrix structure and sets the flg flag to 1 to say that workm is a valid matrix which can be used to change this coordinate system into world coordinates. If the super value isn't world, then it's got to do some calculations, as we'll see in a later section.
In order to do scaling, you'd have to then apply the scaling transformation matrix by multiplying it with the new rotation matrix. But there's a simple trick. If you want to scale X by a factor of x, Y by a factor of y and Z by a factor of z, then all you need to do (after calling RotMatrix) is to multiply the diagonal entries in matrix by x, y and z:
matrix.m[0][0] *= x;
matrix.m[1][1] *= y;
matrix.m[2][2] *= z;
Since you usually want to scale the object by an equal amount in every direction, it's common for x, y and z to all be the same.
Setting the t[3] translation parts is easy too. t[0]
is how much you want to move this object along the X axis, t[1]
is for the Y, and t[2] is for the Z.
Finally, set the flg flag to 0. This tells the libraries that
you've changed something, and so it knows to re-calculate workm
(see below).
If you have a creature described by multiple coordinate systems with a child-parent relationship, then you can easily control the motion of all the limbs. To draw a child coordinate system, you have to convert its coordinate system to its parents. And the parent's coordinate system has to be converted to its parent's coordinate system. And so on ad infinitum until the you converted the coordinate system all the back up to the world coordinate system.
To convert between coordinate system X and world, the formula is defined recursively:
LocalToWorldMatrix(X) {
if super == WORLD
return X.matrix;
else
return X.matrix * LocalToWorldMatrix(X.super);
}
While matrix contains the matrix to convert between this coordinate system and its parent, workm contains the matrix to convert between local and world. If flg is set to one (and this is only ever done by the libraries), it means that workm contains the product of this matrix and all its ancestors matrices. That's why you have to reset it to zero whenever you change the matrix structure, because that product will have to be recalculated.
Our model described before assumes that our eye is on the Z axis, at a distance of E. Of course, most people don't want this, they want an eye that they can point in an arbitrary direction, looking from an arbitrary position. Well, this is possible, but you don't want to know the maths. So we fake it instead.
For years, people thought that the Sun went around the Earth, when the opposite was true. But how do you know for sure that the Sun doesn't go around the Earth? It's a valid question, because the Sun going around the Earth and the Earth going around the Sun both look the same to someone standing on Earth. Similarly, if you moved all your objects in 3D space 10 units to the left, it would look the same if you moved the eye 10 units to the right.
We use this principle to change world coordinates into screen coordinates. We set up the position of the camera and the direction it is pointing, and the call GsSetRefView2() will create an appropriate matrix for us that converts from world coordinates and screen coordinates. So rather than moving the eye in relation to the world, we're going to move the whole world in relation to the eye!
So now the process is essentially complete.
For(every point V in this local coordinate system) {
V' = V * LW * WS * P
Screen X = V'.X
Screen Y = V'.Y
OT Z = V'.Z
}
There are library functions to calculate all these matrices for you:
You simply call GsSetLs() with LocalToScreenMatrix, and that's the matrix you need! So what did GsGetLs() do?
A B C 0 = m[0][0], m[0][1], m[0][2], 0
D E F 0 = m[1][0], m[1][1], m[1][2], 0
G H I 0 = m[2][0], m[2][1], m[2][2], 0
J K L 1 = t[0] , t[1] , t[2],
1
Then the libraries (GsSortObject4) load in the a 3D point V:
(X Y Z 1) = V.vx, V.vy, V.vz, 1
It then performs a matrix multiply (don't worry about how this works, it's also optimising for the fact that there are lots of zeroes in the 4th column):
SX = A*X + D*Y + G*Z + 1*J
SY = B*X + E*Y + H*Z + 1*K
SZ = C*X + F*Y + I*Z + 1*L
That's the multiply over and done with. SX, SY and SZ now contain the point after it's been translated from local coordinates to screen. Now we do the perspective transform:
ScreenX = SX * E / SZ
ScreenY = SY * E / SZ
OTZ = (Average value of SZ for the last 3 points if it's a triangle,
or last 4 points if it's a quadrangle)
ScreenX and ScreenY are what you use to put your polygons on the screen. OTZ is used to decide where in the ordering table this polygon should go.
You may have noticed that the perspective transform didn't look like the way I described it - there's a few maths operations missing there! This is an optimisation to reduce the number of maths ops involved. Instead of doing the perspective transformation as described near the top of this document, the GTE assumes the eye is at the origin and the screen is the distance E away along the Z axis. It makes the maths much simpler. You don't have to worry about this.
For each object A{
For each coordinate system B in object A {
RotMatrix(&B.rotation,
B.GsCoordUnit2.matrix); // Set up rotation for this coordinate
system in the matrix
B.GsCoordUnit2.matrix.t[0]
= B.position.x;
// Set up the translation for this object
B.GsCoordUnit2.matrix.t[1]
= B.position.y;
B.GsCoordUnit2.matrix.t[2]
= B.position.z;
B.GsCoordUnit2.flg =
0;
// Tell Gs that we've changed the matrix.
GsGetLws(&B.GsCoordUnit2,
&myLWmatrix, &myLSmatrix);
GsSetLight(&myLWmatrix);
// Sets up the light matrix with the LW matrix
GsSetLs(&myLSmatrix);
// Sets the main GTE matrix with the LS matrix
For each TMD C which
shares this coordinate system {
GsSortObject4(C);
// Draw each object which uses this coordinate system
}
}
}