Как применить дельта рыскания, тангажа и крена (не эйлера) к узлу относительно осей вращения узлов вместо осей вращения сцены?
Пожалуйста, миритесь с моим длинным вопросом, я пытаюсь сделать его как можно более понятным. (Как найдено в другом вопросе.)
В приведенном ниже примере все кнопки поворота являются тестовой заменой значений гироскопа, поступающих от датчика гироскопа. Датчик прикреплен к реальному торсу, поэтому кнопки предназначены для представления дельт вращения, которые должны применяться к виртуальному торсу по отношению к системе координат туловища, а не к системе координат сцены.
Все кнопки работают сами по себе, если начинать с "нулевого" вращения. Но когда я три раза нажимаю рыскание, а затем поворачиваюсь, я вижу, что вращение крена работает на осях сцены. Но я бы хотел применить его к текущему вращению туловища.
Я уже попробовал несколько предложений для связанных проблем отсюда, но не пришел к решению.
Примечание: я не уверен, что термины рыскание, тангаж и крен обычно связаны с углами Эйлера, поэтому я хочу подчеркнуть, что, насколько я понимаю, значения от гироскопа не являются углами Эйлера, так как они представляют дельты вращения относительно текущее вращение туловища, а не накопленные углы "абсолютной" точки отсчета туловища. Поэтому, если я использовал эти термины ненадлежащим образом, постарайтесь понять, что я имел в виду.
(Справочная информация: у меня есть проект робота 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 ответа
Для начала, есть несколько вещей, которые вы должны рассмотреть для приложения JavaFX 3D:
- буфер глубины и сглаживание
- subScene
- камера
и у вас нет ни одного из них.
Вам необходимо включить буфер глубины, так как вы можете видеть, что маленький желтый прямоугольник находится сверху большого прямоугольника (светло-желтое лицо вообще не должно быть видно):
Согласно JavaDoc для Scene
:
Сцена, содержащая 3D-фигуры или 2D-фигуры с 3D-преобразованиями, может использовать поддержку буфера глубины для правильной сортировки по глубине.
Изменить:
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);
}
Теперь вы можете видеть правильную сортировку по глубине и кнопки пользовательского интерфейса доступны.
Другой вариант - добавить камеру. Вы можете удалить трансляционное преобразование, а также "увеличить", чтобы увеличить ваши поля:
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);
Повороты
Теперь, с точки зрения поворотов, если вы хотите применить заданное вращение к текущему состоянию блоков, а не к осям "сцены" (я имею в виду три ортогональных не повернутых оси), вы должны принять во внимание учтите предыдущее состояние перед применением нового вращения.
В этом сообщении в блоге о кубике Рубика каждое лицо (состоящее из 9 маленьких "кубиков") можно вращать снова и снова, и затронутые кубики выполняют ряд предыдущих вращений. Проект можно найти здесь.
В этом случае с помощью преобразований и Affine 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', y', z'из столбцов 1, 2 и 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}
,
Наконец, вы можете определить повороты по локальной оси как:
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();
}
Для всех, кто интересуется: я поместил весь окончательный код (включая набросок arduino) на github. Существует также YouTube, показывающий быстрый и точный ответ: