javafx - 如何将偏航,俯仰和滚动增量(不是欧拉)应用于节点旋转轴而不是场景旋转轴?

时间:2018-02-18 11:05:06

标签: javafx rotation javafx-3d

请忍受我的长期问题,我想尽可能清楚。 (如另一个问题所示。)

在下面的示例中,所有旋转按钮都是来自陀螺仪传感器的陀螺仪值的测试替代品。传感器固定在真实世界的躯干上,因此按钮用于表示相对于躯干坐标系而不是场景坐标系应用于虚拟躯干的旋转增量。

如果从“零”旋转开始,所有按钮都能正常工作。但是当我按下3次偏航然后滚动时,我看到滚动旋转在场景轴上工作。但我想把它应用到当前的躯干旋转中。

我已经从这里尝试了几个相关问题的建议,但没有找到解决方案。

旁注:我不确定偏航,俯仰和滚转这两个术语是否通常都与欧拉角有关,所以我想强调一下,根据我的理解,陀螺仪传感器的值不是欧拉角,因为它们代表旋转相对于当前躯干旋转的增量,而不是与躯干起始点“绝对”的累积角度。因此,如果我不恰当地使用这些术语,请尝试理解我的意思。

(背景信息:我有一个机器人项目roboshock.de,陀螺仪传感器连接到机器人躯干,我想要看到机器人在屏幕上的旋转。下面示例中的旋转按钮只是一个测试替换来自传感器的陀螺仪值。)

非常感谢任何帮助。

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class PuppetTestApp extends Application {

    int width = 800;
    int height = 500;
    XGroup torsoGroup;
    double torsoX = 50;
    double torsoY = 80;

    public Parent createRobot() {
        Box torso = new Box(torsoX, torsoY, 20);
        torso.setMaterial(new PhongMaterial(Color.RED));
        Box head = new Box(20, 20, 20);
        head.setMaterial(new PhongMaterial(Color.YELLOW.darker()));
        head.setTranslateY(-torsoY / 2 -10);

        torsoGroup = new XGroup();
        torsoGroup.getChildren().addAll(torso, head);
        return torsoGroup;
    }

    public Parent createUI() {
        HBox buttonBox = new HBox();

        Button b;
        buttonBox.getChildren().add(b = new Button("Exit"));
        b.setOnAction( (ActionEvent arg0) -> { System.exit(0); } );

        buttonBox.getChildren().add(b = new Button("pitch up"));
        b.setOnAction(new TurnAction(torsoGroup.rx, -15) );

        buttonBox.getChildren().add(b = new Button("pitch down"));
        b.setOnAction(new TurnAction(torsoGroup.rx, 15) );

        buttonBox.getChildren().add(b = new Button("Yaw left"));
        b.setOnAction(new TurnAction(torsoGroup.ry, -15) );

        buttonBox.getChildren().add(b = new Button("Yaw right"));
        b.setOnAction(new TurnAction(torsoGroup.ry, 15) );

        buttonBox.getChildren().add(b = new Button("Roll right"));
        b.setOnAction(new TurnAction(torsoGroup.rz, -15) );

        buttonBox.getChildren().add(b = new Button("Roll left"));
        b.setOnAction(new TurnAction(torsoGroup.rz, 15) );

        return buttonBox;
    }

    class TurnAction implements EventHandler<ActionEvent> {
        final Rotate rotate;
        double deltaAngle;

        public TurnAction(Rotate rotate, double targetAngle) {
            this.rotate = rotate;
            this.deltaAngle = targetAngle;
        }

        @Override
        public void handle(ActionEvent arg0) {
            addRotate(torsoGroup, rotate, deltaAngle);
        } 
    }

    private void addRotate(XGroup node, Rotate rotate, double angle) {

        // HERE I DO SOMETHING WRONG

        // not working 1:
        //Transform newRotate = new Rotate(angle, rotate.getAxis());
        //node.getTransforms().add(newRotate);

        // not working 2:
        double x = rotate.getAngle();
        rotate.setAngle(x + angle);
    }

    public class XGroup extends Group {

        public Rotate rx = new Rotate(0, Rotate.X_AXIS);
        public Rotate ry = new Rotate(0, Rotate.Y_AXIS);
        public Rotate rz = new Rotate(0, Rotate.Z_AXIS);

        public XGroup() { 
            super(); 
            getTransforms().addAll(rz, ry, rx); 
        }

        public void setRotate(double x, double y, double z) {
            rx.setAngle(x);
            ry.setAngle(y);
            rz.setAngle(z);
        }

        public void setRotateX(double x) { rx.setAngle(x); }
        public void setRotateY(double y) { ry.setAngle(y); }
        public void setRotateZ(double z) { rz.setAngle(z); }
    }

    @Override 
    public void start(Stage stage) throws Exception {
        Parent robot = createRobot();
        Parent ui = createUI();
        StackPane combined = new StackPane();
        combined.getChildren().addAll(ui, robot);
        combined.setStyle("-fx-background-color: linear-gradient(to bottom, cornsilk, midnightblue);");

        Scene scene = new Scene(combined, width, height);
        stage.setScene(scene);
        stage.show();
     }

    public static void main(String[] args) {
        launch(args);
    }
}

2 个答案:

答案 0 :(得分:5)

对于初学者来说,JavaFX 3D应用程序应该考虑以下几点:

  • 深度缓冲和抗锯齿
  • 子场景
  • 相机

你没有任何这些。

您需要启用深度缓冲,因为您可以看到小黄色框似乎位于大框的顶部(淡黄色的面部根本不可见):

without depth buffer

根据Scene的JavaDoc:

  

包含3D形状或具有3D变换的2D形状的场景可以使用深度缓冲支持进行适当的深度排序渲染

变化:

Scene scene = new Scene(combined, width, height);

到:

Scene scene = new Scene(combined, width, height, true, SceneAntialiasing.BALANCED);

一旦你这样做了,你就会意识到当盒子转到z&gt; 0不再可见,顶部的按钮不再可点击。

在同一场景中混合2D和3D并不是一个好主意。为此,您需要一个SubScene,您可以在其中布置3D内容,并将2D保留在场景中。此外,您可以将深度缓冲区和抗锯齿选项移动到subScene:

Parent robot = createRobot();
// add subScene
SubScene subScene = new SubScene(robot, width, height, true, SceneAntialiasing.BALANCED);
Parent ui = createUI();
StackPane combined = new StackPane();
combined.getChildren().addAll(ui, subScene);
Scene scene = new Scene(combined, width, height);

现在的问题是布局,方框会显示在左上角,而不是中心。

您可以向论坛添加翻译:

public XGroup() { 
    super(); 
    getTransforms().addAll(new Translate(width/2, height/2, 0), rz, ry, rx); 
}    

subscene and depth buffer

现在您可以看到正确的深度排序渲染,并且可以访问ui按钮。

另一个选择是添加相机。您可以删除翻译变换,也可以缩放&#34;看到你的盒子更大:

Parent robot = createRobot();
SubScene subScene = new SubScene(robot, width, height, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.01);
camera.setFarClip(100000);
camera.setTranslateZ(-400);
subScene.setCamera(camera);

Camera

轮作

现在,就旋转而言,如果你想在框的当前状态上应用给定的旋转,而不是与&#34;场景&#34; axis(我的意思是三个正交的非旋转轴),你必须在应用新的旋转之前考虑先前的状态。

