在 3D 中丢失 space - 来自旋转矩阵(javafx 仿射)的倾斜值(欧拉?)仅部分起作用
lost in 3D space - tilt values (euler?) from rotation matrix (javafx affine) only works partially
不久前我问了这个问题:
今天我想问一下,如何从旋转矩阵中得到相对于body(不是房间)的倾斜度(fore-back和侧向)。为了使问题易于理解,我从 José Pereda 的精彩回答中获取了最终代码,并基本上添加了一个方法“getEulersFromRotationMatrix”。这有点管用,但有时会出问题。
附件是完整的工作示例。通过以下点击路径问题变得清晰:
// right after start
tilt fore
tilt left // all right
tilt right
tilt back // all right
// right after start
turn right
turn right
turn right
tilt fore
tilt back // all right
tilt left // bang, tilt values are completely off
虽然按钮按预期移动躯干,但倾斜值(打印在按钮下方)在某些时候会出现错误。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class PuppetTestApp extends Application {
private final int width = 800;
private final int height = 500;
private XGroup torsoGroup;
private final double torsoX = 50;
private final double torsoY = 80;
private Label output = new Label();
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("tilt fore"));
b.setOnAction(new TurnAction(torsoGroup.rx, 15) );
buttonBox.getChildren().add(b = new Button("tilt back"));
b.setOnAction(new TurnAction(torsoGroup.rx, -15) );
buttonBox.getChildren().add(b = new Button("tilt left"));
b.setOnAction(new TurnAction(torsoGroup.rz, 15) );
buttonBox.getChildren().add(b = new Button("tilt right"));
b.setOnAction(new TurnAction(torsoGroup.rz, -15) );
buttonBox.getChildren().add(b = new Button("turn left"));
b.setOnAction(new TurnAction(torsoGroup.ry, -28) ); // not 30 degree to avoid any gymbal lock problems
buttonBox.getChildren().add(b = new Button("turn right"));
b.setOnAction(new TurnAction(torsoGroup.ry, 28) ); // not 30 degree to avoid any gymbal lock problems
VBox vbox = new VBox();
vbox.getChildren().add(buttonBox);
vbox.getChildren().add(output);
return vbox;
}
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);
EulerValues euler= getEulersFromRotationMatrix(affine);
output.setText(String.format("tilt fore/back=%3.0f tilt sideways=%3.0f", euler.forward, euler.leftSide));
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();
}
/**
* Shall return the tilt values relative to the body (not relative to the room)
* (Maybe euler angles are not the right term here, but anyway)
*/
private EulerValues getEulersFromRotationMatrix(Affine rot) {
double eulerX; // turn left/right
double eulerY; // tilt fore/back
double eulerZ; // tilt sideways
double r11 = rot.getMxx();
double r12 = rot.getMxy();
double r13 = rot.getMxz();
double r21 = rot.getMyx();
double r31 = rot.getMzx();
double r32 = rot.getMzy();
double r33 = rot.getMzz();
// used instructions from https://www.gregslabaugh.net/publications/euler.pdf
if (r31 != 1.0 && r31 != -1.0) {
eulerX = -Math.asin(r31); // already tried with the 2nd solution as well
double cosX = Math.cos(eulerX);
eulerY = Math.atan2(r32/cosX, r33/cosX);
eulerZ = Math.atan2(r21/cosX, r11/cosX);
}
else {
eulerZ = 0;
if (r31 == -1) {
eulerX = Math.PI / 2;
eulerY = Math.atan2(r12, r13);
}
else {
eulerX = -Math.PI / 2;
eulerY = Math.atan2(-r12, -r13);
}
}
return new EulerValues(
eulerY / Math.PI * 180.0,
eulerZ / Math.PI * 180.0,
-eulerX / Math.PI * 180.0);
}
public class EulerValues {
public double leftTurn;
public double forward;
public double leftSide;
public EulerValues(double forward, double leftSide, double leftTurn) {
this.forward = forward;
this.leftSide = leftSide;
this.leftTurn = leftTurn;
}
}
public static void main(String[] args) {
launch(args);
}
}
PS:这可能看起来我几乎没有任何进展,但这只是因为我试图将问题减少到可能的最低限度。如果你想看看这些东西是如何嵌入到我的主要项目中的,你可以观看我刚刚上传的这个小视频(但不添加任何问题):https://www.youtube.com/watch?v=R3t8BIHeo7k
我想我现在自己明白了:我计算的是“默认”欧拉角,有时称为 z x' z'',其中第 1 和第 3 旋转围绕同一轴。但我正在寻找的是可以应用于 z、y' 和 x'' 轴(按此顺序)以达到旋转矩阵所呈现的位置的角度。 (然后忽略 z 旋转)。
或者更好地计算 z y' x'' 欧拉和 z x' y'' 欧拉和
仅使用 x' 和 y' 值。
添加:
不,那是错误的。我确实计算了 Tait-Bryan x y z 旋转。所以这不是解决方案。
好的,新的解释:
我计算的旋转轴是房间相对旋转(不是物体相对旋转),第二个旋转是在垂直轴上(我对此不感兴趣)。但因为它是“在中间”,所以它可以抵消第 1 和第 3 个旋转,结果就是这样。
所以解决方案应该是改变旋转顺序,这来自我的矩阵到欧拉算法。但如何做到这一点?
我刚刚交换了所有“y”和“z”:
r11 = rot.getMxx();
r12 = rot.getMxz();
r13 = rot.getMxy();
r21 = rot.getMzx();
r31 = rot.getMyx();
r32 = rot.getMyz();
r33 = rot.getMyy();
现在它真的如我所愿。 :)
不久前我问了这个问题:
今天我想问一下,如何从旋转矩阵中得到相对于body(不是房间)的倾斜度(fore-back和侧向)。为了使问题易于理解,我从 José Pereda 的精彩回答中获取了最终代码,并基本上添加了一个方法“getEulersFromRotationMatrix”。这有点管用,但有时会出问题。
附件是完整的工作示例。通过以下点击路径问题变得清晰:
// right after start
tilt fore
tilt left // all right
tilt right
tilt back // all right
// right after start
turn right
turn right
turn right
tilt fore
tilt back // all right
tilt left // bang, tilt values are completely off
虽然按钮按预期移动躯干,但倾斜值(打印在按钮下方)在某些时候会出现错误。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class PuppetTestApp extends Application {
private final int width = 800;
private final int height = 500;
private XGroup torsoGroup;
private final double torsoX = 50;
private final double torsoY = 80;
private Label output = new Label();
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("tilt fore"));
b.setOnAction(new TurnAction(torsoGroup.rx, 15) );
buttonBox.getChildren().add(b = new Button("tilt back"));
b.setOnAction(new TurnAction(torsoGroup.rx, -15) );
buttonBox.getChildren().add(b = new Button("tilt left"));
b.setOnAction(new TurnAction(torsoGroup.rz, 15) );
buttonBox.getChildren().add(b = new Button("tilt right"));
b.setOnAction(new TurnAction(torsoGroup.rz, -15) );
buttonBox.getChildren().add(b = new Button("turn left"));
b.setOnAction(new TurnAction(torsoGroup.ry, -28) ); // not 30 degree to avoid any gymbal lock problems
buttonBox.getChildren().add(b = new Button("turn right"));
b.setOnAction(new TurnAction(torsoGroup.ry, 28) ); // not 30 degree to avoid any gymbal lock problems
VBox vbox = new VBox();
vbox.getChildren().add(buttonBox);
vbox.getChildren().add(output);
return vbox;
}
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);
EulerValues euler= getEulersFromRotationMatrix(affine);
output.setText(String.format("tilt fore/back=%3.0f tilt sideways=%3.0f", euler.forward, euler.leftSide));
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();
}
/**
* Shall return the tilt values relative to the body (not relative to the room)
* (Maybe euler angles are not the right term here, but anyway)
*/
private EulerValues getEulersFromRotationMatrix(Affine rot) {
double eulerX; // turn left/right
double eulerY; // tilt fore/back
double eulerZ; // tilt sideways
double r11 = rot.getMxx();
double r12 = rot.getMxy();
double r13 = rot.getMxz();
double r21 = rot.getMyx();
double r31 = rot.getMzx();
double r32 = rot.getMzy();
double r33 = rot.getMzz();
// used instructions from https://www.gregslabaugh.net/publications/euler.pdf
if (r31 != 1.0 && r31 != -1.0) {
eulerX = -Math.asin(r31); // already tried with the 2nd solution as well
double cosX = Math.cos(eulerX);
eulerY = Math.atan2(r32/cosX, r33/cosX);
eulerZ = Math.atan2(r21/cosX, r11/cosX);
}
else {
eulerZ = 0;
if (r31 == -1) {
eulerX = Math.PI / 2;
eulerY = Math.atan2(r12, r13);
}
else {
eulerX = -Math.PI / 2;
eulerY = Math.atan2(-r12, -r13);
}
}
return new EulerValues(
eulerY / Math.PI * 180.0,
eulerZ / Math.PI * 180.0,
-eulerX / Math.PI * 180.0);
}
public class EulerValues {
public double leftTurn;
public double forward;
public double leftSide;
public EulerValues(double forward, double leftSide, double leftTurn) {
this.forward = forward;
this.leftSide = leftSide;
this.leftTurn = leftTurn;
}
}
public static void main(String[] args) {
launch(args);
}
}
PS:这可能看起来我几乎没有任何进展,但这只是因为我试图将问题减少到可能的最低限度。如果你想看看这些东西是如何嵌入到我的主要项目中的,你可以观看我刚刚上传的这个小视频(但不添加任何问题):https://www.youtube.com/watch?v=R3t8BIHeo7k
我想我现在自己明白了:我计算的是“默认”欧拉角,有时称为 z x' z'',其中第 1 和第 3 旋转围绕同一轴。但我正在寻找的是可以应用于 z、y' 和 x'' 轴(按此顺序)以达到旋转矩阵所呈现的位置的角度。 (然后忽略 z 旋转)。
或者更好地计算 z y' x'' 欧拉和 z x' y'' 欧拉和 仅使用 x' 和 y' 值。
添加: 不,那是错误的。我确实计算了 Tait-Bryan x y z 旋转。所以这不是解决方案。
好的,新的解释:
我计算的旋转轴是房间相对旋转(不是物体相对旋转),第二个旋转是在垂直轴上(我对此不感兴趣)。但因为它是“在中间”,所以它可以抵消第 1 和第 3 个旋转,结果就是这样。
所以解决方案应该是改变旋转顺序,这来自我的矩阵到欧拉算法。但如何做到这一点?
我刚刚交换了所有“y”和“z”:
r11 = rot.getMxx();
r12 = rot.getMxz();
r13 = rot.getMxy();
r21 = rot.getMzx();
r31 = rot.getMyx();
r32 = rot.getMyz();
r33 = rot.getMyy();
现在它真的如我所愿。 :)