Modding:Matrix Operations

From Vintage Story Wiki


This page explains the basics of how Vintage Story uses matrices to transform coordinates. There are many other in-depth guides on computer graphics. However, there are several different conventions for matrices in computer graphics. So this page explains the conventions used by Vintage Story so that the readers can identify whether an external guide matches the VS conventions.

Mathematical matrix and column notations

In mathematics, matrices are usually named with a capital letter (for example A). The lower case version of the same letter (for example a) is used to identify cells within the matrix. The lower case letter is suffixed with the row and column (in that order) of the cell to access. The row and column are 1-indexed (the first one starts at 1 instead of 0). The row and column are optionally separated by a comma. For example a14 is the cell of A at the first row and fourth column. cx,y is the cell of C at the xth row and yth column.

Often times tables are defined with a table of all their cells, where the table is surrounded by square brackets. Here is an example that explicitly specifies how cells of matrix A can be accessed through the a cell variables.

A

=
  a11 a12 a13 a14  
  a21 a22 a23 a24  
  a31 a32 a33 a34  
  a41 a42 a43 a44  

Column vectors can also specified with a table. Typically every table with 1 column represents a column vector rather than a matrix with only 1 column.

v =
  v1  
  v2  
  v3  
  v4  

Matrix memory format

Vintage Story uses 4 by 4 matrices. Each matrix has 16 cells in it. The Matrixf class stores those cells as an array of floats with 16 elements. There are numerous ways one could order those 16 cells in the array, but Vintage Story uses column-major order. The left-hand side of the equation below shows how the cells are ordered in mathematical notation for a matrix named A. The right-hand side shows the same matrix defined by its array elements.

  a11 a12 a13 a14  
  a21 a22 a23 a24  
  a31 a32 a33 a34  
  a41 a42 a43 a44  
=
  a[0] a[4] a[8] a[12]  
  a[1] a[5] a[9] a[13]  
  a[2] a[6] a[10] a[14]  
  a[3] a[7] a[11] a[15]  

Note that many online tutorials use row-major order instead of column-major order. So be careful when copying code from tutorials!

Homogeneous coordinates

Coordinates-system.png

Typically Vintage Story stores 3d coordinates with just the x, y, and z components. However, an additional component, w, is necessary for transformation matrices to be able to translate objects (move them around). When the w component is added, the coordinate is called a homogeneous coordinate.

The w component should always be set to 1 (except temporarily during the perspective transformation, and even then it is renormalized back to 1). Often Vintage Story only adds the w component right before applying a transformation matrix, then drops the w component (which is still set to 1) and keeps just the x, y, and z components.

Several 3D coordinate systems are used by VS. In all of them, larger Y values mean go higher, larger X values mean go east, and larger Z values mean go south. However, the scale and origin are different for the various coordinate systems.

Spawn relative
The origin is spawn point. Each block is 1 by 1 by 1.
Absolute
The origin is the bottom south east corner of the world boundary. Each block is 1 by 1 by 1. When building a custom renderer, this coordinate system can be selected by setting shader_prog.ViewMatrix to IRenderAPI.CurrentProjectionMatrix.
Player relative
The origin is inside the player head. Each block is 1 by 1 by 1. When building a custom renderer, this coordinate system can be selected by setting shader_prog.ViewMatrix to IRenderAPI.CameraMatrixOriginf. Due to floating rounding, this coordinate system is more accurate than absolute coordinates for rendering purposes.
Voxel
These coordinates are intended to be used inside of a single block. The origin is the bottom south east corner of the block. The block is 16 by 16 by 16. Using components less than 0 or greater than 16 causes the shape to be rendered outside of the block in the neighboring block's space.

The matrix code supports the same range of values as is supported by the C# float type. However, when it comes to actually placing blocks or entities, the absolute coordinates are limited by the world size, which is itself limited by the max world size.

Types of transformations

Here are 5 simple transformations. A later section will explain how they can be combined.

Identity
Returns the input coordinates unaltered. Matrixf.Create creates an identity matrix.
Scale
Shrink or grow the object. The scale may be non-uniform (for example make the object bigger horizontally but keep the height the same). A scale transformation can be added to a matrix with Matrixf.Scale.
Rotation
Rotate the object about some axis. A rotation transformation can be added to a matrix with Matrixf.Rotate, Matrixf.RotateDeg, or several other functions.
Translate
Move the object around, but keep its size the same. A translate transformation can be added to a matrix with

Matrixf.Translate.

Perspective
Convert the 3d coordinates into 2d coordinates so that they can be shown on the computer screen.