在这个关于魔方的blog post中,每个面(由9个小的#34;立方体和#34;组成)可以一次又一次地旋转,受影响的立方体可以进行多次以前的轮换。该项目可以找到here

在这种情况下,使用变换和仿射prepend是在3D主体上始终更新局部正交轴的关键。

我建议:

private void addRotate(XGroup node, Rotate rotate, double angle) {
    Transform newRotate = new Rotate(angle, rotate.getAxis());
    Affine affine = node.getTransforms().isEmpty() ? new Affine() : new Affine(node.getTransforms().get(0));
    affine.prepend(newRotate);
    node.getTransforms().setAll(affine);
}

尽管如此,您的新旋转都是在场景正交轴上定义的。

如果你想要你的本地轴,你可以从仿射矩阵中获取它们。如果您随时打印affine,则可以获得x&#39;,y&#39;,z&#39;第1,2和3列的轴:

axes

Affine [
     0.70710678, 0.50000000,  0.50000000, 0.0
     0.00000000, 0.70710678, -0.70710678, 0.0
    -0.70710678, 0.50000000,  0.50000000, 0.0]

即,x&#39;轴(蓝色)是{0.7071, 0.0, -0.7071}

所以最后你可以在本地轴上定义旋转:

private void addRotate(XGroup node, Rotate rotate, double angle) {

    Affine affine = node.getTransforms().isEmpty() ? new Affine() : new Affine(node.getTransforms().get(0));
    double A11 = affine.getMxx(), A12 = affine.getMxy(), A13 = affine.getMxz(); 
    double A21 = affine.getMyx(), A22 = affine.getMyy(), A23 = affine.getMyz(); 
    double A31 = affine.getMzx(), A32 = affine.getMzy(), A33 = affine.getMzz(); 

    // rotations over local axis  
    Rotate newRotateX = new Rotate(angle, new Point3D(A11, A21, A31));
    Rotate newRotateY = new Rotate(angle, new Point3D(A12, A22, A32));
    Rotate newRotateZ = new Rotate(angle, new Point3D(A13, A23, A33));

    // apply rotation
    affine.prepend(rotate.getAxis() == Rotate.X_AXIS ? newRotateX : 
            rotate.getAxis() == Rotate.Y_AXIS ? newRotateY : newRotateZ);
    node.getTransforms().setAll(affine);
}

我相信这会给你你想要的东西。

这是整个修改后的代码:

private final int width = 800;
private final int height = 500;
private XGroup torsoGroup;
private final double torsoX = 50;
private final double torsoY = 80;

public Parent createRobot() {
    Box torso = new Box(torsoX, torsoY, 20);
    torso.setMaterial(new PhongMaterial(Color.RED));
    Box head = new Box(20, 20, 20);
    head.setMaterial(new PhongMaterial(Color.YELLOW.darker()));
    head.setTranslateY(-torsoY / 2 -10);

    Box x = new Box(200, 2, 2);
    x.setMaterial(new PhongMaterial(Color.BLUE));
    Box y = new Box(2, 200, 2);
    y.setMaterial(new PhongMaterial(Color.BLUEVIOLET));
    Box z = new Box(2, 2, 200);
    z.setMaterial(new PhongMaterial(Color.BURLYWOOD));

    torsoGroup = new XGroup();
    torsoGroup.getChildren().addAll(torso, head, x, y, z);
    return torsoGroup;
}

public Parent createUI() {
    HBox buttonBox = new HBox();

    Button b;
    buttonBox.getChildren().add(b = new Button("Exit"));
    b.setOnAction( (ActionEvent arg0) -> { Platform.exit(); } );

    buttonBox.getChildren().add(b = new Button("pitch up"));
    b.setOnAction(new TurnAction(torsoGroup.rx, -15) );

    buttonBox.getChildren().add(b = new Button("pitch down"));
    b.setOnAction(new TurnAction(torsoGroup.rx, 15) );

    buttonBox.getChildren().add(b = new Button("Yaw left"));
    b.setOnAction(new TurnAction(torsoGroup.ry, -15) );

    buttonBox.getChildren().add(b = new Button("Yaw right"));
    b.setOnAction(new TurnAction(torsoGroup.ry, 15) );

    buttonBox.getChildren().add(b = new Button("Roll right"));
    b.setOnAction(new TurnAction(torsoGroup.rz, -15) );

    buttonBox.getChildren().add(b = new Button("Roll left"));
    b.setOnAction(new TurnAction(torsoGroup.rz, 15) );

    return buttonBox;
}

class TurnAction implements EventHandler<ActionEvent> {
    final Rotate rotate;
    double deltaAngle;

    public TurnAction(Rotate rotate, double targetAngle) {
        this.rotate = rotate;
        this.deltaAngle = targetAngle;
    }

    @Override
    public void handle(ActionEvent arg0) {
        addRotate(torsoGroup, rotate, deltaAngle);
    } 
}

private void addRotate(XGroup node, Rotate rotate, double angle) {
    Affine affine = node.getTransforms().isEmpty() ? new Affine() : new Affine(node.getTransforms().get(0));
    double A11 = affine.getMxx(), A12 = affine.getMxy(), A13 = affine.getMxz(); 
    double A21 = affine.getMyx(), A22 = affine.getMyy(), A23 = affine.getMyz(); 
    double A31 = affine.getMzx(), A32 = affine.getMzy(), A33 = affine.getMzz(); 

    Rotate newRotateX = new Rotate(angle, new Point3D(A11, A21, A31));
    Rotate newRotateY = new Rotate(angle, new Point3D(A12, A22, A32));
    Rotate newRotateZ = new Rotate(angle, new Point3D(A13, A23, A33));

    affine.prepend(rotate.getAxis() == Rotate.X_AXIS ? newRotateX : 
            rotate.getAxis() == Rotate.Y_AXIS ? newRotateY : newRotateZ);

    node.getTransforms().setAll(affine);
}

public class XGroup extends Group {
    public Rotate rx = new Rotate(0, Rotate.X_AXIS);
    public Rotate ry = new Rotate(0, Rotate.Y_AXIS);
    public Rotate rz = new Rotate(0, Rotate.Z_AXIS);
}

@Override 
public void start(Stage stage) throws Exception {
    Parent robot = createRobot();
    SubScene subScene = new SubScene(robot, width, height, true, SceneAntialiasing.BALANCED);
    PerspectiveCamera camera = new PerspectiveCamera(true);
    camera.setNearClip(0.01);
    camera.setFarClip(100000);
    camera.setTranslateZ(-400);
    subScene.setCamera(camera);

    Parent ui = createUI();
    StackPane combined = new StackPane(ui, subScene);
    combined.setStyle("-fx-background-color: linear-gradient(to bottom, cornsilk, midnightblue);");

    Scene scene = new Scene(combined, width, height);
    stage.setScene(scene);
    stage.show();
}

答案 1 :(得分:0)

对于任何感兴趣的人:我已将所有最终代码(包括arduino sketch)放在github上。还有一个youtube显示快速而准确的响应:

https://github.com/tschuett-munich/gyro-to-javafx