作为学习经验,我正在尝试构建一个没有任何库的简单 3D 游戏。我已经设法弄清楚渲染(大部分情况下),并且我已经让 4D 矩阵变换工作得很好。
我无法相对于自己的方向旋转我的播放器对象;我想建立一个类似街机的飞行模型,以便对象相对于其当前方向而不是相对于世界空间滚动、偏航和俯仰。
一些文章指出四元数是比 4D 矩阵更好的旋转解决方案。经过一点阅读,我认为我已经弄清楚了,但在实践中我无法让这种方法发挥作用。我想我要么不正确地计算旋转轴,要么完全错误地将它们应用于四元数。
我不完全理解数学是如何工作的,所以这里可能有一些我遗漏的明显东西。我已经阅读了有关此主题的几篇文章,并阅读了有关四元数数学的内容,直到我的大脑融化了,所以任何帮助都会很棒!
以下是我的代码的相关部分、JSFiddle 演示的链接以及我发现对我的研究最有帮助的两篇文章:
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
}