Applying a transformation matrix

A transformation can be applied to a coordinate vector by performing a matrix multiplication on the vector. There are two conventions for multiplying the matrix by the vector. Vintage Story uses column vectors. In the examples below, let A be a transformation matrix and v be a coordinate vector.

Row vectors
The coordinate vector is converted into a row vector (a 4 rows by 1 column matrix) first, and it is placed on the left-hand side of the multiplication. That is, t = v * A.
Column vectors
The coordinate vector is converted into a column vector (a 1 column by 4 rows matrix) first, and it is placed on the right-hand side of the multiplication. That is, t = A * v.

Note that depending on whether column vectors or row vectors are used, different transformation matrices are necessary to perform the same transforms. Specifically the transformation matrices are transposes of each other.

Here is an alternate way to mathematically denote the column vector multiplication. This notation emphasizes the cells in the matrix and vector.

t =
  a11 a12 a13 a14  
  a21 a22 a23 a24  
  a31 a32 a33 a34  
  a41 a42 a43 a44  
*
  v1  
  v2  
  v3  
  v4  

Combining transformations

As shown above, a transformation matrix is applied through matrix multiplication. So let's say T is a matrix that lefts all of the objects over 3 units in the x direction, S doubles the coordinates distance from the origin 2, and U shifts objects up 1 unit. Then the following formula would first add 3 to the x coordinate, then double all components, then add 1 to the y component.

U * (S * (T * v)) =
  2 * (3 + v1)  
  1 + 2 * v2  
  2 * v3  
  1  

Matrix multiplication is associative. So the matrices can be multiplied together first, then the resulting matrix can be multiplied by the vector. That produces the same result.

((U * S) * T) * v = U * (S * (T * v))

The result of multiplying the matrices together can be recalculated. Let's say W is precalculated as the result of (U * S) * T. Then:

W * v = U * (S * (T * v))

Instead of creating the matrix transformations individually, then combining them together with multiplication, the Matrixf functions instead return the result of taking the current matrix, multiplying it by the specified transformation on the right, then returning the result. So the below code snippet accomplishes the same operations as shown above (first add 3 to x, then double all components, then add 1 to y). Although the CPU actually creates the identity matrix, then combines it with the add 1 to y translation matrix, then combines it with the 2x scale matrix, then combines it with the add 3 to x translation matrix, and finally the resulting matrix is applied to v.

Matrixf.Create().Translate(0, 1, 0).Scale(2, 2, 2).Translate(3, 0, 0).TransformVector(v)

Note that in the code, the transformations are effectively written backwards with regards to the order they are applied!

Here's an example from the code in ChestLabelRenderer that draws the text on labelled chests.

            prog.ModelMatrix = ModelMat
                .Identity()
                .Translate(pos.X - camPos.X, pos.Y - camPos.Y, pos.Z - camPos.Z)
                .Translate(0.5f, 0.5f, 0.5f)
                .RotateY(rotY + GameMath.PI)
                .Translate(-0.5f, -0.5, -0.5f)
                .Translate(0.5f, 0.35f, 1 / 16f + 0.03f)
                .Scale(0.45f * QuadWidth, 0.45f * QuadHeight, 0.45f * QuadWidth)
                .Values
            ;
///...
            prog.ViewMatrix = rpi.CameraMatrixOriginf;
//...
            rpi.RenderMesh(quadModelRef);

The above code creates a transformation matrix that does the following:

  1. Start with the quad mesh which is a square from [-1, -1, 0] to [1, 1, 0].
  2. Scale the mesh by about half, so that it goes from being twice as big as a block in the x and y dimensions to about the size of a block. Specifically scale it by a little less than half to account for the border on the sign.
  3. Move the mesh so that instead of being centered around [0, 0, 0], it is centered roughly around the north face of the block. In the y coordinate (vertically) the block is placed a little below center, because in the model, the sign is a vertically little below center. The label is inset a little less than 1.5 voxels, because the sign is inside 1.5 voxels in the model. The label is not inset exactly 1.5 voxels to prevent z fighting.
  4. Translate in preparation of a rotation. Effectively this rotation shifts the rotation origin from being around the bottom southwest corner of the block to the center of the block.
  5. Rotate the label based on the rotation of the chest. Do an extra half circle rotation to account for how a chest with 0 rotation actually has the label on the north face (vs the south face that the earlier calculations used).
  6. Undo the rotation center translation.
  7. Move the sign to the correct offset within the game. Since the shader uses rpi.CameraMatrixOriginf, this translation uses player relative coordinates.
Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository