[英]JavaFX 3D perspective camera movement
所以现在,多亏了我之前的问题的帮助,以及用户“trashgod”(在我的例子中更恰当地发音为“javafxgod”)的非常有用的回答,我已经能够在我尝试的实际 model 上取得很大进展使用 JavaFX 进行构建,并对其 function 感到更加自在......然而,尽管现在能够大致移动我的 model,但我仍然对相机的行为感到非常困惑,尽管提供的代码很有帮助有效,我还无法修改导航以按我想要的方式工作。
令人遗憾的是,经过这么多年的 JavaFX,在我看来这是一个非常令人印象深刻的基础(他们混合支持 3D 的整个方式,尤其是 2D 看起来很酷),仍然没有基本的实用程序和库像标准轨道导航(例如,像这样,这大致是我在相机导航方面试图实现的目标,用 threejs 完成,它是OrbitNavigation
类)。
我至少很好地组织了它(很好地捆绑到一个Group
中),我想,所以当我最终把它做得很好和抛光时,其他人至少可以从中受益,因为这实际上是每个做任何 3D 建模的人的东西在 JFX 需求中,实际上,好的示例(并非一团糟)似乎并不容易找到。
我认为我最困惑的一点(尽管进行了大量研究并且非常透彻地阅读了Node
文档)是何时使用局部坐标与父坐标。 例如,在我之前的问题中,提供的代码似乎使用了父坐标,当我使用setTranslateX(getTranslateX() + 10)
或如图所示的类似方法时,它们似乎被解释为父坐标,因为当我旋转 我看到自己沿着轴标记移动。
但是,当我尝试将这些父坐标转换为本地坐标时,我会遇到无法理解的行为,并且通常会完全丢失我的 model。 请哦,请有人解释这里到底发生了什么,评论中显示了几种不同的尝试和问题:
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
class 相当满意,起初我试图用 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);
另请参阅javaFX 中的围绕 object 旋转透视相机,它说明了围绕pivot旋转相机; 与此不同的是,它既将相机设置在场景上,又将相机添加到相机旋转的组中。
由于您的目标是 model 一个太阳系仪,请考虑沿着合适Shape
的PathTransition
,如此处所示; 只要您构建的黄道与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();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.