代码之家  ›  专栏  ›  技术社区  ›  Thomas Schütt

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

  •  4
  • Thomas Schütt  · 技术社区  · 6 年前

    请容忍我的冗长问题,我正在尽可能地把它说清楚。(如另一个问题所示。)

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

    如果从“零”旋转开始,所有按钮都可以自行工作。但当我按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 回复  |  直到 6 年前
        1
  •  5
  •   José Pereda    6 年前

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

    • 深度缓冲区和抗锯齿
    • 亚新世
    • 照相机

    而你没有这些。

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

    without depth buffer

    根据JavaDoc Scene :

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

    更改:

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

    收件人:

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

    一旦你这样做了,你就会意识到当盒子转到z时>0不再可见,顶部的按钮也不再可单击。

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

    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按钮。

    另一个选项是添加相机。您可以删除“平移”变换,也可以“缩放”以查看更大的长方体:

    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

    轮换

    现在,就旋转而言,如果要在长方体的当前状态上应用给定的旋转,并且与“场景”轴无关(我认为您是指三个正交的非旋转轴),则必须在应用新旋转之前考虑之前的状态。

    在这个 blog post 关于魔方,每个面(由9个小“立方体”组成)可以反复旋转,并且受影响的立方体可以进行之前的多次旋转。可以找到该项目 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 在任何时候,您都可以从列1、2和3中获取x、y、z轴:

    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]
    

    一、 e,x轴(蓝色)为 {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();
    }
    
        2
  •  0
  •   Thomas Schütt    6 年前

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

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