绕固定轴旋转对象


12

我试图让我的应用程序用户通过在屏幕上拖动手指来旋转在屏幕中央绘制的3D对象。屏幕上的水平移动表示绕固定的Y轴旋转,而垂直移动表示绕X轴旋转。我遇到的问题是,如果我只允许绕一个轴旋转,则对象旋转良好,但是一旦引入第二个旋转,该对象就不会按预期旋转。

这是正在发生的情况的图片:

在此处输入图片说明

蓝色轴代表我的固定轴。画出具有此固定蓝色轴的屏幕。这就是我要相对于对象旋转的对象。发生的是红色。

这是我所知道的:

  1. 围绕Y(0,1,0)的第一次旋转使模型从蓝色空间(称为空间A)移至另一个空间(称为空间B)。
  2. 尝试再次使用向量(1、0、0)旋转时,空间B中的x轴会旋转,而不是空间A中的x轴旋转,这不是我的意思。

考虑到我(想)知道的内容(为简洁起见,省略了W坐标),这是我尝试的操作:

  1. 首先使用四元数绕Y(0,1,0)旋转。
  2. 将旋转Y四元数转换为矩阵。
  3. 将Y旋转矩阵乘以我的固定轴x矢量(1、0、0),以获得相对于新空间的X轴。
  4. 使用四元数围绕这个新的X向量旋转。

这是代码:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};
    float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();

    // multiply x axis by rotationY to put it in object space
    float[] xAxisObjectSpace = new float[4];
    multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);

    float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();

    float[] rotationMatrix = new float[16];
    multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
    return rotationMatrix;
  }

这不符合我的预期。旋转似乎有效,但在某些点上水平移动不会绕Y轴旋转,而是绕Z轴旋转。

我不确定我的理解是错误的,还是其他原因引起了问题。除了旋转之外,我还对对象进行了其他一些转换。在应用旋转之前,我将对象移至中心。我使用上面函数返回的矩阵旋转它,然后在Z方向上将其平移-2以便看到对象。我不认为这会打乱我的工作,但是无论如何这是下面的代码:

private float[] getMvpMatrix() {
    // translates the object to where we can see it
    final float[] translationMatrix = new float[16];
    setIdentityM(translationMatrix, 0);
    translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);

    float[] rotationMatrix = rotationMatrix();

    // centers the object
    final float[] centeringMatrix = new float[16];
    setIdentityM(centeringMatrix, 0);
    float moveX = (extents.max[0] + extents.min[0]) / 2f;
    float moveY = (extents.max[1] + extents.min[1]) / 2f;
    float moveZ = (extents.max[2] + extents.min[2]) / 2f;
    translateM(centeringMatrix, 0, //
      -moveX, //
      -moveY, //
      -moveZ //
    );

    // apply the translations/rotations
    final float[] modelMatrix = new float[16];
    multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
    multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);

    final float[] mvpMatrix = new float[16];
    multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
    return mvpMatrix;
  }

我已经坚持了几天。非常感谢您的帮助。

================================================== ================

更新:

使它在Unity中起作用很简单。这是一些旋转以原点为中心的多维数据集的代码:

public class CubeController : MonoBehaviour {

    Vector3 xAxis = new Vector3 (1f, 0f, 0f);
    Vector3 yAxis = new Vector3 (0f, 1f, 0f);

    // Update is called once per frame
    void FixedUpdate () {
        float horizontal = Input.GetAxis ("Horizontal");
        float vertical = Input.GetAxis ("Vertical");

        transform.Rotate (xAxis, vertical, Space.World);
        transform.Rotate (yAxis, -horizontal, Space.World);
    }
}

使旋转行为符合我期望的部分是变换函数的Space.World参数Rotate

如果我可以使用Unity,那么不幸的是,我必须自己编写这种行为。


1
我在这里的答案gamedev.stackexchange.com/questions/67199/…可能会帮助您..
concept3d 2015年

我理解您的答案背后的概念,但是如何实施却使我无所适从。
Christopher Perry

如果您检查了其他答案,则句法答案将实现我所解释的想法。
concept3d

不,不是,它正在围绕不同的轴进行多次旋转。您建议进行一次旋转。
克里斯托弗·佩里

Answers:


3

您遇到的问题称为万向锁。我认为您想做的就是称为弧线旋转。Arcball的数学可能有点复杂。

一种更简单的方法是在屏幕上找到垂直于2d滑动的2d向量。

取向量并将其投影到飞机附近的相机上,以获取世界空间中的3d向量。屏幕空间到世界空间

然后使用此向量创建一个四元数并将其乘以GameObject。可能有一些咽或缩过渡。

编辑:

统一示例:在统一示例中,游戏对象旋转的内部状态是四元数而不是矩阵。transform.rotation方法根据提供的矢量和角度生成四元数,并将该四元数与游戏对象旋转四元数相乘。它仅在以后生成用于渲染或物理的旋转矩阵。四元数是加性的,并且避免万向节锁定。

您的代码应如下所示:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

ArcBall旋转Opengl教程


我没有获得万向节锁定,第一个旋转将移动轴,因此第二个旋转将基于移动的轴。请再看看我提供的图片。
克里斯托弗·佩里

我希望你知道了。简而言之。可以将四元数相乘以应用旋转。您只应在所有旋转计算结束时生成矩阵。同样,xQ * yQ不等于yQ * xQ。四元数是不可交换的,就像克里斯托弗·佩里(Christopher Perry)所说的那样。
安东尼·雷蒙多

我将所有代码都放在这里。我觉得我已经尝试了一切。也许其他人的眼睛会抓住我的错误。
克里斯托弗·佩里

我不接受,堆栈交换算法会自动将这些点分配给您。:/
Christopher Perry 2015年

对于这种不公正行为,我感到抱歉。
Anthony Raimondo

3

通过旋转累积的旋转矩阵,我可以获得预期的旋转。

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);

1

您的图像与rotationMatrix代码相对应,通过将x轴旋转为以前的y旋转,您将获得局部x轴,然后围绕该对象旋转对象,您将获得显示在图像中的结果。为了使旋转从用户角度来看是合乎逻辑的,您可能希望使用世界坐标轴来旋转对象。

如果希望用户能够多次旋转对象,则将旋转存储在四元数而不是矩阵中是有意义的,随着时间的流逝,多次旋转(以及浮点误差)将使矩阵看起来越来越不像一个旋转矩阵,当然也发生在四元数上,但是只要对四元数进行归一化,就可以使其恢复良好的旋转。

只需将标识四元数用作初始值,并且每次用户滑动屏幕时,您都使用Quaternion.fromAxisAngle(yAxis, -angleX)代码旋转四元数。对于x旋转始终使用(1,0,0,1),对于y旋转始终使用(0,1,0,1)。

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

由于您没有提及语言或任何特定框架,因此Quaternion上的方法当然可以用不同的方式来调用,并且不必经常进行规范化调用,但是由于轮换来自用户滑动屏幕,因此它们不会放慢了速度,这样四元数就不可能从单位四元数中滑落了。


我正在做完全相同的行为。
Christopher Perry

1
@ChristopherPerry尝试反转旋转顺序:myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()它们不是可交换的,因此您执行它们的顺序很重要。如果您的框架/语言不起作用,请添加另一条注释,而我会对此进行深入研究。
丹尼尔·卡尔森

那也不行,我得到相同的行为。
Christopher Perry

我将所有代码都放在这里
Christopher Perry
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.