0
votes

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:

enter image description here

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:

enter image description here

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.

1
rotations are always around the origin. So you can first translate the center of the ball to the origin, then rotate and then translate back.ratchet freak
@ratchetfreak: That's why I use an identity matrix to begin with. So a matrix.translate(new Vector3(0,0,0)) at the beginning does not change a thing. I also mentioned that explicitly in my post.user4063815
To code 1: Matrices cannot be combined by simply overwriting values in it. Instead you have to multiply all primitive matrices together. In your current implementation, for example, rotate will override some of the values set by scale.BDL
To code 2: The translate while rotating because the origin of the sphere seems not to be in its center, but in the bottom-most point of the sphere. This is then the point around which the spheres rotate.BDL

1 Answers

0
votes

So, according to @BDL there were two errors and I (kind of) fixed both of them:

  1. The Translating-While-Rotating-Bug

The Problem was that the .obj file was not centered at the origin, so I fixed this with the kind help of Blender. When it was centered no more translating occured. Stupid mistake!

  1. The actual rotation-bug

There was no bug in the lwjgl-implementation. After I fixed 1. everything worked. But I wanted to fix my own implementation as well, so again, thanks to BDL I knew I had to multiply my matrices.

For X and Y this already works, for Z not so good, but basically what I do:

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)
    val rotXYMatrix: Matrix4 = Matrix4.multiply(Matrix4.rotate(new Vector3(0,1,0), Math.toRadians(orientation.y).toFloat), matrix)
    val rotXYZMatrix: Matrix4 = Matrix4.multiply(Matrix4.rotate(new Vector3(0,0,1), Math.toRadians(orientation.z).toFloat), rotXYMatrix)
    rotXYZMatrix.translate(position)

    rotXYZMatrix
}

Not sure why it doesn't work for z but here is the (semi-)working result:

enter image description here

edit: Also fixed now:

Was a bug in:

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
  }

Of course not this.m12 = cos but this.m11 = cos. Working as expected now!