javafx-如何相对于节点旋转轴而不是场景旋转轴将偏航,俯仰和滚动增量(不是欧拉)应用于节点? [英] javafx - How to apply yaw, pitch and roll deltas (not euler) to a node in respect to the nodes rotation axes instead of the scene rotation axes?

查看:106
本文介绍了javafx-如何相对于节点旋转轴而不是场景旋转轴将偏航,俯仰和滚动增量(不是欧拉)应用于节点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请耐心回答我一个长的问题,我正在尝试使其尽可能清楚. (在另一个问题中发现).

Please bear with my long question, I am trying to make it as clear as possible. (As found in another question.)

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

In the example below, all rotate buttons are a test replacement for the gyro values coming in from a gyroscope sensor. The sensor is fixed to the real world torso, so the buttons are meant to represent rotation deltas to be applied to the virtual torso in respect to the torsos coordinate system, not the scene coordinate system.

如果从零"旋转开始,则所有按钮本身都可以正常工作.但是当我按3次偏航然后滚动时,我看到滚动旋转在场景轴上起作用.但我想将其应用于当前的躯干旋转.

All buttons work fine by themselfs if starting from a "zero" rotation. But when I press 3 times yaw, and then roll, then I see that the roll rotation works on the scene axes. But I would like to apply it to the current torso rotation instead.

我已经从这里尝试了一些有关相关问题的建议,但是没有找到解决方案.

I have already tried several suggestions to related problems from here, but did not come to a solution.

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

A side note: I am not sure if the terms yaw, pitch and roll are usually bound to euler angles, so I want to underline that to my understanding the values from the gyro sensor are not euler angles, as they represent rotation deltas relative to the current torso rotation, and not accumulated angles "absolute" to the torso starting point. So if I have used these terms inappropriately please try to understand what I have meant anyway.

(背景信息:我有一个机器人项目roboshock.de,其陀螺仪传感器连接到了机器人躯干,并且我想在屏幕上可视化机器人的旋转.以下示例中的旋转按钮仅是一个测试替换来自传感器的陀螺仪值.)

(Background info: I have a robot project roboshock.de with a gyroscope sensor connected to the robots torso, and I want to visualize the rotation of the robot on the screen. The rotate buttons in the below example are only a test replacement for the gyro values coming in from the sensor.)

非常感谢您的帮助.

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);
    }
}

推荐答案

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

For starters, there are a few things that you should consider for a JavaFX 3D application:

  • 深度缓冲和抗锯齿
  • subScene
  • 相机

而您没有任何一个.

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

You need depth buffer enabled, as you can see that the small yellow box seems to be on top of the big box (the light yellow face shouldn't be visible at all):

根据JavaDoc中的Scene:

According to JavaDoc for Scene:

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

A scene containing 3D shapes or 2D shapes with 3D transforms may use depth buffer support for proper depth sorted rendering

更改:

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

收件人:

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

完成此操作后,您将意识到当框转到z> 0时不再可见,并且顶部的按钮不再可单击.

Once you do it, you'll realize that when the box goes to z > 0 is no longer visible, and the buttons on top are not clickable anymore.

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

It is not good idea to mix 2D and 3D in the same scene. For that you need a SubScene, where you can lay out your 3D content, and leave the 2D in the Scene itself. Also, you can move the depth buffer and antialiasing options to the 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);

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

Now the problem is the layout, the boxes will show up in the top left corner, not in the center.

您可以将翻译添加到您的论坛:

You can add a translation to your group:

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

现在您可以看到正确的深度排序渲染,并且可以使用ui按钮.

Now you can see the correct depth sorted rendering, and the ui buttons are accessible.

另一个选项是添加摄像机.您可以删除转换转换,也可以缩放"以查看更大的框:

Another option is adding a camera. You can remove the translate transform, and you can also "zoom" to see your boxes bigger:

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);

旋转

现在,就旋转而言,如果要在框的当前状态上应用给定的旋转,并且与场景"轴无关(我指的是三个正交的非旋转轴),在应用新的轮播之前先考虑以前的状态.

Now, in terms of rotations, if you want to apply a given rotation over the current state of the boxes, and not related to the "scene" axes (I take you mean the three orthogonal non rotated axis), you have to take into account the previous state before applying a new rotation.

在此博客帖子此处找到该项目.

In this blog post about the Rubik's cube, each face (composed of 9 small "cubies") can be rotated again and again and the affected cubies carry on a number of previous rotations. The project can be found here.

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

In this case, using transforms and Affine prepend was the key to have always updated local orthogonal axes on the 3D body.

我建议这样做:

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);
}

尽管如此,所有新旋转都在场景正交轴上定义.

Nonetheless, your new rotations are all defined over the scene orthogonal axes.

如果要改用局部轴,则可以从仿射矩阵中获取它们.如果您随时打印affine,则可以从第1、2和3列获取x',y',z'轴.

If you want your local axes instead, you can get them from the affine matrix. If you print the affine at any time, you can get x', y', z' axes from the columns 1, 2 and 3:

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'轴(蓝色)为{0.7071, 0.0, -0.7071}.

I.e, the x' axis (blue) is {0.7071, 0.0, -0.7071}.

因此,最后您可以将局部轴上的旋转定义为:

So finally you can define the rotations over the local axis as:

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);
}

我相信这会为您提供所需的东西.

I believe this will give you what you were looking for.

这是整个修改后的代码:

This is the whole modified code:

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();
}

这篇关于javafx-如何相对于节点旋转轴而不是场景旋转轴将偏航,俯仰和滚动增量(不是欧拉)应用于节点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