1

As a learning experience, I'm trying to build a simple 3D game without any libraries. I've managed to figure out rendering (for the most part), and I've gotten 4D matrix transforms to work well enough.

I'm having trouble rotating my player object relative to its own orientation; I want to build an arcade-like flight model, so that the object rolls, yaws, and pitches relative to its current orientation, not relative to the world space.

A few articles pointed me to quaternions as a better solution for rotation than the 4D matrix. After a little reading I thought I had it figured out, but in practice I can't get that approach to work. I think I'm either calculating the rotational axes incorrectly, or I'm applying them to the quaternions completely the wrong way.

I don't fully understand how the math works, so there could be something obvious here that I'm missing. I've read several articles on this topic, and read about quaternion math until my brain melted, so any help would be great!

Here are relevant parts of my code, a link to a JSFiddle demo, and two of the articles I found the most helpful in my research:

https://jsfiddle.net/peternatewood/guwd03et/

https://www.3dgep.com/understanding-quaternions/

http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/

var player = {
  // These keep track of player input: -1 and 1 are opposite directions, 0 is no input
  roll: 0, pitch: 0, yaw: 0,
  radians: [0, 0, 0], // X, Y, Z
  // Keep track of the rotational axes
  axisX: [1, 0, 0],
  axisY: [0, 1, 0],
  axisZ: [0, 0, 1],
  // 4D matrices
  scale: [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  trans: [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  rotX: [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  rotY: [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ],
  rotZ: [
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
  ]
};

/*
  This also updates the player's radians, so I use it
  for both the matrix and quaternion transforms
  "mod" is a timing modifier to account for varying framerates
*/
function updateMatrix3D(mod) {
  var rad, sin, cos;

  if (player.yaw) {
    player.axisY[0] = Math.sin(player.radians[0]);
    player.axisY[1] = Math.cos(player.radians[1]);
    player.axisY[2] = Math.sin(player.radians[2]);

    rad = player.radians[1] + mod * player.yaw / 40;
    if (rad >= TAU) {
      rad -= TAU;
    }
    if (rad < 0) {
      rad += TAU;
    }
    player.radians[1] = rad;

    var sinY = Math.sin(-player.radians[1]);
    var cosY = Math.cos(-player.radians[1]);

    player.rotY[ 0] =  cosY;
    player.rotY[ 2] = -sinY;
    player.rotY[ 8] =  sinY;
    player.rotY[10] =  cosY;
  }

  if (player.roll) {
    player.axisZ[0] = Math.sin(player.radians[0]);
    player.axisZ[1] = Math.sin(player.radians[1]);
    player.axisZ[2] = Math.cos(player.radians[2]);

    rad = player.radians[2] + mod * player.roll / 40;
    if (rad >= TAU) {
      rad -= TAU;
    }
    if (rad < 0) {
      rad += TAU;
    }
    player.radians[2] = rad;

    var sinZ = Math.sin(player.radians[2]);
    var cosZ = Math.cos(player.radians[2]);

    player.rotZ[ 0] =  cosZ;
    player.rotZ[ 1] = -sinZ;
    player.rotZ[ 4] =  sinZ;
    player.rotZ[ 5] =  cosZ;
  }

  if (player.pitch) {
    player.axisX[0] = Math.cos(player.radians[0]);
    player.axisX[1] = Math.sin(player.radians[1]);
    player.axisX[2] = Math.sin(player.radians[2]);

    rad = player.radians[0] + mod * player.pitch / 40;
    if (rad >= TAU) {
      rad -= TAU;
    }
    if (rad < 0) {
      rad += TAU;
    }
    player.radians[0] = rad;

    var sinX = Math.sin(-player.radians[0]);
    var cosX = Math.cos(-player.radians[0]);

    player.rotX[ 5] =  cosX;
    player.rotX[ 6] =  sinX;
    player.rotX[ 9] = -sinX;
    player.rotX[10] =  cosX;
  }
}

// Quaternion => [scalar, x-value * i, y-value * j, z-value * k]
function transformQuat(v, q) {
  return [
    q[3]*q[3]*v[0] + 2*q[1]*q[3]*v[2] - 2*q[2]*q[3]*v[1] + q[0]*q[0]*v[0] + 2*q[1]*q[0]*v[1] + 2*q[2]*q[0]*v[2] - q[2]*q[2]*v[0] - q[1]*q[1]*v[0],
    2*q[0]*q[1]*v[0] + q[1]*q[1]*v[1] + 2*q[2]*q[1]*v[2] + 2*q[3]*q[2]*v[0] - q[2]*q[2]*v[1] + q[3]*q[3]*v[1] - 2*q[0]*q[3]*v[2] - q[0]*q[0]*v[1],
    2*q[0]*q[2]*v[0] + 2*q[1]*q[2]*v[1] + q[2]*q[2]*v[2] - 2*q[3]*q[1]*v[0] - q[1]*q[1]*v[2] + 2*q[3]*q[0]*v[1] - q[0]*q[0]*v[2] + q[3]*q[3]*v[2],
    1 // I set w to 1 because I don't need to scale anything
  ];
}

/*
quatX => Pitch
quatY => Yaw
quatZ => Roll
*/
function renderQuaternion() {
  var quatX = [
    player.axisX[0] * Math.sin(player.radians[0] / 2),
    player.axisX[1] * Math.sin(player.radians[0] / 2),
    player.axisX[2] * Math.sin(player.radians[0] / 2),
    Math.cos(player.radians[0] / 2)
  ];
  var quatY = [
    player.axisY[0] * Math.sin(player.radians[1] / 2),
    player.axisY[1] * Math.sin(player.radians[1] / 2),
    player.axisY[2] * Math.sin(player.radians[1] / 2),
    Math.cos(player.radians[1] / 2)
  ];
  var quatZ = [
    player.axisZ[0] * Math.sin(player.radians[2] / 2),
    player.axisZ[1] * Math.sin(player.radians[2] / 2),
    player.axisZ[2] * Math.sin(player.radians[2] / 2),
    Math.cos(player.radians[2] / 2)
  ];

  // Rendering happens here
}

0 Answers0