Ultimate 3D offers a set of useful functions, which can be used to perform calculations with vectors and matrices. Mathematically, matrices can be very complex objects and vectors even more so. But for our purposes we will only work with two easytounderstand special cases. For those of you who are impatient, here is the description in one sentence. Matrices can be used to describe transformations and vectors are sets of three floating point values that can be used to describe a position or a direction. But let's go into a bit more detail.
A vector is made up of an xcoordinate, a ycoordinate and a zcoordinate. You should already be familiar with vectors describing positions from the x
, y
and z
variables of objects. But vectors can also describe a direction with a length. For this purpose they are seen relative to the origin. If you draw a line from the origin (the vector with the coordinates (000)) to the vector describing the direction, the direction your imaginary pencil went in is the direction the vector points to. You can imagine it as an arrow. And if you want to move an object with a position described by a position vector, into a direction described by a direction vector, you just have to add the direction vector to the position vector. If a direction vector has a length of one it's called a normalized vector.
A matrix can be used to describe a transformation. In case you have forgotten what a transformation is, here's an explanation again: in Ultimate3D, a transformation is described by three groups of variables: x
, y
, and z
; rotx
, roty
, and rotz
; and scalx
, scaly
, and scalz
. The rotx/y/z
variables perform a rotation, the scalx/y/z
variables perform a scaling and the x
, y
and z
variables perform a translation. So every transformation can be seen as a combination of scaling, translation and rotation.
The great thing about matrices as a method to describe transformations, is that you can use them for different calculations very efficiently. They can also be used to solve problems which would be very complicated to solve without matrices. There's a very useful notation that can be used to describe the function of each matrix. This notation can be explained very well through a little example. If you have a model in Ultimate 3D, all information about its geometry is given in the coordinate system of the model. The model has its own coordinate system, the one you made in the modeling program. This coordinate system is called model space. The transformation that's defined through the transformation variables of this model transforms the information that is given in model space into the global coordinate system, which is called world space. For this reason the transformation is called a modeltoworld space transformation. So the notation is that a transformation that transforms something from space A into space B is called an A to B space transformation or an A to B space matrix. The terms matrix and transformation can be used more or less synonymously in this context.
Now if you have an A to B space transformation and a B to C space transformation you can transform the A to B space transformation by the B to C space transformation and as a result you'll get an A to C space transformation. So if you transform a vector by this combined matrix it will get transformed from space A to space C directly! That's why matrices are so incredibly useful. You can chain them in any way you want. If you want to transform a group of objects, you can build a matrix that describes this transformation and use it to transform the single transformation matrices of the objects. Then they'll behave as if they were glued together, just like you want it to be. With transformation matrices you can easily rotate around the whole world. If you want to, you can turn it head over. You can transform it into any coordinate system you want.
Now that was a lot of theory. But this theoretical background knowledge will make the practical use of the matrix and vector calculation functions a lot easier. So let's go on with the practical part.
Since Game Maker can not handle data structures like vectors and matrices on its own, Ultimate 3D does the work. Game Maker just gets identifiers as return values. These identifiers can either be the return value of a function, or one of the parameters. Almost every matrix or vector calculation function takes a parameter that's called OutputMatrixID
or OutputVectorID
. To avoid endless repetitions, I'm going to explain how this parameter works only once.
OutputMatrixID / OutputVectorID
The ID of the matrix or vector you want the return of the function to be written to. If you do not want to overwrite an existing matrix or vector you can pass 1 instead. Then Ultimate 3D will create a new matrix or vector and return its ID. You have to save this ID. When you do not need this matrix or vector anymore, you have to release the matrix or vector using ReleaseMatrix(MatrixID)
or ReleaseVector(VectorID)
. Otherwise the matrix or vector will not get deleted and Ultimate 3D will tell you about too many matrices or vectors being created after a while.
Passing 1 to one of these two parameters is the only possibility to create a new matrix or vector. But lots of matrices already exist. Namely these are the transformation matrices of all model, primitive and camera objects. All these matrices can be accessed through one simple function.
This function returns the ID of the transformation matrix of the object with the given ID:
GetObjectTransformation(
ObjectID
)


ObjectID
The Game Maker ID of a model, primitive or camera object.
Through the matrix ID GetObjectTransformation(...) gives you, you can read or write to the matrix of an object. This way you can change its transformation without touching its transformation variables. But you should always do this after the call to Step(), because Step() usually overwrites the transformation matrix of an object. Besides object transformation matrices, there is another group of matrices that already exists in large amounts: the transformation matrices of bones and meshes. These can be retrieved as well, but you can not just retrieve their IDs.
This function copies the bonetoworld space transformation matrix of the given bone to the given output matrix.
GetBoneTransformation(
OutputMatrixID,
BoneIndex
)


OutputMatrixID
See above.
BoneIndex
The index of the bone you want to retrieve the transformation of. You can use GetBoneIndex(...) to get this value.
The matrix that gets returned by this function will always be the transformation matrix of the frame that can be seen on the screen at the moment, so if you use this function to append objects to bones, it may happen that you get a following effect. To avoid this you can use UpdateSkeleton()
to update the bone transformation matrices of the model object that calls this function. But be careful with this. It takes quite a bit of computing time. UpdateSkeleton() is also useful for the following function.
If you are using the model manipulation functions you will often need to get the transformation matrix of meshes. Those are needed to get vertex positions from mesh space into world space. A single mesh can be used multiple times within a model, especially in models created with Anim8or. For example, if you have modeled an object for the upper leg and append this object to the bone for the left leg and to the bone for the right leg this mesh will be used twice. For this reason two functions are needed to be able to retrieve the mesh transformations.
This function returns how often the given mesh is used in the model object it is called by.
GetMeshOccurrenceCount(
MeshIndex
)


MeshIndex
The index of the mesh, for which you want to retrieve the occurrence count. This can be any integer value in the range from 0 to GetMeshCount()1.
This function outputs the current mesh to world space transformation of the given occurrence of a given mesh:
GetMeshOccurrenceTransformation(
OutputMatrixID,
MeshIndex,
OccurrenceIndex
)


OutputMatrixID
See above.
MeshIndex
The index of the mesh, for which you want to retrieve the transformation of one of its occurrences. This can be any integer value in the range from 0 to GetMeshCount()1.
OccurrenceIndex
The index of the occurrence of the given mesh, for which you want to retrieve a transformation matrix. This can be any integer value in the range from 0 to GetMeshOccurrenceCount()1.
Now that you know all functions that can be used to retrieve existing matrices, it's time to make you familiar with the matrix creation functions, which allow you to create new transformation matrices. They are all quite similar, so I'm just going to list them right here. The parameters are explained below.
This function creates a matrix that performs a translation:
CreateTranslationMatrix(
OutputMatrixID,
TranslationX, TranslationY, TranslationZ
)


This function creates a matrix that performs a scaling:
CreateScalingMatrix(
OutputMatrixID,
ScalingX, ScalingY, ScalingZ
)


This function creates a matrix that performs a rotation:
CreateRotationMatrix(
OutputMatrixID,
RotationX, RotationY, RotationZ
)


This function creates a full transformation matrix which performs a scaling, a translation and a rotation:
CreateTransformationMatrix(
OutputMatrixID,
TranslationX, TranslationY, TranslationZ,
RotationX, RotationY, RotationZ,
ScalingX, ScalingY, ScalingZ
)


OutputMatrixID
See above.
TranslationX, TranslationY, TranslationZ
These variables define a translation. They are equivalent to the transformation variables x
, y
and z
.
RotationX, RotationY, RotationZ
These variables define a rotation. They are equivalent to the transformation variables rotx
, roty
and rotz
.
ScalingX, ScalingY, ScalingZ
These variables define a scaling. They are equivalent to the transformation variables scalx
, scaly
and scalz
.
But creating matrices is not everything; you also need some functions to perform calculations with them. The first function that's available in this context does not really calculate anything, it just copies some data. However, it can be helpful in many cases.
This function copies a matrix to the given output matrix.
CopyMatrix(
OutputMatrixID,
InputMatrixID
)


OutputMatrixID
See above.
InputMatrixID
The identifier of the matrix you want to copy to the output matrix.
The next function transforms one matrix by another matrix:
TransformMatrix(
OutputMatrixID,
InputMatrixID1, InputMatrixID2
)


OutputMatrixID
See above.
InputMatrixID1, InputMatrixID2
The two input matrices. The first matrix will get transformed by the second matrix. This means that if the first matrix is an A to B space matrix and the second matrix is a B to C space matrix, the resulting matrix will be an A to C space matrix. Note that the order of the matrices does matter.
This function inverts a matrix. The inverse of a matrix is a matrix that does the exact opposite transformation of the original matrix. So if the original matrix is an A to B space matrix, the inverse of this matrix will be a B to A space matrix. If you transform a matrix by its inverse, the resulting matrix will be a matrix that does not do anything (the identity matrix). This is because if you go from A space to B space and then from B space back to A space, the result will be a transformation from A space to A space, which is no transformation at all.
InvertMatrix(
OutputMatrixID,
InputMatrixID
)


OutputMatrixID
See above.
InputMatrixID
The ID of the matrix you want to invert. Note that matrices with a scaling value of 0 can not be inverted. If the input matrix can not be inverted the input matrix itself will get returned.
This function interpolates between two given matrices using a very good interpolation method. The translation and the scaling are interpolated linearly, the rotation is interpolated using a method called spherical linear interpolation.
InterpolateMatrices(
OutputMatrixID,
InputMatrixID1, InputMatrixID2,
InterpolationFactor
)


OutputMatrixID
See above.
InputMatrixID1, InputMatrixID2
The two matrices, between which you want to perform the linear interpolation.
InterpolationFactor
A factor, which defines the influence of each of the two matrices. Usually this is a value in the range from zero to one. At 0.0 the output matrix will equal the first input matrix, at 1.0 the output matrix will equal the second matrix.
The next function can be used to retrieve the entries of a matrix. You need to know how transformation matrices work to be able to have any use of this function. The most common use of this function is using it to pass matrices to vertex shaders.
GetMatrixEntry(
InputMatrixID,
LineIndex, ColumnIndex
)


InputMatrixID
The ID of the matrix from which you want to retrieve an entry.
LineIndex, ColumnIndex
The index of the line and the column from which you want to retrieve an entry. Both values should be integers in the range from 0 to 3, where 0 refers to the first line resp. column and 3 to the last.
Now you can relax, because the really complicated part lies behind you. You can now use your knowledge about matrices to transform complete objects. But matrices can also be used to transform single vectors. Before you can do so you need to know how to create vectors. So let's go on with this.
The following function creates a vector from the given x, y, and zcoordinates.
CreateVector(
OutputVectorID,
X, Y, Z
)


OutputVectorID
See above.
X, Y, Z
The x, y, and zcoordinates you want to create the vector with.
Another possible way of creating vectors is creating them from a longitude and a latitude angle. To do this you can use the following function:
CreateDirectionVector(
OutputVectorID,
Longitude, Latitude
)


OutputVectorID
See above.
Longitude, Latitude
The longitude and latitude angles you want to create this vector from. To get a description of how longitude and latitude angles work look at the description of the function Move(...).
To retrieve information from vectors you can use the following function:
GetVector(
VectorID,
VectorElementID
)


VectorID
The identifier of the vector from which you want to retrieve information.
VectorElementID
This parameter identifies the coordinate that is to be returned. This can be 1 for the xcoordinate, 2 for the ycoordinate or 3 for the zcoordinate.
The next function is very basic, but it can be useful though. It takes a vector and copies it:
CopyVector(
OutputVectorID,
InputVectorID
)


OutputVectorID
See above.
InputVectorID
The vector, which is to be copied to the given output vector.
When you have got two vectors you can take the sum of them. The vectors get added together component by component. So (x  y  z)+(x'  y'  z') will result in (x+x'  y+y'  z+z'). This is done by the following function:
CalculateVectorSum(
OutputVectorID,
InputVectorID1, InputVectorID2
)


OutputVectorID
See above.
InputVectorID1, InputVectorID2
The identifiers of the vectors you want to add together.
Often you also need to compute the difference between two vectors. This can be done through the following function.
CalculateVectorDifference(
OutputVectorID,
InputVectorID1, InputVectorID2
)


OutputVectorID
See above.
InputVecorID1
The ID of the vector, which should function as minuend.
InputVectorID2
The ID of the vector, which should function as subtrahend.
The other basic mathematical operation of vectors is multiplying them by a scalar. If you have a vector v=(x  y  z) and a scalar (or floating point value) k, k*v will be (k*x  k*y  k*z). This scalar multiplication is done by the following function:
CalculateVectorScalarProduct(
OutputVectorID,
InputVectorID,
ScalarFactor
)


