4x4变换矩阵(libgdx)的意外旋转角度

时间:2014-01-11 12:51:08

标签: java 3d rotation libgdx linear-algebra

enter image description here

这是它的要点,你可以看到旋转角度从0到236然后跳到119 - 0,很奇怪。

我想得到一个模型的角度,以度数(0-360)为单位。但是到目前为止使用libgdx我无法使用:

获得预期的结果
angle = modelInstance.transform.getRotation(new Quaternion()).getAxisAngle(Vector3.Zero)

假设我只围绕y轴旋转,这是从libgdx中的变换矩阵获得旋转角度的正确方法吗?

我已经包含了一个完整的示例类,以证明我通过这种方式获得旋转所获得的角度不是预期的,而不是0到360的完整旋转我得到类似0 - 117然后跳转到243。

public class RotateExperiment extends InputAdapter implements ApplicationListener {

    private Environment environment;
    private ModelBatch modelBatch;
    private Camera camera;
    private ModelInstance player;

    @Override
    public void create() {
        this.player = new ModelInstance(new ModelBuilder().createCone(10, 10,10, 10, new Material(ColorAttribute.createDiffuse(Color.GRAY)), VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal));

        int w = Gdx.graphics.getWidth();
        int h = Gdx.graphics.getHeight();

        modelBatch = new ModelBatch();
        camera = new PerspectiveCamera(67, w, h);
        camera.position.set(10f, 10f, 10f);
        camera.lookAt(0,0,0);
        camera.near = 0.1f;
        camera.far = 300f;
        camera.update();

        environment = new Environment();
        environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
        environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
    }

    @Override
    public void dispose() {}

    private void updateModel(){
        Vector3 directionVector = new Vector3(0,0,0);
        if(Gdx.input.isKeyPressed(Input.Keys.S)){
            directionVector.z = -1;
        }
        if(Gdx.input.isKeyPressed(Input.Keys.W)){
            directionVector.z = 1;
        }
        if(Gdx.input.isKeyPressed(Input.Keys.A)){
            if(Gdx.input.isButtonPressed(Input.Buttons.RIGHT)){
                directionVector.y = 1;
            } else {
                player.transform.rotate(Vector3.Y, 90 * Gdx.graphics.getDeltaTime());
            }
        }
        if(Gdx.input.isKeyPressed(Input.Keys.D)){
            if(Gdx.input.isButtonPressed(Input.Buttons.RIGHT)){
                directionVector.y = -1;
            } else {
                player.transform.rotate(Vector3.Y, -90 * Gdx.graphics.getDeltaTime());
            }
        }
        if(directionVector.z != 0 || directionVector.y != 0){
            player.transform.translate(directionVector.nor().scl(20 * Gdx.graphics.getDeltaTime()));
        }
    }

    public void update() {
        if(player != null){
            updateModel();
            //update angle text -> actual test code
            outputText.setText(String.valueOf(player.transform.getRotation(new Quaternion()).getAxisAngle(Vector3.Zero)));
        }
    }

    @Override
    public void render() {
        update();
        Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        modelBatch.begin(camera);
        modelBatch.render(player, environment);
        modelBatch.end();
    }

    @Override
    public void resize(int width, int height) {
        Gdx.gl.glViewport(0, 0, width, height);
    }

    @Override
    public void pause() {}

    @Override
    public void resume() {}

    private final static JLabel outputText = new JLabel("test");

    public static void main(String[] args) {
        LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "TestRotation";
        cfg.useGL20 = true;
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        cfg.width = screenSize.width;
        cfg.height = screenSize.height;

        new LwjglApplication(new RotateExperiment(), cfg);

        JFrame outputWindow = new JFrame();
        JPanel outputPanel = new JPanel();

        outputPanel.add(outputText);
        outputWindow.add(outputPanel);
        outputWindow.setAlwaysOnTop(true);
        outputWindow.setVisible(true);
        outputWindow.pack();
        outputWindow.toFront();
    }
}

如果您正在运行该示例,请在加载后单击libgdx渲染窗口,并且可以使用“A”和“D”控制模型旋转。使用上述方法生成的旋转也在单独的JFrame中输出。

