I'm completely stuck with rotation in OpenGL (again). I'm not sure what it is exactly, but I have a few theories.
First let me share some code:
I'm rendering three entities here, these are just .obj-models of Ico-Spheres, created in blender.
val testEntity: Test = new Test(new Vector3(-3.0f,0,0), new Vector3(0,0,0), 0.75f)
val testEntity2: Test = new Test(new Vector3(0,0,0), new Vector3(0,0,0), 0.75f)
val testEntity3: Test = new Test(new Vector3(3.0f,0,0), new Vector3(0,0,0), 0.75f)
override def render(): Unit =
{
camera.update()
testEntity.addRotation(new Vector3(1,0,0))
testEntity.render(camera.getViewProjectionMatrix, camera.getViewMatrix)
testEntity2.addRotation(new Vector3(0,1,0))
testEntity2.render(camera.getViewProjectionMatrix, camera.getViewMatrix)
testEntity3.addRotation(new Vector3(0,0,1))
testEntity3.render(camera.getViewProjectionMatrix, camera.getViewMatrix)
}
As you can see, all I'm doing is setting three spheres next to each other and trying to rotate each around one axis.
addRotation(new Vector3(1,0,0)) just adds an rotation to the orientation of the entity (which is (0,0,0) at the beginning), so in this case it adds 1 on the x-axis per frame.
Theory #1 - The calculation of the model matrix is wrong
This is how I calculate my model matrix for each entity per frame
protected def calculateModelMatrix(position: Vector3, orientation: Vector3, scale: Float): Matrix4 =
{
val matrix: Matrix4 = Matrix4.Identity
matrix.scale(new Vector3(scale, scale, scale))
matrix.rotate(new Vector3(1,0,0), Math.toRadians(orientation.x).toFloat)
matrix.rotate(new Vector3(0,1,0), Math.toRadians(orientation.y).toFloat)
matrix.rotate(new Vector3(0,0,1), Math.toRadians(orientation.z).toFloat)
matrix.translate(position)
matrix
}
Rotating should always happen in the point of origin, but the Identity matrix takes care of this. First we scale, then we do our rotations and then translate the entity to the right position.
The rotate
-method of the Matrix4-class looks like this:
def rotate(axis: Vector3, angle: Float): Unit =
{
if(angle == 0) return
axis match
{
case Vector3(1,0,0) => rotateX(angle)
case Vector3(0,1,0) => rotateY(angle)
case Vector3(0,0,1) => rotateZ(angle)
case _ => //println("Rotation not yet implemented!")
}
}
private def rotateX(angle: Float): Unit =
{
val cos: Float = Math.cos(angle).toFloat
val sin: Float = Math.sin(angle).toFloat
this.m11 = cos
this.m12 = -sin
this.m21 = sin
this.m22 = cos
}
private def rotateY(angle: Float): Unit =
{
val cos: Float = Math.cos(angle).toFloat
val sin: Float = Math.sin(angle).toFloat
this.m00 = cos
this.m02 = sin
this.m20 = -sin
this.m22 = cos
}
private def rotateZ(angle: Float): Unit =
{
val cos: Float = Math.cos(angle).toFloat
val sin: Float = Math.sin(angle).toFloat
this.m00 = cos
this.m01 = -sin
this.m10 = sin
this.m12 = cos
}
For the helper-methods I kept strictly to the maths (or at least tried to) (reference: http://inside.mines.edu/fs_home/gmurray/ArbitraryAxisRotation/ and https://en.wikipedia.org/wiki/Rotation_matrix)
Theory #2 - The calculation of the projection and/or view-matrix is wrong
For each frame I call camera.update()
. It should update my view and projectionMatrix according to the position the camera is:
def update(): Unit =
{
updateViewMatrix()
updateViewProjectionMatrix()
}
private def calculateViewProjectionMatrix(): Matrix4 =
{
Matrix4.multiply(this.projectionMatrix, this.viewMatrix)
}
private def updateViewProjectionMatrix(): Unit =
{
this.viewProjectionMatrix = calculateViewProjectionMatrix()
}
private def calculateProjectionMatrix(): Matrix4 =
{
val aspectRatio: Float = 1024 / 768 // TODO get this from somewhere
// val yScale: Float = ((1.0f / Math.tan(Math.toRadians(FOV / 2f))) * aspectRatio).toFloat
val yScale: Float = (1.0f / Math.tan(Math.toRadians(FOV / 2f))).toFloat
val xScale: Float = yScale / aspectRatio
val frustumLength = FAR_PLANE - NEAR_PLANE
val matrix: Matrix4 = Matrix4.Zero
matrix.m00 = xScale
matrix.m11 = yScale
matrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustumLength)
matrix.m23 = -1.0f
matrix.m32 = -((2.0f * NEAR_PLANE * FAR_PLANE) / frustumLength)
matrix
}
private def calculateViewMatrix(): Matrix4 =
{
val matrix: Matrix4 = Matrix4.Identity
matrix.rotate(new Vector3(1,0,0), Math.toRadians(this.pitch).toFloat)
matrix.rotate(new Vector3(0,1,0), Math.toRadians(this.yaw).toFloat)
// matrix.rotate(new Vector3(0,0,1), Math.toRadians(this.roll).toFloat)
matrix.translate(new Vector3(-this.position.x, -this.position.y, -this.position.z))
matrix
}
The outcome is the following:
Basically absolute garbage! The one rotation around the y-axis (in the middle) is the only one not translating while rotating. Still it looks a bit compressed/mashed (or is this just me?!) And the one rotating around z? It is just completely off!
Now I also tried to reproduce rotate
from https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/util/vector/Matrix4f.java
Looks like this in code:
def rotate(axis: Vector3, angle: Float): Unit =
{
val cosAngle: Float = Math.cos(angle).toFloat
val sinAngle: Float = Math.sin(angle).toFloat
val oneMinusCosAngle: Float = 1.0f - cosAngle
val xy: Float = axis.x * axis.y
val xz: Float = axis.x * axis.z
val yz: Float = axis.y * axis.z
val xs: Float = axis.x * sinAngle
val ys: Float = axis.y * sinAngle
val zs: Float = axis.z * sinAngle
val f00: Float = axis.x * axis.x * oneMinusCosAngle + cosAngle
val f01: Float = xy * oneMinusCosAngle + zs
val f02: Float = xz * oneMinusCosAngle - ys
val f10: Float = xy * oneMinusCosAngle - zs
val f11: Float = axis.y * axis.y * oneMinusCosAngle + cosAngle
val f12: Float = yz * oneMinusCosAngle + xs
val f20: Float = xz * oneMinusCosAngle + ys
val f21: Float = yz * oneMinusCosAngle - xs
val f22: Float = axis.z * axis.z * oneMinusCosAngle + cosAngle
val t00: Float = this.m00 * f00 + this.m10 * f01 + this.m20 * f02
val t01: Float = this.m01 * f00 + this.m11 * f01 + this.m21 * f02
val t02: Float = this.m02 * f00 + this.m12 * f01 + this.m22 * f02
val t03: Float = this.m03 * f00 + this.m13 * f01 + this.m23 * f02
val t10: Float = this.m00 * f10 + this.m10 * f11 + this.m20 * f12
val t11: Float = this.m01 * f10 + this.m11 * f11 + this.m21 * f12
val t12: Float = this.m02 * f10 + this.m12 * f11 + this.m22 * f12
val t13: Float = this.m03 * f10 + this.m13 * f11 + this.m23 * f12
this.m20 = this.m00 * f20 + this.m10 * f21 + this.m20 * f22
this.m21 = this.m01 * f20 + this.m11 * f21 + this.m21 * f22
this.m22 = this.m02 * f20 + this.m12 * f21 + this.m22 * f22
this.m23 = this.m03 * f20 + this.m13 * f21 + this.m23 * f22
this.m00 = t00
this.m01 = t01
this.m02 = t02
this.m03 = t03
this.m10 = t10
this.m11 = t11
this.m12 = t12
this.m13 = t13
}
And this is the outcome:
It looks better than my own implementation, but still for the x- and z-axis the entities are translating while rotating.
I can't understand why this is the case and I debugged for a very long time now, to no real avail. Something must be really wrong in what I do.
Later down the road I wanted to switch to Quaternions
but I figured, if the basic rotations don't work, it's not a good idea to implement Quaternions just yet.
matrix.translate(new Vector3(0,0,0))
at the beginning does not change a thing. I also mentioned that explicitly in my post. – user4063815