cube.rotation.set(),正如您已经发现的那样,沿对象的局部轴创建旋转。在第一次旋转期间,例如,沿 X 轴,世界坐标中的局部 X 轴与世界 X 轴完全相等。然而,在旋转之后,世界坐标中的局部 Y 和 Z 轴不再等于世界 Y 和 Z 轴。因此,沿这些轴的后续旋转将不再看起来像沿世界轴的旋转。如果您再次更改旋转顺序,则只有第一次旋转将发生在偶然与世界空间相同的局部空间中。所有后续旋转都将在不再相同的局部空间中计算。
您想要将对象旋转 3 次:一次沿着世界 X 轴,一次沿着世界 Y 轴,一次沿着世界 Z 轴。但是我们现在知道所有的旋转都发生在局部空间中。在世界空间中进行旋转的关键是要问一个问题:“这个世界轴在我的对象的局部空间中是什么样子的?”如果您可以取一个世界轴,将其表示为对象局部空间中的向量,然后沿该向量进行旋转,那么您就可以沿世界向量进行旋转,而不管特定的局部空间如何。
让我们把这个想法融入你的小提琴:
var x = toRadians($("#rotateX").slider("option", "value"));
var y = toRadians($("#rotateY").slider("option", "value"));
var z = toRadians($("#rotateZ").slider("option", "value"));
var rotation = new THREE.Matrix4();
rotation.multiply(new THREE.Matrix4().makeRotationX(x));
var worldYAxis = new THREE.Vector3(0, 1, 0).applyMatrix4(new THREE.Matrix4().getInverse(rotation));
var rotationWorldY = new THREE.Matrix4().makeRotationAxis(worldYAxis, y);
rotation.multiply(rotationWorldY);
var worldZAxis = new THREE.Vector3(0, 0, 1).applyMatrix4(new THREE.Matrix4().getInverse(rotation));
var rotationWorldZ = new THREE.Matrix4().makeRotationAxis(worldZAxis, z);
rotation.multiply(rotationWorldZ);
cube.matrixAutoUpdate = false;
cube.matrix = rotation;
如果您尝试此代码,您会注意到当 Y 和 Z 旋转为零时,X 旋转沿世界 X 轴,当 Z 旋转为零时,Y 旋转沿世界 Y 轴,而 Z 旋转始终沿Z轴。
但是,如果在设置了 X、Y、Z 旋转之后,我们再次转动 X 滑块会怎样?您会注意到旋转不再沿世界 X 轴,而是沿局部 X 轴。原因很简单,尽管可能无法解释为什么会这样。一句话——轮班不通勤。你看,旋转X(a) * Y(b) * Z(c) * X(d) 通常不等于X(a + d) * Y(b) * Z(c)。也就是说,如果你沿着世界 X 旋转 a,然后沿着世界 Y 旋转 b,然后沿着世界 Z 旋转 c,然后再沿着世界 X 旋转 d,那么结果就不一样了如果你从一开始就沿着世界 X 旋转 a + d,你会得到的旋转。原因是变换开始时的“世界 X”和变换结束时的“世界 X”是两个不同的向量,它们的值取决于到目前为止所做的所有变换。因此,它们代表局部空间中不同的旋转轴。
我将尝试预测下一个问题:“我怎样才能做到,无论我如何左右移动滑块,对象总是只沿着世界轴旋转?”答案建立在我们已经知道的围绕世界轴旋转的基础上——世界轴必须表示为局部空间中的向量,并且旋转必须围绕该向量进行。该想法的实现如下:
var prevX = 0, prevY = 0, prevZ = 0;
function doRotate() {
var x = toRadians($("#rotateX").slider("option", "value"));
var y = toRadians($("#rotateY").slider("option", "value"));
var z = toRadians($("#rotateZ").slider("option", "value"));
var inverse = new THREE.Matrix4().getInverse(cube.matrix);
var rotation = cube.matrix;
var worldXAxis = new THREE.Vector3(1, 0, 0).applyMatrix4(new THREE.Matrix4().getInverse(rotation));
var rotationWorldX = new THREE.Matrix4().makeRotationAxis(worldXAxis, x - prevX);
rotation.multiply(rotationWorldX);
var worldYAxis = new THREE.Vector3(0, 1, 0).applyMatrix4(new THREE.Matrix4().getInverse(rotation));
var rotationWorldY = new THREE.Matrix4().makeRotationAxis(worldYAxis, y - prevY);
rotation.multiply(rotationWorldY);
var worldZAxis = new THREE.Vector3(0, 0, 1).applyMatrix4(new THREE.Matrix4().getInverse(rotation));
var rotationWorldZ = new THREE.Matrix4().makeRotationAxis(worldZAxis, z - prevZ);
rotation.multiply(rotationWorldZ);
prevX = x;
prevY = y;
prevZ = z;
cube.matrixAutoUpdate = false;
cube.matrix = rotation;
}
小提琴:https://jsfiddle.net/2whv0e8o/13/
上述函数绑定为所有滑块的slide事件处理程序:"slide": doRotate。现在,如果您尝试左右旋转,您会注意到无论框的当前方向如何,移动滑块总是会导致沿世界轴旋转。但是有一个警告:滑块的值不再代表围绕任何特定轴的实际旋转角度。您会注意到doRotate 函数仅沿世界轴应用增量旋转,因为它们当前以局部坐标表示。我们已经实现了能够应用始终沿世界轴完成的增量旋转的目标,但是我们失去了 X、Y 和 Z 滑块绝对值的含义。但是,如果这些值没有任何意义,那么可以使用什么来将对象的当前旋转保存为 the 旋转?根据我的经验,最好只批发存储对象的变换矩阵。也就是说,与其尝试保存三个特定的旋转角度、一个平移向量,还可能加上缩放参数,只需将整个矩阵保存为对象的一部分。