现在,感谢我的帮助先前的问题 https://stackoverflow.com/questions/69249037/most-simple-rotate-camera-via-mouse-not-working,以及来自用户“trashgod”(在我的例子中更合适地发音为“javafxgod”)的非常有用的答案,我已经能够在我尝试使用 JavaFX 构建的实际模型上取得很大进展,并且获得了更多对其功能感到满意...但是,尽管现在能够在我的模型上大致移动,但我仍然对相机的行为感到完全困惑,尽管提供的代码有效,但我还无法修改导航以我想要的方式工作。
令人遗憾的是,JavaFX 经过这么多年的发展,在我看来已经有了令人印象深刻的基础(它们混合支持 3D 和 2D 的整个方式尤其看起来很酷),但仍然没有基本的实用程序和库像标准轨道导航(例如,像这样 https://codepen.io/pholmq/full/XGPrPd,这大致就是我在相机导航方面试图实现的目标,用 Threejs 完成,它是OrbitNavigation
class).
我至少已经很好地组织了它(很好地捆绑到一个Group
),我认为,这样,当我最终将其完美地完善和完善时,其他人至少可以从中受益,因为这实际上是每个在 JFX 中进行 3D 建模的人都需要的东西,而且确实是很好的例子(这些都不是)一切都混乱了)似乎不太容易找到。
我认为我最困惑的一点(尽管进行了大量研究并非常彻底地阅读了Node
文档)是使用本地坐标与父坐标的情况。例如,在我之前的问题中,提供的代码似乎使用父坐标,当我使用setTranslateX(getTranslateX() + 10)
或如图所示的类似内容,这些似乎被解释为parent坐标,因为当我旋转时,我看到自己沿着轴标记移动。
但是,当我尝试将这些父坐标转换为本地坐标时,我会得到我不理解的行为,并且通常会完全丢失我的模型。请哦请有人解释一下这里到底发生了什么,几次不同的尝试和评论中显示的问题:
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();
}
}