Schabby's Blog
Reserve Orbital Defence Commander

In the following post I provide a simple-as-possible math primer to understand basic concepts necessary for 3D graphics. I do not put much emphasize on a formal notation, but rather on understandability and applicability for our case. That is why we always assume the Cartesian Coordinate System and specialize our definitions for three dimensions.

Scalars

A common variable that holds a single numerical value is called a scalar and is written as s \in \mathbb{R} . For example s = 3.42 or s = -0.23 . Wow, that was trivial, I am even a little bit ashamed of myself.

Vectors

We write a vector as a ordered series of scalars

 \vec{a} = \begin{pmatrix} a_x \\ a_y \\ a_z \end{pmatrix}


where in this case  \vec{a} is three dimensional so that we may add  \vec{a} \in \mathbb{R}^3 . In few cases we will deal with four dimensional vectors which will then be denoted as being sampled from  \mathbb{R}^4 . Vectors have an orientation (vertical or horizontally) so we may write

 \vec{a}^\top = \begin{pmatrix} a_x \\ a_y \\ a_z \end{pmatrix}^\top = (a_x, a_y, a_z)


which we refer to as transpose. The subscripts x, y, z are just a convention to denote the first, second and third dimensions, so we may as well write a_0, a_1, a_2, \dots.

The geometric interpretation of vectors is diverse and I will not dwell on it. For our computer graphics case, it is enough to say the vectors may be interpreted as a direction (directional vector) or as a point in space.

Vector Length

The length (also called Euclidean Length or l2-norm) of a vector  \vec{a} \in \mathbb{R}^n is defined as

  \| a \| = \sqrt{ a_0^2+a_1^2+\dots } = \sqrt{ \sum_{k=1}^n a_k^2 }


So in the three dimensional case, we compute the length as  \| a \| = \sqrt{a_x^2+a_y^2+a_z^2} .


Dot Product


The dot product between two vectors  \vec{a}, \vec{b} \in \mathbb{R}^3 is defined as  \vec{a} \cdot \vec{b} = (a_x b_x + a_y b_y + a_z b_z) and results in a scalar. A more general definition for n dimensions, we express the dot product as

 \vec{a}, \vec{b} \in \mathbb{R}^n,\;\;\;\;\vec{a} \cdot \vec{b} = \sum_{k=1}^n a_k b_k


It becomes evident, that  \| a \|^2 = \vec{a} \cdot \ \vec{a} .


Cross-Product


The cross product between two vectors \vec{a},\vec{b}\in \mathbb{R}^3 is defined in closed form as

 \vec{a}\times \vec{b} = \begin{pmatrix} a_y b_z - a_z b_y \\ a_z b_x - a_x b_z \\ a_x b_y - a_y b_x\end{pmatrix}

Matrices

A matrix  M \in \mathbb{R}^{3 \times 3} is defined as

 M = \begin{pmatrix} m_{0,0} & m_{1,0}  & m_{2,0} \\ m_{0,1} & m_{1,1}  & m_{2,1} \\ m_{0,2} & m_{1,2}  & m_{2,2}\end{pmatrix}

Matrix-Vector Multiplication

The multiplication a vector  \vec{b} \in \mathbb{R}^3 with a Matrix  M \in \mathbb{R}^{3 \times 3} , results in another vector  \vec{v} \in \mathbb{R}^3 :

 Ma = \begin{pmatrix} m_{0,0}a_x + m_{1,0}a_y + m_{2,0}a_z \\ m_{0,1}a_x + m_{1,1}a_y + m_{2,1}a_z \\ m_{0,2}a_x + m_{1,2}a_y + m_{2,2}a_z\end{pmatrix} = \vec{v}

Matrix-Matrix Multiplication


Homogeneous Coordinates


Computer graphics involves a lot of matrix transformations that need to be applied in sequence. To combine several transformation matrices through multiplication into one single matrix, we need to extend the coordinate system by one dimension. Fortunately, this pretty easy as we always just fill in 1 for the additional dimension. For example, if we are dealing with a three dimensional vector

 \vec{a} = \begin{pmatrix} a_x\\a_y\\a_z\end{pmatrix}

we easily extend the vector to homogeneous coordinates adding a 1, such that

 \vec{a} = \begin{pmatrix} a_x\\a_y\\a_z\\1\end{pmatrix}


and leave the other dimension untouched. To better understand why we need to homogeneous coordinates, we shall look on the example of a translation matrix. Let's assume we multiply a translation matrix T_{3 \times 3}(\vec{t}) with another translation matrix K_{3 \times 3}(\vec{k}):