OutputVectorID
See above.
InputVectorID
The identifier of the vector which is to be multiplied by a scalar.
ScalarFactor
A floating point value which will be multiplied by every single component of the vector.
Especially for direction vectors it is a quite common case that they need to be normalized. Normalizing a vector means multiplying it by the reciprocal of its length to reach that its new length is exactly one. As I already said vectors with a length of one are called normalized vectors. This can be done easily through the following function:
NormalizeVector(
InputOutputVectorID
)


InputOutputVectorID
The ID of the vector, which is to be normalized. This vector is the input and the output. After the function call the vector saved in InputOutputVectorID
has a length of exactly one and points still in the same direction.
Often the length of a vector is needed. The length of a vector v=(x  y  z), which is referred to as v, is defined by sqrt(x*x+y*y+z*z), so it is the distance from the origin to the vector (seen as position vector). The following function can be used to get the length of a vector. The length is its return value.
CalculateVectorLength(
VectorID
)


VectorID
The identifier of the vector you want to calculate the length for.
If you are working with direction vectors it is often necessary to convert them back to longitude and latitude values. For example you may need them to use them as parameters for CheckRayIntersection(...). Computing those manually is a bit complicated. For this reason Ultimate 3D has handy functions for this purpose.
This function computes the longitude angle, which describes the given vector correctly and returns it.
CalculateVectorLongitude(
VectorID
)


VectorID
The ID of the vector for which you want to determine the longitude. The vector does not need to be normalized.
This function computes the latitude angle, which describes the given vector correctly and returns it.
CalculateVectorLatitude(
VectorID
)


VectorID
The ID of the vector for which you want to determine the latitude. The vector does not need to be normalized.
Now a really useful function follows. It is not surprising that it exists, but it is handy anyway. It can be used to transform a vector by a matrix.
The following function transforms the given vector by the given matrix. If the vector is given in A space and the matrix describes an A to B space transformation the resulting vector will be the same vector in B space.
TransformVector(
OutputVectorID,
InputVectorID,
InputMatrixID
)


OutputVectorID
See above.
InputVectorID
The identifier of the vector which is to be transformed.
InputMatrixID
The identifier of the input matrix the vector is to be transformed by.
Sometimes this function can be quite impractical though. E.g. if you transform a direction vector you usually just want the scaling and the rotation to be applied, while the translation is of no interest. And if you have already rotated a vector using some other matrix it may be that you just want the translation of a matrix to be applied. In cases like this the following function is very useful.
This function transforms the given vector by the given matrix using only the given components of the transformation. If all components of the transformation are used (true passed for the last three parameters) this function does exactly the same as TransformVector(...). All combinations of values are valid for the last three parameters:
TransformVectorEx(
OutputVectorID,
InputVectorID,
InputMatrixID,
ApplyRotation,
ApplyTranslation,
ApplyScaling
)


