java - JavaFX 3D透视相机运动
问题描述
所以现在,感谢我之前的问题的帮助,以及用户“trashgod”(在我的例子中更恰当地发音为“javafxgod”)的非常有用的回答,我已经能够在我尝试的实际模型上取得很大进展使用 JavaFX 构建,并且对它的功能更加满意......然而,尽管现在能够大致移动我的模型,但我仍然对相机的行为完全感到困惑,尽管提供的代码很有帮助工作,我还无法修改导航以按我想要的方式工作。
真的很遗憾,经过这么多年的 JavaFX,在我看来这是一个非常令人印象深刻的基础(它们混合支持 3D 和 2D 的整个方式特别酷),仍然没有基本的实用程序和库像标准轨道导航(例如,像这样,这大致是我在相机导航方面试图实现的,用threejs和它的OrbitNavigation
类完成)。
Group
我认为我至少已经很好地组织了它(很好地捆绑在一个JFX 需要,而且确实很好的例子(并非都是混淆的)似乎并不容易找到。
我认为我最困惑的一点(尽管进行了大量的研究和对Node
文档的非常透彻的阅读)是何时使用本地坐标与父坐标。例如,在我之前的问题中,提供的代码似乎使用父坐标,当我使用setTranslateX(getTranslateX() + 10)
如图所示的或类似的东西时,这些似乎被解释为父坐标,因为当我旋转时,我看到自己沿着轴移动标记。
但是,当我尝试将这些父坐标转换为本地坐标时,我会得到我不理解的行为,并且通常会完全丢失我的模型。请哦,请有人解释一下这里到底发生了什么,评论中显示了几种不同的尝试和问题:
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.input.*;
import javafx.scene.transform.Rotate;
public class NavigableCamera extends Group {
private final Camera camera;
private final Rotate xRotate = new Rotate(0, Rotate.X_AXIS);
private final Rotate yRotate = new Rotate(0, Rotate.Y_AXIS);
private final Rotate zRotate = new Rotate(0, Rotate.Z_AXIS);
private double x, y, z, angleX, angleY;
private Node pickedNode;
public NavigableCamera(Scene scene) {
camera = new PerspectiveCamera(true);
camera.setFarClip(6000);
camera.setNearClip(0.01);
getChildren().add(camera);
//okay, sure this works and the camera ends up pointing at the origin, but "level" with it.
setTranslateZ(-500);
//but why oh why doesn't this work to end with the camera looking "down" at the origin from above?
// setTranslateY(-500);
// Point3D pivot = Point3D.ZERO;
// Rotate pointDown = new Rotate(90, pivot.getX(), pivot.getY(), pivot.getZ(), Rotate.X_AXIS);
// getTransforms().add(pointDown);
getTransforms().addAll(xRotate, yRotate, zRotate);
initKeyboardControl(scene);
initMouseControl(scene);
initTouchControls(scene);
scene.setCamera(camera);
}
private void initMouseControl(Scene scene) {
scene.setOnMousePressed(event -> {
x = event.getSceneX();
y = event.getSceneY();
angleX = xRotate.getAngle();
angleY = yRotate.getAngle();
PickResult pickResult = event.getPickResult();
pickedNode = pickResult.getIntersectedNode();
double x, y, z;
if(pickedNode != null) {
x = parentToLocal(pickedNode.getBoundsInParent()).getCenterX();
y = parentToLocal(pickedNode.getBoundsInParent()).getCenterY();
z = parentToLocal(pickedNode.getBoundsInParent()).getCenterZ();
} else {
Point3D pivotPoint = pickResult.getIntersectedPoint();
x = pivotPoint.getX();
y = pivotPoint.getY();
z = pivotPoint.getZ();
}
xRotate.setPivotX(x);
xRotate.setPivotY(y);
xRotate.setPivotZ(z);
yRotate.setPivotX(x);
yRotate.setPivotY(y);
yRotate.setPivotZ(z);
});
scene.setOnMouseDragged(event -> {
xRotate.setAngle(angleX - (x - event.getSceneY()));
yRotate.setAngle(angleY + x - event.getSceneX());
});
scene.setOnMouseDragReleased(event -> {
xRotate.setPivotX(0);
xRotate.setPivotY(0);
xRotate.setPivotZ(0);
});
scene.setOnScroll(event -> {
xRotate.setAngle(xRotate.getAngle() + event.getDeltaY() / 10);
yRotate.setAngle(yRotate.getAngle() - event.getDeltaX() / 10);
setTranslateX(getTranslateX() + event.getDeltaX());
setTranslateY(getTranslateY() + event.getDeltaY());
});
}
private void initKeyboardControl(Scene scene) {
scene.setOnKeyPressed(event -> {
KeyCode code = event.getCode();
switch (code) {
case SPACE:
moveLocalZ(+10);
break;
case BACK_SPACE:
moveLocalZ(-10);
break;
case RIGHT:
// this commented out setTranslate, as well as all the others seem to operate on a "PARENT" coordinate system, but when I try to get local coordinates instead, it doesn't work!
// setTranslateX(getTranslateX() + 20);
moveLocalX(+10);
break;
case LEFT:
// setTranslateX(getTranslateX() - 20);
moveLocalX(-10);
break;
case UP:
moveLocalY(-10);
break;
case DOWN:
moveLocalY(+10);
break;
lateZ() - 30);
break;
}
});
}
private void moveLocalX(double amount) {
Point3D p = localToParent(getTranslateX() + amount, getTranslateY(), getTranslateZ());
moveToParentPoint(p);
}
private void moveLocalY(double amount) {
Point3D p = localToParent(getTranslateX(), getTranslateY() + amount, getTranslateZ());
moveToParentPoint(p);
}
private void moveLocalZ(double amount) {
Point3D p = localToParent(getTranslateX(), getTranslateY(), getTranslateZ() + amount);
moveToParentPoint(p);
}
private void moveToParentPoint(Point3D p) {
setTranslateX(p.getX());
setTranslateY(p.getY());
setTranslateZ(p.getZ());
}
private void initTouchControls(Scene scene) {
// on an entirely side note, I really don't get why this can't just be here, but it can be a member of the class?
// double z;
scene.setOnZoomStarted(event -> {
z = event.getZ();
});
// frankly I can't even visualize what a "scale" means in the context of a "camera"
// what I want for "zoom" is to move the camera "forwards" or "backwards" as in towards the
// direction it's facing and away from it, but when I have tried this, I end up moving just
// along the Z axis, wherever I'm facing!
scene.setOnZoom(event -> {
if (z > 0) {
setScaleZ(getScaleZ() + event.getZoomFactor());
} else {
setScaleZ(getScaleZ() - event.getZoomFactor());
}
});
}
}
我对这个可爱的AxesMarker
小类很满意,起初我试图用 3D 块做(基于一些在线示例),但是这种 2D 方法更干净,并且是 JavaFX 强大功能的一个很好的例子:
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
public class AxesMarker extends Group {
Line x, y, z;
public AxesMarker(double length) {
x = new Line(0, 0, length, 0);
y = new Line(0, 0, 0, length);
z = new Line(0, 0, length, 0);
z.getTransforms().add(new Rotate(90, 0, 0, 0, Rotate.Y_AXIS));
x.setStroke(Color.RED);
y.setStroke(Color.GREEN);
z.setStroke(Color.BLUE);
getChildren().addAll(x, y, z);
}
}
在这里足以Application
演示以下内容的用法NavigableCamera
:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CameraTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
root.getChildren().add(new AxesMarker(200));
Scene scene = new Scene(root, 800, 800, Color.BLACK);
NavigableCamera camera = new NavigableCamera(scene);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
解决方案
要获得方向感,请从这个例子开始。注意x、y和z轴分别是红色、绿色和蓝色;如此处所示,正x指向右侧,正y指向下方,正z指向屏幕。相机已沿z轴平移到 -2000,因此可以看到蓝色的z轴。
现在,围绕x轴旋转相机 -90° 并将其沿y轴平移到 -2000,因此可以直接向下看绿色y轴。
private final Rotate xRotate = new Rotate(-90, Rotate.X_AXIS);
camera.setTranslateY(-2000);
最后,相应地调整键盘和鼠标处理程序。例如,使用箭头键沿y轴移动轴组。
case UP:
g.setTranslateY(g.getTranslateY() - 100);
break;
case DOWN:
g.setTranslateY(g.getTranslateY() + 100);
break;
case HOME:
g.xRotate.setAngle(-90);
另见Rotating perspective camera around an object in javaFX,它说明了围绕枢轴旋转相机;与此不同的是,它既将相机设置在场景中,又将相机添加到相机旋转的组中。
由于您的目标是为一个 orrery 建模,因此请考虑PathTransition
一个合适Shape
的,如图所示;只要您将黄道构造为与xy平面重合,就可以像往常一样旋转该组。
经测试:
import javafx.application.Application;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
/**
* @see https://stackoverflow.com/a/69339586/230513
* @see https://stackoverflow.com/a/69260181/230513
*/
public class RotateCameraExample extends Application {
private static class RotateCamera extends Group {
private final Camera camera;
private final Rotate xRotate = new Rotate(-90, Rotate.X_AXIS);
private final Rotate yRotate = new Rotate(0, Rotate.Y_AXIS);
private final Rotate zRotate = new Rotate(0, Rotate.Z_AXIS);
public RotateCamera() {
buildAxes();
camera = new PerspectiveCamera(true);
camera.setFarClip(6000);
camera.setNearClip(0.01);
camera.setTranslateY(-2000);
camera.getTransforms().addAll(xRotate, yRotate, zRotate);
}
private void buildAxes() {
final Box xAxis = new Box(1200, 10, 10);
final Box yAxis = new Box(10, 1200, 10);
final Box zAxis = new Box(10, 10, 1200);
xAxis.setMaterial(new PhongMaterial(Color.RED));
yAxis.setMaterial(new PhongMaterial(Color.GREEN));
zAxis.setMaterial(new PhongMaterial(Color.BLUE));
Group axisGroup = new Group();
axisGroup.getChildren().addAll(xAxis, yAxis, zAxis);
this.getChildren().add(axisGroup);
}
}
@Override
public void start(Stage stage) {
RotateCamera g = new RotateCamera();
Scene scene = new Scene(g, 800, 800, Color.BLACK);
scene.setCamera(g.camera);
stage.setScene(scene);
stage.show();
scene.setOnScroll((final ScrollEvent e) -> {
g.xRotate.setAngle(g.xRotate.getAngle() + e.getDeltaY() / 10);
g.yRotate.setAngle(g.yRotate.getAngle() - e.getDeltaX() / 10);
g.setTranslateX(g.getTranslateX() + e.getDeltaX());
g.setTranslateY(g.getTranslateY() + e.getDeltaY());
});
scene.setOnKeyPressed((KeyEvent e) -> {
System.out.println(g.getTranslateY());
KeyCode code = e.getCode();
switch (code) {
case LEFT:
g.zRotate.setAngle(g.zRotate.getAngle() + 10);
break;
case RIGHT:
g.zRotate.setAngle(g.zRotate.getAngle() - 10);
break;
case UP:
g.setTranslateY(g.getTranslateY() - 100);
break;
case DOWN:
g.setTranslateY(g.getTranslateY() + 100);
break;
case HOME:
g.xRotate.setAngle(-90);
g.yRotate.setAngle(0);
g.zRotate.setAngle(0);
g.setTranslateX(0);
g.setTranslateY(0);
g.setTranslateZ(0);
break;
default:
break;
}
});
}
public static void main(String[] args) {
launch();
}
}
推荐阅读
- java - 如何比较不同类型的列表
- regex - 需要正则表达式来编辑有效的电子邮件地址,但不是以某些单词开头的地址,不能使用环视
- react-native - 下载后React Native将文件路径转换为URI
- postgresql - 获得每个评分和平均值的总票数。Postgres
- amazon-web-services - AWS Cert Mgr - 如何创建根 CA 证书?
- html - 如何将列出页数的分页器下拉菜单的字体更改为更大?
- python - 第三方 API 调用后 Django 无法再找到 base.html
- sql - 根据 TypeID 创建列
- java - Java Producer Consumer - 为什么不是简单的 FixedThreadPool?
- dynamics-crm - 当用户登录时,我可以在 CRM 中自动创建 Power Geo Logging 配置实体(powergeologgingconfiguration)吗?