T_{3 \times 3}(\vec{t})K_{3 \times 3}(\vec{k})= \begin{pmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & t_z \end{pmatrix} \begin{pmatrix} 1 & 0 & k_x \\ 0 & 1 & k_y \\ 0 & 0 & k_z \end{pmatrix} = \begin{pmatrix} 1 & 0 & k_x + t_x k_z \\ 0 & 1 & k_y + t_y k_z \\ 0 & 0 & t_z k_z \end{pmatrix}=W_{3 \times 3}


If we now apply a vector (1, 1, 1)^{\top} on W_{3 \times 3}, we get

 W_{3 \times 3}(1,1,1)^{\top}=\begin{pmatrix} 1 + k_x + t_x k_z \\ 1+ k_y + t_y k_z \\ t_z k_z \end{pmatrix}


which is obviously crap, because for a translation we assume each dimension to be a simple additive term, without multiplications. If we however extend the problem to homogeneous coordinates, we get

T_{4 \times 4}(\vec{t})K_{4 \times 4}(\vec{k})= \begin{pmatrix} 1 & 0  & 0& t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1\end{pmatrix} \begin{pmatrix} 1 & 0  & 0& k_x \\ 0 & 1 & 0 & k_y \\ 0 & 0 & 1 & k_z \\ 0 & 0 & 0 & 1\end{pmatrix}

which is

T_{4 \times 4}(\vec{t})K_{4 \times 4}(\vec{k})=\begin{pmatrix} 1 & 0  &0 & t_x+k_x \\ 0 & 1 & 0 & t_y+k_y \\ 0 & 0 & 1 & t_z+k_z \\ 0 & 0 & 0 & 1\end{pmatrix} = W_{4 \times 4}

We apply the vector again on the translation matrix

 W_{4 \times 4}(1,1,1,1)^{\top}=\begin{pmatrix} k_x + t_x \\ k_y + t_y \\ t_z+ k_z \\ 1\end{pmatrix}


which looks much better. The individual coordinates are a simple addition of the translation vector. This is obviously far from being a mathematically sustainable proof, but it nicely illustrates the reason for homogeneous coordinates.

Matrix Transformations

Computer graphics makes heavy use of matrix transformations. The most important ones are given in the following section.

Translation Matrix


We used the translation transformation matrix already in the section about homogeneous coordinate systems. Being  T \in \mathbb{R}^{4 \times 4} we define the translation matrix as

T(\vec{t})=\begin{pmatrix} 1 & 0  &0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1\end{pmatrix}

Rotation Matrix

The rotation matrix in homogeneous coordinates around the x-axis is given as

 R_x(\theta) \in \mathbb{R}^{4 \times 4}, R_x(\theta) = \begin{pmatrix} 1 & 0  &0 & 0 \\ 0 & cos(\theta) & -sin(\theta) & 0 \\ 0 & sin(\theta) & cos(\theta) & 0 \\ 0 & 0 & 0 & 1\end{pmatrix}

The rotation matrix in homogeneous coordinates around the y-axis is given as

 R_y(\theta) \in \mathbb{R}^{4 \times 4}, R_y(\theta) = \begin{pmatrix} cos(\theta) & 0  & sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -sin(\theta) & 0 & cos(\theta) & 0 \\ 0 & 0 & 0 & 1\end{pmatrix}

The rotation matrix in homogeneous coordinates around the z-axis is given as

 R_z(\theta) \in \mathbb{R}^{4 \times 4}, R_z(\theta) = \begin{pmatrix} cos(\theta) & -sin(\theta)  &0 & 0 \\ sin(\theta) & cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1\end{pmatrix}

Source Code

During the years, I built my own little linear algebra classes that I am happy to share with you here.

Vector3f

This is the code for a three dimensional float vector.

import java.nio.FloatBuffer;
 
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
 
public class Vector3f 
{
	public float x, y, z;
 
	public Vector3f() {	}
 
	public Vector3f(Vector3f toBeCopied) 
	{
		this.x = toBeCopied.x;
		this.y = toBeCopied.y;
		this.z = toBeCopied.z;
	}
 
	public Vector3f(float[] toBeCopied) 
	{
		if( toBeCopied.length != 3 )
		{
			throw new IllegalArgumentException("length has to be exactly 3");
		}
 
		this.x = toBeCopied[0];
		this.y = toBeCopied[1];
		this.z = toBeCopied[2];
	}
 
	public Vector3f(float x, float y, float z) 
	{
		this.x = x;
		this.y = y;
		this.z = z;
	}
 
	public Vector3f add(float scalar)
	{
		x += scalar;
		y += scalar;
		z += scalar;
 
		return this;
	}
 
	public Vector3f add(float dX, float dY, float dZ)
	{
		x += dX;
		y += dY;
		z += dZ;
 
		return this;
	}
 
	public Vector3f sub(float scalar)
	{
		x -= scalar;
		y -= scalar;
		z -= scalar;
 
		return this;
	}
 
	public Vector3f sub(float dX, float dY, float dZ)
	{
		x -= dX;
		y -= dY;
		z -= dZ;
 
		return this;
	}
 
	public Vector3f sub(Vector3f a)
	{
		x -= a.x;
		y -= a.y;
		z -= a.z;
 
		return this;
	}
 
	public Vector3f subAndAssign(Vector3f a, Vector3f b)
	{
		x = a.x - b.x;
		y = a.y - b.y;
		z = a.z - b.z;
 
		return this;
	}
 
	public Vector3f add(Vector3f a)
	{
		x += a.x;
		y += a.y;
		z += a.z;
 
		return this;
	}
 
	public Vector3f addAndAssign(Vector3f a, Vector3f b)
	{
		x = a.x + b.x;
		y = a.y + b.y;
		z = a.z + b.z;
 
		return this;
	}
 
	public Vector3f add(Vector3f a, Vector3f b)
	{
		x += a.x + b.x;
		y += a.y + b.y;
		z += a.z + b.z;
 
		return this;
	}
 
	public float l1Norm()
	{
		return Math.abs(x) + Math.abs(y) + Math.abs(z);
	}
 
	/**
	 * Returns the length of the vector, also called L2-Norm or Euclidean Norm.
	 */
	public float l2Norm()
	{
		return (float) Math.sqrt(x*x+y*y+z*z);
	}
 
	public float l2NormSquared()
	{
		return x*x + y*y + z*z;
	}
 
	public Vector3f cross(Vector3f a)
	{
		float tempX = y * a.z - z * a.y;
		float tempY = z * a.x - x * a.z;
		float tempZ = x * a.y - y * a.x;
 
		x = tempX;
		y = tempY;
		z = tempZ;
 
		return this;
	}	
 
	public Vector3f crossAndAssign(Vector3f a, Vector3f b)
	{
		float tempX = a.y * b.z - a.z * b.y;
		float tempY = a.z * b.x - a.x * b.z;
		float tempZ = a.x * b.y - a.y * b.x;
 
		x = tempX;
		y = tempY;
		z = tempZ;
 
		return this;
	}
 
	public float dot(Vector3f a)
	{
		return x*a.x + y*a.y + z*a.z;
	}
 
	public Vector3f mult(Vector3f a)
	{
		x *= a.x;
		y *= a.y;
		z *= a.z;
 
		return this;
	}
 
	public Vector3f scale(float scalar)
	{
		x *= scalar;
		y *= scalar;
		z *= scalar;
 
		return this;
	}
 
	public Vector3f normalize()
	{
		float length = l2Norm();
		x /= length;
		y /= length;
		z /= length;
 
		return this;
	}
 
	public float getX() {
		return x;
	}
 
	public void setX(float x) {
		this.x = x;
	}
 
	public float getY() {
		return y;
	}
 
	public void setY(float y) {
		this.y = y;
	}
 
	public float getZ() {
		return z;
	}
 
	public void setZ(float z) {
		this.z = z;
	}
 
	public Vector3f set(float x, float y, float z)
	{
		this.x = x;
		this.y = y;
		this.z = z;
 
		return this;
	}
 
	public Vector3f set(Vector3f v)
	{
		this.x = v.x;
		this.y = v.y;
		this.z = v.z;
 
		return this;
	}
 
	public String toString()
	{
		return "("+x+", "+y+", "+z+")";
	}
 
	public void glVertex3f()
	{
		GL11.glVertex3f(x, y, z);
	}
 
	public void glNormal3f() 
	{
		GL11.glNormal3f(x, y, z);
	}
 
	public void glColor3f() 
	{
		GL11.glColor3f(x, y, z);
	}
 
	public float[] getArrayCopy() 
	{
		return new float[]{x, y, z};
	}
 
	public FloatBuffer getBufferCopy() 
	{
		FloatBuffer buffer = BufferUtils.createFloatBuffer(3);
		buffer.put(x);
		buffer.put(y);
		buffer.put(z);
		buffer.flip();
		return buffer;
	}
 
	public void glRotate(float cosAngle)
	{
		GL11.glRotatef(cosAngle, x, y, z);
	}
 
	/**
	 * Returns the angle (radians) between this vector and the provided vector.
	 * Note that both vectors have to be normalized.
	 */
	public float angle(Vector3f v)
	{
		return (float) Math.acos(dot(v));
	}
 
}

Matrix3f

Here is the code for a 3x3 float matrix.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
 
public class Matrix3f 
{
	/**
	Non-OpenGL format:
 
	    | m0, m1, m2 |
	M = | m3, m4, m5 |
	    | m6, m7, m8 |
		 */
	private float[][] m = new float[3][3];
 
	public Matrix3f()
	{
		setIdentity();
	}
 
	public Vector3f mult(Vector3f source, Vector3f target)
	{
		target.x = m[0][0]*source.x + m[0][1]*source.y + m[0][2]*source.z;
		target.y = m[1][0]*source.x + m[1][1]*source.y + m[1][2]*source.z;
		target.z = m[2][0]*source.x + m[2][1]*source.y + m[2][2]*source.z;
 
		return target;
	}
 
	public void setIdentity()
	{
		m[0][0] = 1;	m[1][0] = 0;	m[2][0] = 0;
		m[0][1] = 0;	m[1][1] = 1;	m[2][1] = 0;
		m[0][2] = 0;	m[1][2] = 0;	m[2][2] = 1;
 
	}
 
	public Vector3f mult(Vector3f source)
	{
		float x = m[0][0]*source.x + m[0][1]*source.y + m[0][2]*source.z;
		float y = m[1][0]*source.x + m[1][1]*source.y + m[1][2]*source.z;
		float z = m[2][0]*source.x + m[2][1]*source.y + m[2][2]*source.z;
 
		source.set(x, y, z);
 
		return source;
	}
 
	public Matrix3f mult(Matrix3f source, Matrix3f target)
	{
		target.set(0, 0, m[0][0]*source.get(0,0) + m[1][0]*source.get(0,1) + m[2][0]*source.get(0,2));
		target.set(1, 0, m[0][0]*source.get(1,0) + m[1][0]*source.get(1,1) + m[2][0]*source.get(1,2));
		target.set(2, 0, m[0][0]*source.get(2,0) + m[1][0]*source.get(2,1) + m[2][0]*source.get(2,2));
 
		target.set(0, 1, m[0][1]*source.get(0,0) + m[1][1]*source.get(0,1) + m[2][1]*source.get(0,2));
		target.set(1, 1, m[0][1]*source.get(1,0) + m[1][1]*source.get(1,1) + m[2][1]*source.get(1,2));
		target.set(2, 1, m[0][1]*source.get(2,0) + m[1][1]*source.get(2,1) + m[2][1]*source.get(2,2));
 
		target.set(0, 2, m[0][2]*source.get(0,0) + m[1][2]*source.get(0,1) + m[2][2]*source.get(0,2));
		target.set(1, 2, m[0][2]*source.get(1,0) + m[1][2]*source.get(1,1) + m[2][2]*source.get(1,2));
		target.set(2, 2, m[0][2]*source.get(2,0) + m[1][2]*source.get(2,1) + m[2][2]*source.get(2,2));
 
		return target;
	}
 
	public Matrix3f mult(Matrix3f source)
	{
		float value00 = m[0][0]*source.get(0,0) + m[1][0]*source.get(0,1) + m[2][0]*source.get(0,2);
		float value10 = m[0][0]*source.get(1,0) + m[1][0]*source.get(1,1) + m[2][0]*source.get(1,2);
		float value20 = m[0][0]*source.get(2,0) + m[1][0]*source.get(2,1) + m[2][0]*source.get(2,2);
 
		float value01 = m[0][1]*source.get(0,0) + m[1][1]*source.get(0,1) + m[2][1]*source.get(0,2);
		float value11 = m[0][1]*source.get(1,0) + m[1][1]*source.get(1,1) + m[2][1]*source.get(1,2);
		float value21 = m[0][1]*source.get(2,0) + m[1][1]*source.get(2,1) + m[2][1]*source.get(2,2);
 
		float value02 = m[0][2]*source.get(0,0) + m[1][2]*source.get(0,1) + m[2][2]*source.get(0,2);
		float value12 = m[0][2]*source.get(1,0) + m[1][2]*source.get(1,1) + m[2][2]*source.get(1,2);
		float value22 =  m[0][2]*source.get(2,0) + m[1][2]*source.get(2,1) + m[2][2]*source.get(2,2);
 
		m[0][0] = value00;	m[1][0] = value10;	m[2][0] = value20; 
		m[0][1] = value01;	m[1][1] = value11;	m[2][1] = value21;
		m[0][2] = value02;	m[1][2] = value12;	m[2][2] = value22;
 
		return this;
	}
 
	public void setTranslation(Vector3f translate)
	{
		m[2][0] = translate.x;
		m[2][1] = translate.y;
		m[2][2] = translate.z;
	}
 
	public void setTranslation(float x, float y, float z)
	{
		m[2][0] = x;
		m[2][1] = y;
		m[2][2] = z;
	}
 
	// http://de.wikipedia.org/wiki/Drehmatrix
	public void setRotationMatrixXAxis(float alpha)
	{
		m[0][0] = 1; m[1][0] = 0;          m[2][0] = 0;
		m[1][0] = 0; m[1][1] = cos(alpha); m[2][1] = -sin(alpha);
		m[2][0] = 0; m[1][2] = sin(alpha); m[2][2] = cos(alpha);
	}
 
	public void setRotationMatrixYAxis(float alpha)
	{
		m[0][0] = cos(alpha);  m[1][0] = 0; m[2][0] = sin(alpha);
		m[0][1] = 0;           m[1][1] = 1; m[2][1] = 0;
		m[0][2] = -sin(alpha); m[1][2] = 0; m[2][2] = cos(alpha);
	}
 
	public void setRotationMatrixZAxis(float alpha)
	{
		m[0][0] = cos(alpha); m[1][0] = -sin(alpha); m[2][0] = 0;
		m[0][1] = sin(alpha); m[1][1] = cos(alpha);  m[2][1] = 0;
		m[0][2] = 0;          m[1][2] = 0;           m[2][2] = 1;
	}
 
	public void setRotationMatrix(float alpha, float[] v)
	{
		m[0][0] = cos(alpha)+ v[0]*v[0]*(1-cos(alpha)); m[1][0] = v[0]*v[1]*(1-cos(alpha))-v[2]*sin(alpha); m[2][0] = v[0]*v[2]*(1-cos(alpha))+v[1]*sin(alpha);
		m[0][1] = v[1]*v[0]*(1-cos(alpha));             m[1][1] = cos(alpha)+v[1]*v[1]*(1-cos(alpha));      m[2][1] = v[1]*v[2]*(1-cos(alpha))-v[0]*sin(alpha);
		m[0][2] = v[2]*v[0]*(1-cos(alpha));             m[1][2] = v[2]*v[1]*(1-cos(alpha))+v[0]*sin(alpha); m[2][2] = cos(alpha)+v[2]*v[2]*(1-cos(alpha));
	}
 
	private static final float sin(float rad)
	{
		return (float) Math.sin(rad);
	}
 
	private static final float cos(float rad)
	{
		return (float) Math.cos(rad);
	}
 
	public float get(int row, int column)
	{
		return m[row][column];
	}
 
	public void set(int row, int column, float value)
	{
		m[row][column] = value;
	}
 
	public void setUpperLeft(Matrix4f modelView) 
	{
		m[0][0] = modelView.m[0][0];
		m[1][0] = modelView.m[1][0];
		m[2][0] = modelView.m[2][0];
 
		m[0][1] = modelView.m[0][1];
		m[1][1] = modelView.m[1][1];
		m[2][1] = modelView.m[2][1];
 
		m[0][2] = modelView.m[0][2];
		m[1][2] = modelView.m[1][2];
		m[2][2] = modelView.m[2][2];
 
	}
 
	public void transpose()
	{
		float[][] newM = new float[3][3];
 
		for( int x = 0; x < 3; x++ )
		{
			for( int y = 0; y < 3; y++ )
			{
				newM[y][x] = m[x][y];
			}	
		}
		this.m = newM;
	}
 
	public FloatBuffer getAsFloatBuffer() 
	{
		FloatBuffer buffer = ByteBuffer.allocateDirect(3*3*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		for( int x = 0; x < 3; x++ )
			for( int y = 0; y < 3; y++ )
				buffer.put(m[x][y]); // it turned out that this works with shaders (uniform matrix)
 
		buffer.flip();
		return buffer;
	}
 
	public void invert()
	{
		float det = determinant();
		transpose();
		m[0][0] /= det;
		m[0][1] /= det;
		m[0][1] /= det;
		m[1][0] /= det;
		m[1][1] /= det;
		m[1][2] /= det;
		m[2][0] /= det;
		m[2][1] /= det;
		m[2][2] /= det;		
	}
 
	public float determinant()
	{
		// http://en.wikipedia.org/wiki/Invertible_matrix
		final float a = m[0][0];
		final float b = m[1][0];
		final float c = m[2][0];
		final float d = m[0][1];
		final float e = m[1][1];
		final float f = m[2][1];
		final float g = m[0][2];
		final float h = m[1][2];
		final float k = m[2][2];
		return a*(e*k-f*h) - b*(k*d-f*g) + c*(d*h-e*g);
	}
 
	public void print() 
	{
		System.out.println(m[0][0]+"\t"+m[1][0]+"\t"+m[2][0]);
		System.out.println(m[0][1]+"\t"+m[1][1]+"\t"+m[2][1]);
		System.out.println(m[0][2]+"\t"+m[1][2]+"\t"+m[2][2]);
 
	}
}

Matrix4f

Here is the code for a 4x4 float matrix.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
 
import org.lwjgl.opengl.GL11;
 
public class Matrix4f 
{
 
	/**
	 * format[ x ][ y ]
	 * 
	 * 
	 *  (0, 0) (1, 0) (2, 0) (3, 0)  (4, 0)
	 *  (0, 1) (1, 1) (2, 1) (3, 1)  (4, 1)
	 *  (0, 2) (1, 2) (2, 2) (3, 2)  (4, 2)
	 *  (0, 3) (1, 3) (2, 3) (3, 3)  (4, 3)
	 *  
	 *  http://www-lehre.informatik.uni-osnabrueck.de/~mm/skript/7_3_3D_Transformation.html
	 */
 
	public float[][] m = new float[4][4];
 
	public void set(Matrix4f matrix)
	{
		m[0][0] = matrix.m[0][0];
		m[1][0] = matrix.m[1][0];
		m[2][0] = matrix.m[2][0];
		m[3][0] = matrix.m[3][0];
 
		m[0][1] = matrix.m[0][1];
		m[1][1] = matrix.m[1][1];
		m[2][1] = matrix.m[2][1];
		m[3][1] = matrix.m[3][1];
 
		m[0][2] = matrix.m[0][2];
		m[1][2] = matrix.m[1][2];
		m[2][2] = matrix.m[2][2];
		m[3][2] = matrix.m[3][2];
 
		m[0][3] = matrix.m[0][3];
		m[1][3] = matrix.m[1][3];
		m[2][3] = matrix.m[2][3];
		m[3][3] = matrix.m[3][3];
	}
 
	/**
	 * computes this = A*B  
	 */
	public void multAndAssing(Matrix4f A, Matrix4f B) 
	{
		for( int x = 0; x < 4; x++ )	
		{
			for( int y = 0; y < 4; y++ )
			{
				m[x][y] = A.get(0, y)*B.get(x, 0) + A.get(1, y)*B.get(x, 1) + A.get(2, y)*B.get(x, 2) + A.get(3, y)*B.get(x, 3);
			}
		}
	}
 
	public void setFromModelViewMatrix()
	{
		FloatBuffer buffer = ByteBuffer.allocateDirect(4*4*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		//GL11.glLoadMatrix(buffer);
		//GL11.glLoadIdentity();
		GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buffer);
 
		m[0][0] = buffer.get(0);
		m[0][1] = buffer.get(1);
		m[0][2] = buffer.get(2);
		m[0][3] = buffer.get(3);
		m[1][0] = buffer.get(4);
		m[1][1] = buffer.get(5);
		m[1][2] = buffer.get(6);
		m[1][3] = buffer.get(7);
		m[2][0] = buffer.get(8);
		m[2][1] = buffer.get(9);
		m[2][2] = buffer.get(10);
		m[2][3] = buffer.get(11);
		m[3][0] = buffer.get(12);
		m[3][1] = buffer.get(13);
		m[3][2] = buffer.get(14);
		m[3][3] = buffer.get(15);
	}
 
	public void setFromProjectionMatrix()
	{
		FloatBuffer buffer = ByteBuffer.allocateDirect(4*4*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		//GL11.glLoadMatrix(buffer);
		//GL11.glLoadIdentity();
		GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, buffer);
 
		m[0][0] = buffer.get(0);
		m[0][1] = buffer.get(1);
		m[0][2] = buffer.get(2);
		m[0][3] = buffer.get(3);
		m[1][0] = buffer.get(4);
		m[1][1] = buffer.get(5);
		m[1][2] = buffer.get(6);
		m[1][3] = buffer.get(7);
		m[2][0] = buffer.get(8);
		m[2][1] = buffer.get(9);
		m[2][2] = buffer.get(10);
		m[2][3] = buffer.get(11);
		m[3][0] = buffer.get(12);
		m[3][1] = buffer.get(13);
		m[3][2] = buffer.get(14);
		m[3][3] = buffer.get(15);
	}
 
	public void glLoadMatrix()
	{
		GL11.glLoadMatrix( getAsFloatBuffer() );
	}
 
	public String toString()
	{
		String s = "";
		s += (m[0][0]+"\t"+m[1][0]+"\t"+m[2][0]+"\t"+m[3][0]) + '\n';
		s += (m[0][1]+"\t"+m[1][1]+"\t"+m[2][1]+"\t"+m[3][1]) + '\n';
		s += (m[0][2]+"\t"+m[1][2]+"\t"+m[2][2]+"\t"+m[3][2]) + '\n';
		s += (m[0][3]+"\t"+m[1][3]+"\t"+m[2][3]+"\t"+m[3][3]) + '\n';
 
		return s;
	}
 
	public void print()
	{
		System.out.println(m[0][0]+"\t"+m[1][0]+"\t"+m[2][0]+"\t"+m[3][0]);
		System.out.println(m[0][1]+"\t"+m[1][1]+"\t"+m[2][1]+"\t"+m[3][1]);
		System.out.println(m[0][2]+"\t"+m[1][2]+"\t"+m[2][2]+"\t"+m[3][2]);
		System.out.println(m[0][3]+"\t"+m[1][3]+"\t"+m[2][3]+"\t"+m[3][3]);
	}
 
	public Matrix4f()
	{
		setIdentity();
	}
 
	public Vector3f mult(Vector3f source, Vector3f target)
	{
		target.x = m[0][0]*source.x + m[0][1]*source.y + m[0][2]*source.z + m[0][3]*1;
		target.y = m[1][0]*source.x + m[1][1]*source.y + m[1][2]*source.z + m[1][3]*1;
		target.z = m[2][0]*source.x + m[2][1]*source.y + m[2][2]*source.z + m[2][3]*1;
 
		return target;
	}
 
	public void setIdentity()
	{
		m[0][0] = 1;	m[1][0] = 0;	m[2][0] = 0;	m[3][0] = 0;
		m[0][1] = 0;	m[1][1] = 1;	m[2][1] = 0;	m[3][1] = 0;
		m[0][2] = 0;	m[1][2] = 0;	m[2][2] = 1;	m[3][2] = 0;
		m[0][3] = 0;	m[1][3] = 0;	m[2][3] = 0;	m[3][3] = 1;
	}
 
	public Vector3f mult(Vector3f source)
	{
		float x = m[0][0]*source.x + m[1][0]*source.y + m[2][0]*source.z + m[3][0]*1;
		float y = m[0][1]*source.x + m[1][1]*source.y + m[2][1]*source.z + m[3][1]*1;
		float z = m[0][2]*source.x + m[1][2]*source.y + m[2][2]*source.z + m[3][2]*1;
 
		source.set(x, y, z);
 
		return source;
	}
 
	/**
	 * computes A*v
	 */
	public Vector4f mult(Vector4f source)
	{
		float v1 = m[0][0]*source.array[0] + m[1][0]*source.array[1] + m[2][0]*source.array[2] + m[3][0]*source.array[3];
		float v2 = m[0][1]*source.array[0] + m[1][1]*source.array[1] + m[2][1]*source.array[2] + m[3][1]*source.array[3];
		float v3 = m[0][2]*source.array[0] + m[1][2]*source.array[1] + m[2][2]*source.array[2] + m[3][2]*source.array[3];
		float v4 = m[0][3]*source.array[3] + m[1][0]*source.array[1] + m[2][3]*source.array[2] + m[3][3]*source.array[3];
 
		source.set(v1, v2, v3, v4);
 
		return source;
	}
 
	/**
	 * computes this*matrix = target
	 */
	public Matrix3f mult(Matrix3f matrix, Matrix3f target)
	{
		for( int row = 0; row < 4; row++ )
		{
			for( int column = 0; column < 4; column++ )
			{
				target.set(column, row, m[0][row]*matrix.get(column,0) + m[1][row]*matrix.get(column,1) + m[2][row]*matrix.get(column,2) + m[3][row]*matrix.get(column,3));
			}
		}
 
		return target;
	}
 
	/**
	 * computes this*matrix = this
	 */
	public Matrix4f mult(Matrix4f matrix)
	{
		float[][] tmp = new float[4][4];
 
		for( int row = 0; row < 4; row++ )
		{
			for( int column = 0; column < 4; column++ )
			{
				tmp[column][row] = m[0][row]*matrix.get(column, 0) + m[1][row]*matrix.get(column, 1) + m[2][row]*matrix.get(column, 2) + m[3][row]*matrix.get(column, 3);
			}
		}
 
		this.m = tmp;
 
		return this;
	}
 
	public void setTranslation(Vector3f translate)
	{
		setTranslation(translate.x, translate.y, translate.z);
	}
 
	public void setTranslation(float x, float y, float z)
	{
		setIdentity();
		m[3][0] = x;
		m[3][1] = y;
		m[3][2] = z;
		m[3][3] = 1;
	}
 
	public void setScale(float x, float y, float z)
	{
		setIdentity();
		m[0][0] = x;
		m[1][1] = y;
		m[2][2] = z;
		m[3][3] = 1;
	}
 
	// http://de.wikipedia.org/wiki/Drehmatrix
	public void setRotationMatrixXAxis(float alpha)
	{
		m[0][0] = 1; m[1][0] = 0;         	m[2][0] = 0;			m[3][0] = 0;
		m[1][0] = 0; m[1][1] = cos(alpha);	m[2][1] = -sin(alpha);	m[3][1] = 0;
		m[2][0] = 0; m[1][2] = sin(alpha);	m[2][2] = cos(alpha);	m[3][2] = 0;
		m[3][0] = 0; m[1][3] = 0;			m[2][3] = 0;			m[3][3] = 1;
	}
 
	public void setRotationMatrixYAxis(float alpha)
	{
		m[0][0] = cos(alpha);	m[1][0] = 0;	m[2][0] = sin(alpha);	m[3][0] = 0;
		m[0][1] = 0;          	m[1][1] = 1;	m[2][1] = 0;			m[3][1] = 0;
		m[0][2] = -sin(alpha);	m[1][2] = 0;	m[2][2] = cos(alpha);	m[3][2] = 0;
		m[0][3] = 0;			m[1][3] = 0;	m[2][3] = 0;			m[3][3] = 1;
	}
 
	public void setRotationMatrixZAxis(float alpha)
	{
		m[0][0] = cos(alpha);	m[1][0] = -sin(alpha); m[2][0] = 0;		m[3][0] = 0;
		m[0][1] = sin(alpha);	m[1][1] = cos(alpha);  m[2][1] = 0;		m[3][1] = 0;
		m[0][2] = 0;			m[1][2] = 0;           m[2][2] = 1;		m[3][2] = 0;
		m[0][3] = 0;			m[1][3] = 0;           m[2][3] = 0;		m[3][3] = 1;
	}
 
	public void setRotationMatrix(float alpha, Vector3f u)
	{
		u.normalize();
		final float c = cos(alpha);
		final float s = sin(alpha);
		final float t = 1 - cos(alpha);
		m[0][0] = t*u.x*u.x + c;		m[1][0] = t*u.x*u.y - u.z*s;	m[2][0] = u.x*u.z*t+u.y*s;	m[3][0] = 0;
		m[0][1] = t*u.y*u.x + u.z*s;	m[1][1] = t*u.y*u.y + c;		m[2][1] = u.y*u.z*t-u.x*s;	m[3][1] = 0;
		m[0][2] = t*u.z*u.x - u.y*s;	m[1][2] = t*u.z*u.y + u.x*s;	m[2][2] = u.z*u.z*t + c;	m[3][2] = 0;
		m[0][3] = 0;					m[1][3] = 0;					m[2][3] = 0;				m[3][3] = 1;
	}
 
	public void setRotationMatrix(float alpha, double u, double v, double w)
	{
		double d = Math.sqrt(u*u+v*v+w*w);
		u = u/d;
		w = w/d;
		v = v/d;
		final double c = Math.cos(alpha);
		final double s = Math.sin(alpha);
		final double t = 1 - Math.cos(alpha);
		m[0][0] = (float) (u*u + (v*v+w*w)*c);	m[1][0] = (float) (u*v*t-w*s);			m[2][0] = (float) (u*w*t+v*s);			m[3][0] = 0;
		m[0][1] = (float) (u*v*t+w*s);			m[1][1] = (float) (v*v+(u*u+w*w)*c);	m[2][1] = (float) (v*w*t-u*s);			m[3][1] = 0;
		m[0][2] = (float) (u*w*t-v*s);			m[1][2] = (float) (v*w*t+u*s);			m[2][2] = (float) (w*w+(u*u+v*v)*c);	m[3][2] = 0;
		m[0][3] = 0;							m[1][3] = 0;							m[2][3] = 0;							m[3][3] = 1;
	}
 
	private static final float sin(float rad)
	{
		return (float) Math.sin(rad);
	}
 
	private static final float cos(float rad)
	{
		return (float) Math.cos(rad);
	}
 
	public float get(int column, int row)
	{
		return m[column][row];
	}
 
	public void set(int column, int row, float value)
	{
		m[column][row] = value;
	}
 
	public FloatBuffer getAsFloatBuffer() 
	{
		FloatBuffer buffer = ByteBuffer.allocateDirect(4*4*4).order(ByteOrder.nativeOrder()).asFloatBuffer();
/*
		buffer.put(0, m[0][0]);
		buffer.put(1, m[0][1]);
		buffer.put(2, m[0][2]);
		buffer.put(3, m[0][3]);
		buffer.put(4, m[1][0]);
		buffer.put(5, m[1][1]);
		buffer.put(6, m[1][2]);
		buffer.put(7, m[1][3]);
		buffer.put(8, m[2][0]);
		buffer.put(9, m[2][1]);
		buffer.put(10, m[2][2]);
		buffer.put(11, m[2][3]);
		buffer.put(12, m[3][0]);
		buffer.put(13, m[3][1]);
		buffer.put(14, m[3][2]);
		buffer.put(15, m[3][3]);*/
 
		for( int x = 0; x < 4; x++ )
			for( int y = 0; y < 4; y++ )
				buffer.put(m[x][y]); // it turned out that this works with shaders (uniform matrix)
 
		buffer.flip();
		return buffer;
	}
 
	public void setRow(int row, float a, float b, float c, float d) 
	{
		m[0][row] = a;
		m[1][row] = b;
		m[2][row] = c;
		m[3][row] = d;
	}	
 
	public void glOrtho(float left, float right, float bottom, float top, float near, float far)
	{
		setIdentity();
		m[0][0] = 2f/(right-left);
		m[1][1] = 2f/(top-bottom);
		m[2][2] = -2f/(far-near);
		m[3][1] = -(top+bottom)/(top-bottom);
		m[3][2] = -(far+near)/(far-near);
		m[3][0] = -(right+left)/(right-left);
		m[3][3] = 1;
	}
 
 
	public void frustum(float left, float right, float bottom, float top, float near, float far)
	{
		// http://www.songho.ca/opengl/gl_projectionmatrix.html
		setIdentity();
 
		m[0][0] = (2f*near)/(right-left);
		m[2][0] = (right+left)/(right-left);
 
		m[1][1] = (2*near)/(top-bottom);
		m[2][1] = (top+bottom)/(top-bottom);
 
		m[2][2] = -(far+near)/(far-near);
		m[3][2] = -2*(far*near)/(far-near);
 
		m[2][3] = -1;
		m[3][3] = 0;
 
 
		// http://samplecodebank.blogspot.de/2011/05/glfrustum-example.html
		/*
		m[0][0] = (2f*near)/(right-left);
		m[2][0] = (right+left)/(right-left);
		m[1][1] = (2*near)/(top-bottom);
		m[2][1] = (top+bottom)/(top-bottom);
 
		m[2][2] = (far+near)/(far-near);
		m[3][2] = 2*(far*near)/(far-near);
 
		m[2][3] = -1;
		m[3][3] = 0;
		*/
 
	}
 
 
	public void projection(float viewAngle, float width, float height, float nearClippingPlaneDistance, float farClippingPlaneDistance)
	{
	    	final float radians = (float) (viewAngle*Math.PI / 180f);
    		float halfHeight = (float) (Math.tan(radians/2)*nearClippingPlaneDistance);
	    	float halfScaledAspectRatio = halfHeight*(width/height);
    		frustum(-halfScaledAspectRatio, halfScaledAspectRatio, -halfHeight, halfHeight, nearClippingPlaneDistance, farClippingPlaneDistance);
	}	
 
 
	public void lookAt(float eyeX, float eyeY, float eyeZ, float centerX,
			float centerY, float centerZ, float upX, float upY, float upZ)
	{
		Vector3f c = new Vector3f(eyeX, eyeY, eyeZ);
 
		Vector3f u = new Vector3f();
		Vector3f v = new Vector3f();
		Vector3f w = new Vector3f();
 
		// compute "negative" look direction by substracting
		// c from the look-at point (3,4,0)
		w.subAndAssign(c, new Vector3f(centerX, centerY, centerZ)); // w = c - (3,4,0) 
		w.normalize();
 
		// compute cross product
		u.crossAndAssign(new Vector3f(upX, upY, upZ), w); // side = (0,0,1) x w
		u.normalize();
 
		v.crossAndAssign(w, u); // up = side x look
		v.normalize();
 
		Matrix4f rotation = new Matrix4f(); // identity
		rotation.setIdentity();
 
		// note the format: set(COLUMN, ROW, value)
		// it may be different for your matrix implementation
 
		rotation.set(0, 0, u.x);
		rotation.set(1, 0, u.y);
		rotation.set(2, 0, u.z);
 
		rotation.set(0, 1, v.x);
		rotation.set(1, 1, v.y);
		rotation.set(2, 1, v.z);
 
		rotation.set(0, 2, w.x);
		rotation.set(1, 2, w.y);
		rotation.set(2, 2, w.z);
 
		Matrix4f translation = new Matrix4f(); // identity
		translation.set(3, 0, -c.x);
		translation.set(3, 1, -c.y);
		translation.set(3, 2, -c.z);
 
		// view matrix
		Matrix4f view = new Matrix4f();
		view.multAndAssing(rotation, translation); // view = rotation * translation
 
		set(view);
 
	}
}

5 Antworten

  1. eric lin says:

    Ry(θ) column 4's sin(θ) should be in column 3

  2. schabby says:

    Hi Eric, thanks! Fixed it. Cheers, J

  3. erik says:

    Hi Schabby,
    could you please explain, how the cross-product works in more than 3 dimensions?

  4. schabby says:

    Hi Erik,

    uiuiui, everything beyond 3D is something that requires solid linear algebra knowledge which I am afraid I dont have. I am a mere OpenGL practitioner. I am not sure if there is a formula in a "closed form" that works in n dimensions with n > 3. I did find however: http://en.wikipedia.org/wiki/Seven-dimensional_cross_product

    Sorry and best regards,

    Johannes

  5. erik says:

    Hi Schabby,
    thanks for the answer, I'm currently trying to make a 4-dimensional game engine and this link helped me a lot with the matrices.
    Erik

Post Comment

Please notice: Comments are moderated by an Admin.