Mathematics of 3D Graphics

A basic understanding of mathematical concepts is necessary to script anything that is a bit more complicated. This section will put together a short review as well as MaxScript specific examples

Coordinates System

3DS Max has a bit of wierd looking coordinate system... on first glance it seems like its left handed with Z pointing up instead of Y. However, this is not actually the case. 3DS Max is actually right handed but they define the floor on the XY plane as opposed to the XZ plane

Vectors and Points

A point describes a position in 3D space and is represented by an x,y and z value.

A vector in 3D space describes a motion and is also represented by an x,y and z value.

Suppose you have two points in A and B. If you took the positions of A and subtracted from B, you will get a vector that tells you how to move from A to B, (a direction and how far to go).

All the 3D transform functions you use essentially apply vector math to your object(s) in some manner.

Translation

To translate(move) an object to a new position, you can represent the movement as a vector. The move function adds the vector representing the movement to the position (a point) to get a new point.

For example:

Scaling

To scale an mesh we multiply each vertex of the mesh by a scalar value (a single number). Any number larger than 1 will make the object bigger. Any number smaller than 1 will make the object smaller.

Rotation

To rotate an object, we use the rotate function passing it the object to rotate and any one of a number of possible rotation delta objects. Valid rotation delta object types are:

  • Eulerangles

  • Angleaxis

  • Quaternion

Eg, using a eulerangles rotation delta:

rot_obj = eulerangles x y z

Eulerangles objects are the simplest rotation representation. x y z can be any degree angle values. An eulerangles object as we see above will rotate an object first on the world x axis, second on the world y axis, then finally on the world z axis. Positive values will rotate in a counterclockwise direction around an axis if you imagine the axis arrow pointing out of the clock face.

Eg, assuming mybox is a box object:

rotate mybox (eulerangles 45 30 60)

The above code segment would rotate mybox 45 degrees on the x axis first, 30 degrees on the y axis second, then finally 60 degrees on the z axis.

Please see the maxscript documentation on node transformations for more information.

Quaternion objects offer powerful rotational attributes that may be useful for LSystems though they are more complex and their implementation in MAXScript has some caveats. We explore Quaternions in their own section.

Trigonometry

Trigonometry is the study of triangles (relationships between lengths and angles of triangles). In this section we will breifly go over some things that you may have forgotten about trigonometry that you might find useful.

Basics

Consider the above right angle triangle.

We define the following

Now consider the following diagram of a unit circle:

Vectors

A vector is a coordinate that represents a position in space. In MAXScript, vectors are implemented through point2 and point3 objects. Most frequently, you will be dealing with point3 objects.

Components

A vector is composed of 1 component for each dimension in their respective space. Each component represents a specific point in that respective dimension. For example, the x component in a vector indicates the position of the vector in the x dimension. Image 1 above shows a 2-dimensional (2D) vector that lies at the coordinate (5, 2) (that is, 5 on the x and 2 on the y).

Constructors

In MAXScript, vectors are implemented through the point2 and point3 objects. Point2 objects represent a 2D vector that has 2 components, an x and a y. In MAXScript, these are constructible via the square braces syntax.

Eg:

2d_vector = [50, 75]

The code segment above constructs a point2 object with x value 50 and y value 75.

Similarly, we can construct point3 objects. Eg:

3d_vector = [25, 50, 75]

The code segment above constructs a point3 object with x value 25, y value 50, and z value 75.

Point2 and point3 objects share most operations. For the remainder of this document, the focus will be on point3 objects.

Component Access

We can access the components of a vector via their component names. Eg:

a = [10, 20, 30]
a.x = 15 /* Set x value to 15 */
b = a.y /* Read y value and assign it to b which is now set to 20 */

Operators

Vectors can perform basic mathematical operations, eg:

a = [10, 20, 30]
b = [5, 10, 15]
c = a + b /* c is [15, 30, 45] */
c = b - a /* c is [-5, -10, -15] */
a += b /* a is [15, 30, 45] */
a -= b /* a is [10, 20, 30] */

Vectors can also be scaled by a float, eg:

a = [3, 6, 9]
b = a * 3 /* b is [9, 18, 27] */
b = a / 3 /* b is [1, 2, 3] */
a *= 3 /* a is [9, 18, 27] */
a /= 3 /* a is [1, 2, 3] */

Functions

length

The length of a vector is the distance from the origin (the point zero on all dimensions) to the tip. We can find the length of a vector by calling the length function. Eg:

a = length [30, 0, 0] /* a is 30 */
a = length [0, 1, 0] /* a is 1 */
a = length [1, 0, 1] /* a is 1.41421 */
a = length [1, 1, 1] /* a is 1.73205 */

normalize

The direction of a vector is the direction from the point of origin to the tip of the vector. A pure direction vector is a vector whose length is 1; we call this a normalized vector or a unit length vector. For example, the vectors [10, 0, 0] and [1, 0, 0] share the same direction but have different lengths; [1, 0, 0] is a unit length vector while [10, 0, 0] is not. To get a normalized vector from any non-zero length vector, we call the normalize function.

Eg:

a = normalize [10, 0, 0] /* a is [1, 0, 0] */
a = normalize [1, 0, 0] /* a is [1, 0, 0] since source vector is already unit length */
a = normalize [1, 0, 1] /* a is [0.707107,0,0.707107] */
a = normalize [1, 1, 1] /* a is [0.57735,0.57735,0.57735] */

Quaternions

A quaternion represents an angular rotation around an axis; or more technically, the rotational transformation of a space from one frame of reference to another.

In practical terms, a quarternion gives us a way to rotate a vector around an axis by some angle. Quarternions do not suffer from gimble-lock.

A quarternion can be constructed as an angle and an axis of rotation or by casting other rotation objects. It is not important for us to understand the internal math involved with using quaternions. Instead we only care here about its application to rotations.

Construction

In MAXScript, quaternions are implemented through the Quat class.

quat <degrees_float> <axis_point3>
<angleaxis> as quat
<eulerangle> as quat
<matrix3> as quat --extracts the rotation component as a quat

We can construct a quat by passing an amount of degrees and an axis of rotation to the class constructor or by casting any other rotation object to a quat as seen above.

Eg:

q1 = quat 30 [0, 0, 1]
q2 = (eulerangles 30 0 0) as quat

In the above code segment, we construct the quats q1 and q2 representing the following:

  • q1 represents a rotation of 30 degrees around the z axis

  • q2 represents the same rotation as the casted eulerangles object, which in this case amounts to 30 degrees around the x axis

Note: When constructing a quaternion from an angle and axis, be sure that the axis vector is normalized (See vectors article).

Usage

A quaternion object can be used to rotate objects and vectors. We will look at both capacities.

Components

A quaternion has 4 components, x, y, z, and w. Each is a floating point value. The components of a quat must follow a long list of very specific rules that are beyond the scope of this course. Quaternions overall are complex objects so accessing their raw component values is not very useful. Most of the time, we will use quats as black boxes that we can construct using their angle axis constructor or using a cast. Using the listed high level operations on a quat allows us to make use of them without having to understand the theory behind them.

rotate node quat

To rotate an object using a quaternion, we use the rotate function. Eg:

/* Assume that mybox is a geometric object */

rotate mybox (quat 45 [0, 0, 1]) /* rotate mybox 45 degrees counterclockwise around z axis */

point3 * quat -> point3

We can get a rotated vector by multiplying a point3 by a quat. Eg:

a = [1, 0, 0]
q = quat 90 [0, 1, 0]
b = a * q /* b is [0, 0, 1] ie [1, 0, 0] rotated 90 degrees CLOCKWISE around the y axis */

Note: Due to issues with MAXScript's implementation of quats, vector rotation is clockwise while object rotation is counterclockwise. For a workaround, see the Implementation Issues section.

Identity Quaternion

An identity quaternion is the default quaternion that performs a zero rotation on objects and vectors ie it does not rotate them. Mathematically, this is similar to multiplying a number by 1. The identity quat has the following special component values:

x = 0, y = 0, z = 0, w = 1

To construct this quaternion, we use the 4 element quat constructor. Eg:

q = quat 0 0 0 1 /* 4 element constructor passing in special identity value. Each value is a quat component value in the order x y z w */

a = [1, 0, 0]
b = a * q /* b is [1, 0, 0] since q performs no rotation */

/* Assume that mybox is a geometric object */

rotate mybox q /* mybox's rotation is not changed since q performs no rotation */

quat * quat -> quat

Rotations can be appended by using the operator. The resultant quaternion represents the rotation induced by applying the quat on the right first then the quat on the *left second. Eg:

a = quat 90 [1, 0, 0] /* a is a 90 degree rotation around the x axis */
b = quat 90 [0, 1, 0] /* b is a 90 degree rotation around the y axis */
c = b * a /* c rotates 90 degrees around the x axis first then rotates 90 degrees around the y axis second */

/* Assume that mybox, mybox2 are geometric objects */
rotate mybox a /* rotate mybox 90 degrees counterclockwise around the x axis */
rotate mybox b /* rotate mybox 90 degrees counterclockwise around the y axis */

rotate mybox2 c /* rotate mybox2 90 degrees counterclockwise around the x axis first then 90 degrees counterclockwise around the y axis second */

In the above code segment, mybox and mybox2 were ultimately rotated by the same amount.

inverse quat -> quat

A useful aspect of quaternions is that we can get the negative of a rotation (the opposite rotation) by inversing the rotation quaternion. This is useful since quaternions can be made to hold complex rotations where creating the opposite rotation could be difficult. This is not possible with eulerangles objects. Eg:

a = quat 45 [0, 0, 1] /* a is a 45 degree rotation around the z axis */
b = inverse a /* b is the inverse of a, ie a -45 degree rotation around the z axis */
c = a * b /* c is the identity quaternion since any quat multiplied by its inverse is nullified */

/* Assume that mybox is a geometric object */

rotate mybox a /* mybox is now rotate 45 degrees around the z axis */
rotate mybox b /* mybox is now returned to its rotation before the last line */

Implementation Issues

MAXScript's implementation of quaternions is slightly problematic. The biggest issue is that a quaternion will rotate a vector clockwise around an axis while it will rotate a geometric object counterclockwise around an axis. This will be an issue if your algorithm needs to rotate vectors and objects by the same quaternion; for example, if you need to rotate an object and also update a heading vector.

To solve this problem, we need to rotate either the vector OR the object by an inverse copy of the quaternion used to store overall rotation.

Note: This must be done consistently or else you will produce incorrect results!

Ex:

q = quat 90 [1, 0, 0] /* q is a 90 degree rotation around the x axis */
a = [0, 1, 0]

/* Assume that mybox is a geometric object */

rotate mybox q  /* mybox is rotated 90 degrees counterclockwise around the x axis */
b = a * q /* b is now the vector [0, 0, -1] since a is rotated 90 degrees clockwise around the x axis (refer to coordinate system diagram in Math Review section, reiterated below) */

/* To solve this inconsistency, we should have rotated a by the inverse of q */

b = a * (inverse q) /* b is now the vector [0, 0, 1] representing a 90 degree counterclockwise rotation aro

Last updated