如何计算JavaFX中透视摄像机视口的x和y界限?方法'camera.getBoundsInParent'可用于确定摄像机的位置,但是,如何使用该位置计算摄像机视口的大小?
根据documentation for PerspectiveCamera
:
默认情况下,此摄像机位于场景的中心,沿正z轴显示。此摄像机定义的坐标系统的原点位于面板的左上角,Y轴朝下,Z轴指向远离观察者(进入屏幕)。
fieldOfView
property将场景的可见部分所对应的角度定义为相机,默认情况下,此角度为measured vertically。
最后,默认情况下,
在Z中调整眼睛位置的Z值,使得使用指定的
fieldOfView
生成的投影矩阵将产生Z = 0(投影平面)的单位,在与设备无关的像素中,与ParallelCamera
的单位匹配
换句话说,Z = 0处的场景的可见部分以像素坐标固定为场景的大小。
因此,假设这些默认值,我们可以绘制以下图表:
这里,左侧的点代表默认的摄像机位置。矩形是z=0
屏幕的可见部分。 w
是场景的宽度,h
是场景的高度(因此相机在某些(w/2, h/2, -z_c)
的z_c > 0
)。
来自相机的线在z = 0处与场景的可见部分的顶部中心相交,在图中以任意z
值扩展到场景的可见部分的顶部中心处的某个点。很容易看出顶部三角形是一个直角三角形,高度为-y
,长度为z
,类似于从相机到z=0
场景中心形成的直角三角形。由于相似的三角形,它具有角度f/2
,其中f
是视场的角度。因此,对于场景可见部分顶部的点(w/2, y, z)
,我们必须有
-y/z = tan(f/2)
要么
y = -z tan(f/2)
您可以在图片的底部构建另一个三角形,并推断出y
坐标满足
y = h + z tan(f/2)
对于宽度,只需注意场景的可见部分的比例总是以w:h
的比例,因此对于场景可见部分左边缘中心的点(x, h/2, z)
,我们有
x = -z (w/h) tan(f/2)
并且在场景的可见部分的右侧
x = w + z (w/h) tan(f/2)
因此,总之,场景的边界延伸
(-z (w/h) tan(f/2), -z tan(f/2))
在左上角,到
(w + z (w/h) tan(f/2), h + z tan(f/2))
在右下角,其中z
是z
坐标,w
是场景的宽度,h
是场景的高度,而f
是视野的(垂直)角度。
这是一个演示。这有四个球体位于场景可见部分的顶部中心,底部中心,左侧中心和右侧中心。所有四个球体的z
坐标都是动画的(因此它们在z
方向远离观察者,看起来变得更小),并且它们的x
-或y
坐标被绑定,以便它们保持在可见的极端部分场景。 (例如,红色球体沿着上图中顶部的直角三角形的斜边动画,其他球体的动画类似。)
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PerspectiveCameraTest extends Application {
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 800, 800, true);
PerspectiveCamera camera = new PerspectiveCamera();
camera.boundsInParentProperty().addListener((obs, oldB, newB) -> System.out.println(newB));
scene.setCamera(camera);
primaryStage.setScene(scene);
primaryStage.show();
Sphere top = new Sphere(40);
top.setMaterial(new PhongMaterial(Color.RED));
top.translateXProperty().bind(pane.widthProperty().divide(2));
top.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
return -top.getTranslateZ() * tanFOver2 ;
}, top.translateZProperty(), pane.heightProperty(), camera.fieldOfViewProperty()));
Sphere bottom = new Sphere(40);
bottom.setMaterial(new PhongMaterial(Color.BLUE));
bottom.translateXProperty().bind(pane.widthProperty().divide(2));
bottom.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
return scene.getHeight() + bottom.getTranslateZ() * tanFOver2 ;
}, bottom.translateZProperty(), pane.heightProperty(), camera.fieldOfViewProperty()));
bottom.translateZProperty().bind(top.translateZProperty());
Sphere left = new Sphere(40);
left.setMaterial(new PhongMaterial(Color.GREEN));
left.translateYProperty().bind(pane.heightProperty().divide(2));
left.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
return -left.getTranslateZ() * tanFOver2 * pane.getWidth() / pane.getHeight();
}, left.translateZProperty(), pane.heightProperty(), pane.widthProperty(), camera.fieldOfViewProperty()));
left.translateZProperty().bind(top.translateZProperty());
Sphere right = new Sphere(40);
right.setMaterial(new PhongMaterial(Color.GOLD));
right.translateYProperty().bind(pane.heightProperty().divide(2));
right.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
return pane.getWidth() + right.getTranslateZ() * tanFOver2 * pane.getWidth() / pane.getHeight() ;
}, right.translateZProperty(), pane.heightProperty(), pane.widthProperty(), camera.fieldOfViewProperty()));
right.translateZProperty().bind(top.translateZProperty());
TranslateTransition anim = new TranslateTransition(Duration.seconds(10), top);
anim.setByZ(5000);
anim.play();
pane.getChildren().addAll(top, bottom, left, right);
}
public static void main(String[] args) {
launch(args);
}
}
如果将相机添加到场景中并移动(并可能旋转)它,那么几何图形会变得相当复杂,尽管基本图像仍然是相同的(只是不方便地与轴对齐)。对于更一般情况的类似计算超出了论坛帖子的范围(并留给读者练习......)。