OutputVectorID
See above.
InputVectorID
The identifier of the vector which is to be transformed.
InputMatrixID
The identifier of the input matrix, which contains the transformation that is to be applied to the vector in part or in whole.
ApplyRotation
If you pass true for this parameter the rotation of the transformation in the given matrix effects the vector, otherwise it does not.
ApplyTranslation
If you pass true for this parameter the translation of the transformation in the given matrix effects the vector, otherwise it does not.
ApplyScaling
If you pass true for this parameter the scaling of the transformation in the given matrix effects the vector, otherwise it does not.
Besides these, Ultimate 3D offers some functions to perform common mathematical operations with vectors. The most common operation is the dot product. If you've got two vectors v=(x  y  z) and v'=(x'  y'  z') the dot product <v, v'> is equal to x*x' + y*y' + z*z'. The great thing about the dot product is that the scalar product is equal to the cosine of the angle between the two vectors, multiplied by the length of the first vector and the length of the second vector. So to put that in a little formula: if a is the angle between v and v' the scalar product is equal to cos(a) * v * v'. So if the vectors are normalized (have a length of one) the dot product is equal to the cosine of the angle. This means that the dot product will return 1 if the vectors are parallel and 0 if they are orthogonal. And that's only one of the many uses of the dot product. To implement directional lighting the only thing that needs to be done is calculate one dot product.
This function calculates the dot product of the given two vectors and returns it.
CalculateDotProduct(
InputVectorID1, InputVectorID2
)


InputVectorID1, InputVectorID2
The identifiers of the two vectors that are to be used to calculate the scalar product. The order of the vectors does not matter.
Another operation that is performed with two vectors quite often is calculating the cross product. The cross product results in a new vector, which is orthogonal to the two input vectors. The direction of this vector depends on the order in which the two input vectors are passed to this function. You can remember what this direction will be using your right hand: Build a coordinate system using your thumb (points to the left), your forefinger (points up) and your middle finger (points away from you). Now if the first vector points into the direction of your thumb and the second vector points into the direction of your forefinger, then the resulting vector will point into the direction of your middle finger.
The length of the vector equals twice the area of the triangle that is formed by the two input vectors in relation to the null vector (origin). It is also equal to the product of the lengths of the two input vectors and the sine of the angle between them. If the vectors are parallel, the returned vector will be the zero vector.
This function computes the cross product of two vectors.
CalculateCrossProduct(
OutputVectorID,
InputVectorID1, InputVectorID2
)


OutputVectorID
See above.
InputVectorID1, InputVectorID2
The identifiers of the vectors for which you want to calculate the cross product. The order does matter. If you swap these two parameters this will negate the resulting vector.
There is also another set of useful matrix related functions, which use vectors for their output data. They convert matrices back to transformation variables.
This function computes the translation of a given transformation matrix and outputs it to a vector.
ComputeMatrixTranslation(
OutputVectorID,
InputMatrixID
)


OutputVectorID
See above. After the call to this function the xcomponent will contain the x
value, the ycomponent will contain the y
value and the zcomponent will contain the z
value.
InputMatrixID
The identifier of the matrix for which you want to determine the translation.
This function computes the scaling of a given transformation matrix and outputs it to a vector.
ComputeMatrixScaling(
OutputVectorID,
InputMatrixID
)


OutputVectorID
See above. After the call to this function the xcomponent will contain the scalx
value, the ycomponent will contain the scaly
value and the zcomponent will contain the scalz
value.
InputMatrixID
The identifier of the matrix for which you want to determine the scaling.
This function computes the rotation of a given transformation matrix and outputs it to a vector.
ComputeMatrixRotationAngles(
OutputVectorID,
InputMatrixID
)


OutputVectorID
See above. After the call to this function the xcomponent will contain the rotx
value, the ycomponent will contain the roty
value and the zcomponent will contain the rotz
value.
InputMatrixID
The identifier of the matrix for which you want to determine rotx
, roty
and rotz
values that describe it correctly.
Since this is needed very often here is another useful function, which combines the above three functions. This function applies the transformation described by the given matrix, to the transformation variables of the object it is called by (x
, y
, z
, rotx
, roty
, rotz
, scalx
, scaly
, scalz
).
ApplyTransformationMatrix(
InputMatrixID
)


InputMatrixID
The matrix from which you want to determine the new transformation for the object.
Finally here are some quite special math functions. They can be used to convert between screen coordinates and world space coordinates.
This function computes a direction vector, which describes the ray starting at the camera and going to whatever can be seen at the given pixel of the screen. Note that the returned vector is usually not normalized. The ycomponent of the returned vector is always one.
ScreenCoordToVector(
OutputVectorID,
ScreenCoordX, ScreenCoordY,
CameraIndex
)


OutputVectorID
See above.
ScreenCoordX, ScreenCoordY
A screen position in pixels, relative to the left top of the inner border of the Game Maker window. This is the screen position for which the corresponding ray will be computed.
CameraIndex
The index of the camera for which the operation is to be performed. If you want to calculate the ray for the default camera pass 0 or nothing, otherwise pass the value of the number
variable of the camera object you want to check.
This function does the exact opposite operation of ScreenCoordToVector(...). It outputs a screen position vector from a given 3D world space coordinate. The x and y component of the output vector are viewport coordinates and the z component is the z coordinate of the given position transformed into camera space, which can be used to find out, whether the position is behind the camera.
CoordToScreen(
OutputVector,
X, Y, Z,
CameraIndex
)


OutputVectorID
See above.
X, Y, Z
The world space coordinates of the position that is to be translated into screen space.
CameraIndex
The index of the camera for which the operation is to be performed, which is the value of the number
variable of the corresponding camera object. Pass 0 or nothing if you want to perform this operation for the default camera.
© Christoph Peters. Some rights reserved.