修改 我相信可疑代码可能采用libgdx方法将4x4转换矩阵转换为四元数: Quaternion.setFromAxes 。当你调用transform.getRotation()时,这会调用setFromAxes并传入矩阵轴。我已经看到了open issue,但正如你在他的示例中看到的那样,我的立方体模型没有缩放,所以这不可能是正在发生的事情。 setFromAxes代码如下:

            // the trace is the sum of the diagonal elements; see
    // http://mathworld.wolfram.com/MatrixTrace.html
    final float m00 = xx, m01 = xy, m02 = xz;
    final float m10 = yx, m11 = yy, m12 = yz;
    final float m20 = zx, m21 = zy, m22 = zz;
    final float t = m00 + m11 + m22;

    // we protect the division by s by ensuring that s>=1
    double x, y, z, w;
    if (t >= 0) { // |w| >= .5
        double s = Math.sqrt(t + 1); // |s|>=1 ...
        w = 0.5 * s;
        s = 0.5 / s; // so this division isn't bad
        x = (m21 - m12) * s;
        y = (m02 - m20) * s;
        z = (m10 - m01) * s;
    } else if ((m00 > m11) && (m00 > m22)) {
        double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1
        x = s * 0.5; // |x| >= .5
        s = 0.5 / s;
        y = (m10 + m01) * s;
        z = (m02 + m20) * s;
        w = (m21 - m12) * s;
    } else if (m11 > m22) {
        double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1
        y = s * 0.5; // |y| >= .5
        s = 0.5 / s;
        x = (m10 + m01) * s;
        z = (m21 + m12) * s;
        w = (m02 - m20) * s;
    } else {
        double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1
        z = s * 0.5; // |z| >= .5
        s = 0.5 / s;
        x = (m02 + m20) * s;
        y = (m21 + m12) * s;
        w = (m10 - m01) * s;
    }

    return set((float)x, (float)y, (float)z, (float)w);

1 个答案:

答案 0 :(得分:3)

因此,假设您有一个非缩放模型,并且只围绕y轴执行了旋转,则以下代码将仅从模型中的变换矩阵中获取角度0 -360:

        Vector3 axisVec = new Vector3();
        int angle = (int) (player.transform.getRotation(new Quaternion()).getAxisAngle(axisVec) * axisVec.nor().y);
        angle = angle < 0 ? angle + 360 : angle; //convert <0 values

如果你有一个缩放模型,那么根据open issue的当前解决方案是规范化setFromAxes代码中的轴,我已经用以下内容替换了我的setFromAxes,直到它被更新:

private Quaternion setFromAxes(float xx, float xy, float xz, float yx, float yy, float yz, float zx, float zy, float zz){

    //normalise axis
    Vector3 xAxis = new Vector3(xx, xy, xz).nor();
    Vector3 yAxis = new Vector3(yx, yy, yz).nor();
    Vector3 zAxis = new Vector3(zx, zy, zz).nor();

    xx = xAxis.x;
    xy = xAxis.y;
    xz = xAxis.z;

    yx = yAxis.x;
    yy = yAxis.y;
    yz = yAxis.z;

    zx = zAxis.x;
    zy = zAxis.y;
    zz = zAxis.z;

    // the trace is the sum of the diagonal elements; see
    // http://mathworld.wolfram.com/MatrixTrace.html
    final float m00 = xx, m01 = xy, m02 = xz;
    final float m10 = yx, m11 = yy, m12 = yz;
    final float m20 = zx, m21 = zy, m22 = zz;
    final float t = m00 + m11 + m22;

    // we protect the division by s by ensuring that s>=1
    double x, y, z, w;
    if (t >= 0) { // |w| >= .5
        double s = Math.sqrt(t + 1); // |s|>=1 ...
        w = 0.5 * s;
        s = 0.5 / s; // so this division isn't bad
        x = (m21 - m12) * s;
        y = (m02 - m20) * s;
        z = (m10 - m01) * s;
    } else if ((m00 > m11) && (m00 > m22)) {
        double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1
        x = s * 0.5; // |x| >= .5
        s = 0.5 / s;
        y = (m10 + m01) * s;
        z = (m02 + m20) * s;
        w = (m21 - m12) * s;
    } else if (m11 > m22) {
        double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1
        y = s * 0.5; // |y| >= .5
        s = 0.5 / s;
        x = (m10 + m01) * s;
        z = (m21 + m12) * s;
        w = (m02 - m20) * s;
    } else {
        double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1
        z = s * 0.5; // |z| >= .5
        s = 0.5 / s;
        x = (m02 + m20) * s;
        y = (m21 + m12) * s;
        w = (m10 - m01) * s;
    }

    return new Quaternion((float)x, (float)y, (float)z, (float)w);

}