SpacesLibrary
From Odwiki
Contents |
Tools of the trade
Here's a brief look at the tools that VEX makes available to us for working with spaces. Wherever they exist, their VOP equivalent is also listed. We'll look at examples of usage later on, but for now, we'll just list them all so as to have them handy as a reference.
We can roughly divide the available functions into two categories: the set that transform 3- or 4-dimentional vectors (which, again, could represent points, normals, directional vectors, homogeneous points, etc.), and the group that deals directly with matrices. And even though the colour-transformation functions should technically also be included (they also transform vectors), we won't be mentioning them here.
One key quality that sets the two groups appart, is that the vector functions only perform transformations to and from VEX's predefined spaces (the ones listed in the previous section), whereas the matrix functions give you the flexibility of working with any arbitrary space -- including spaces that we may create ourselves "from scratch".
Vector Functions
| TRANSFORMATION | FUNCTION | OPERATOR | NOTES |
|---|---|---|---|
| World to Object | vector4 wo_space(vector4) vector wo_space(vector) |
Space Change World To Object |
Use for transforming points only. |
| vector wo_vspace(vector) vector4 wo_vspace(vector4) |
Direction Space Change Direction Other Than Normal World To Object |
Use for transforming directional vectors only. | |
| vector wo_nspace(vector) vector4 wo_nspace(vector4) |
Direction Space Change Normal Vector World To Object |
Use for transforming normals only. | |
| Object to World | vector4 ow_space(vector4) vector ow_space(vector) |
Space Change Object To World |
Use for transforming points only. |
| vector ow_vspace(vector) vector4 ow_vspace(vector4) |
Direction Space Change Direction Other Than Normal Object To World |
Use for transforming directional vectors only. | |
| vector ow_nspace(vector) vector4 ow_nspace(vector4) |
Direction Space Change Normal Vector Object To World |
Use for transforming normals only. | |
| World To Texture | vector4 wt_space(vector4) vector wt_space(vector) |
Space Change World To Texture |
Use for transforming points only. |
| vector wt_vspace(vector) vector4 wt_vspace(vector4) |
Direction Space Change Direction Other Than Normal World To Texture |
Use for transforming directional vectors only. | |
| vector wt_nspace(vector) vector4 wt_nspace(vector4) |
Direction Space Change Normal Vector World To Texture |
Use for transforming normals only. | |
| Texture To World | vector4 tw_space(vector4) vector tw_space(vector) |
Space Change Texture To World |
Use for transforming points only. |
| vector tw_vspace(vector) vector4 tw_vspace(vector4) |
Direction Space Change Direction Other Than Normal Texture To World |
Use for transforming directional vectors only. | |
| vector tw_nspace(vector) vector4 tw_nspace(vector4) |
Direction Space Change Normal Vector Texture To World |
Use for transforming normals only. | |
| <Context> To NDC | vector toNDC(vector) | To NDC |
For surface and displacement shaders, the transformation is from World to NDC. For light and shadow shaders, the transformation is from Object to NDC. This is only meaningful for points. |
| NDC To <Context> | vector fromNDC(vector) | From NDC |
For surface and displacement shaders, the transformation is from NDC to World. For light and shadow shaders, the transformation is from NDC to Object. This is only meaningful for points. |
| World To Named Space |
Geometry and Null Objects |
||
| vector otransform(string,vector); | Space Change To Object Geometry/Null Position |
Use for transforming points only. | |
| vector ovtransform(string,vector); | Space Change To Object Geometry/Null Direction Other Than Normal |
Use for transforming directional vectors only. | |
| vector ontransform(string,vector); | Space Change To Object Geometry/Null Normal Vector |
Use for transforming normals only. | |
Light Objects |
|||
| vector ltransform(string,vector); | Space Change To Object Light Position |
Use for transforming points only. | |
| vector lvtransform(string,vector); | Space Change To Object Light Direction Other Than Normal |
Use for transforming directional vectors only. | |
| vector lntransform(string,vector); | Space Change To Object Light Normal Vector |
Use for transforming normals only. | |
Fog (Atmosphere) Objects |
|||
| vector ftransform(string,vector); | Space Change To Object Fog Position |
Use for transforming points only. | |
| vector fvtransform(string,vector); | Space Change To Object Fog Direction Other Than Normal |
Use for transforming directional vectors only. | |
| vector fntransform(string,vector); | Space Change To Object Fog Normal Vector |
Use for transforming normals only. | |
The most immediate thing to note from the table above, is the fact that none of these functions return a matrix. They all take a vector, manipulate it, and return it to us in its transformed state. This means that if we wanted to, say, pass the "toNDC" matrix from one of our light shaders to one of our surface shaders, we wouldn't be able to do it by using the above functions.
What I call "named spaces" are simply spaces that have a label. In this case, the label happens to be the name of an object. e.g: /obj/geo1, /obj/light1, etc. And for reasons that are internal to Mantra and/or the scene description (IFD), VEX requires us to make a distinction between the different types of objects; so that there are separate functions for each type. But don't let this proliferation of functions confuse you: they all transform to the space of an existing, named object.
Now let's have a look at the matrix functions.
Matrix Functions
| FUNCTION | OPERATOR | NOTES |
|---|---|---|
| matrix maketransform (int trs, xyz; vector t, r, s, p) matrix maketransform (int trs, xyz; vector t, r, s) |
Make Transformer Transform (indirectly) UV Transform (indirectly) |
Builds a 4x4 transform matrix from separate vectors representing translation, rotation, scale, and (optionally) pivot. Obviously only useful if these quantities are known. The returned matrix should only be used for transforming points. |
| matrix otransform(string name); matrix ltransform(string name); matrix ftransform(string name); |
N/A Use the Inline VOP |
As with their vector counterparts, these create the matrix for transforming from world space to the space of a geometry, light, or fog object respectively. The returned matrix should only be used for transforming points. |
| matrix3 lookat( vector from, to, up) matrix3 lookat( vector from, to; float roll) matrix3 lookat( vector from, to) |
Look At | Builds a 3x3 rotation matrix (i.e: no translation) from a reference "look at" vector. A system under this transformation will have its +Z axis pointing in the "look at" direction. The resulting system can optionally be "rolled" about its Z axis in one of two ways: through an "up" vector reference, or through an explicit angle of rotation (in radians!). The returned matrix should only be used for transforming points. |
| Multiplication Operator * | Multiply vector * matrix vector * matrix3 vector4 * matrix |
Multiplying a vector times a matrix (or a matrix3) is equivalent to "transforming" the vector. If the matrix used in the operation is any one of the matrices returned by any one of the functions listed in this table, then the vector being transformed should represent a point! (an incorrect transformation will result if it represents a vector or a normal!). |
| vector vtransform(vector, matrix) | N/A Use the Inline VOP |
The multiplication operator "*" can be used to transform points, but it can be somewhat dangerous (unless you know what you're doing) for transforming directional vectors. For transforming directional vectors (by a matrix), use this function instead. |
| void rotate( matrix &; float amount; vector axis) void rotate( matrix3 &; float amount; vector axis) void scale( matrix &; vector scale_vector) void scale( matrix3 &; vector scale_vector) void translate( matrix &; vector4 amount) void translate( matrix &; vector amount) |
Rotate Scale Translate |
The three fundamental transformations available separately so they may be applied (composed) as needed. All angles are in radians! Note that they all take a matrix, and apply their respective transformations directly onto it. In other words, the matrix that is given as a parameter is modified by these functions. |
There are other matrix functions, but they are not directly related to building spaces, so I've left them out for now.
The first thing that should jump out after a quick glance at the table of matrix functions, is that these don't seem to have three versions of each type of operation (one for points, one for directional vectors, and one for normals), whereas the vector functions do. There's only one exception: the function vtransform(), which is a specialization for directional vectors (and has - at time of writing - no direct equivalent in VOPs). So, what's going on? Is it because they don't need them?
Early on in these notes I mentioned that, even though points, vectors, and normals are all stored as vectors, they still represent different things, and so should be treated differently. This is especially true when transforming them. So yes, different versions of the same matrix are needed to properly transform each type of geometric element. But VEX doesn't provide us with these extra versions. All the listed functions (again, with the exception of vtransform) return the full matrix; and this is as it should be.
You see; in reality, the matrices that transform directional vectors and normals, are really special versions of the full matrix (the one that transforms points -- also known as the model matrix). They are specializations that take into account the properties of the entity being transformed. But the "real thing" is the model matrix, not the specialized versions. Still; we need to work with vectors and normals just as often as with points (which is why the vector functions come in all those flavours).
The first order of business then, is to apply some of the things we already know from these notes, along with some concepts covered in the Matrix page, and extend the suite of matrix functions so that henceforth we can comfortably forget about this little complication.
Extending the matrix functions
Since all the built-in functions return the full matrix, we can safely assume that we will have that version always available. What we need then, is a couple of functions that can convert the full matrix into a form that is appropriate for transforming vectors and normals. And the first thing we need to do to achieve this, is to define exactly what it is that makes these matrices different.
As mentioned earlier in these notes, "Points are sensitive to all forms of transformation: translation, rotation, scaling, etc.". This means that points get transformed by the full matrix; and since that's the matrix that the built-in functions return, we can go ahead and use it "as-is" to transform points -- points need no special handling.
When talking about directional vectors however, I mentioned that "...we never consider translations when transforming them from one space to another.". So, for vectors, we use all aspects of the transformation except for translations. This means we need to somehow remove translations from the full matrix if we intend to use it to transform directional vectors. But where in the matrix are translations defined?

Figure 3: The locations of the x, y, and z translations
in a row major matrix..
For a more in-depth explanation, please see the Matrix page, but briefly, in VEX, matrices are in "row-major" order, so translations can be found in the locations M41, M42, and M43 (see figure 3). Also note that translations only exist in 4x4 matrices; 3x3 matrices (corresponding to the matrix3 type in VEX) don't support translations and so can be used to transform either points or vectors without modification.
Armed with this information, we can sketch a VEX function to do the conversion from a point-matrix to a vector-matrix:
// Converts a 4x4 point-matrix to a 4x4 vector-matrix
matrix vmatrix ( matrix m ) {
return (matrix3)m;
}
// Converts a 3x3 point-matrix to a 3x3 vector-matrix
matrix3 vmatrix3 ( matrix3 m ) {
return m;
}
The conversion is very simple: it strips the last row (where the transforms are) by casting it to a 3x3 matrix, and then relies on automatic type conversion (from matrix3 back to matrix) to return a 4x4 matrix. The resultant matrix has the last row and column set to R4 = C4 = {0,0,0,1}, and all translations removed.
Here I've made the choice of having the functions return a matrix instead of directly modifying the matrix passed in as the parameter m. This may be a tiny bit wasteful in that it could potentially create a temporary value (although that depends on the expression and VEX's optimizer), but it also means that we can nest it and chain it with other expressions on the right hand side of an assignment, and, in my opinion, that is by far the most frequent use of such a converter function.
Note that I've also included a version for converting a 3x3 matrix that does absolutely nothing. This dummy function is there for completeness, and because this way, you don't have to remember that 3x3 matrices don't need to be converted for vectors. After compilation and optimization it should dissapear from the code and create no run-time overhead whatsoever; but it should help a great deal with our own sanity. (It is also not defined as a macro just to impose a tiny bit of type-safety by allowing the compiler to catch improper usage)
Converting the model matrix into a form suitable for transforming normals is a little bit trickier. Again; please refer to the Matrix page for the more gory mathematical details, or, if this stuff doesn't make sense, just copy the code given below, and have it handy for your own use -- i.e: you don't need to understand it to use it.
Normals, like vectors, don't get translated when transformed. But normals are special vectors: they are an implicit representation of the tangent space of the surface at a point -- i.e: they uniquely describe the tangent space at a point (in 3D, the tangent space is a plane, in 2D, a line). By definition then, normals must always remain perpendicular to this tangent space. We will skip the derivation here, but it is this "must remain perpendicular" requirement which gives rise to the special handling that normals get when being transformed.
To transform normals then, we need to use the transpose of the inverse of the linear part of the model matrix (where the "linear part" of the model matrix is just the upper 3x3 submatrix). If the model matrix is M, then the version we need to transform normals is (M-1)T. But since we know that we will generally need to normalize the transformed normal anyway, we can use the adjoint instead of the full inverse -- as long as we understand that lengths are not preserved by this transformation. Here it is in VEX:
// Converts a 3x3 model matrix into a 3x3 normal matrix
// by computing the transposed adjoint of the input matrix
matrix3 nmatrix3(matrix3 M) {
float m00,m01,m02, m10,m11,m12, m20,m21,m22;
assign(m00,m01,m02, m10,m11,m12, m20,m21,m22, M);
return matrix3( set(
m11*m22 - m12*m21 , m12*m20 - m10*m22 , m10*m21 - m11*m20,
m21*m02 - m22*m01 , m22*m00 - m20*m02 , m20*m01 - m21*m00,
m01*m12 - m02*m11 , m02*m10 - m00*m12 , m00*m11 - m01*m10
) );
}
NOTE: I have yet to determine if the above is in fact faster than the literal transpose(inverse(M)). My gut says yes, but I haven't tested it yet...hmmm... that's 18 multiplies and 9 subtracts plus assignment.... gotta test it....(Mark?)
To be continued...



