javafx 3D Box,显示的翻译不是预期的
javafx 3D Box, translation displayed is not what is expected
我目前正在处理一个项目,我应该在窗格上显示 3D 框,为此我正在使用 javafx 3D。首先我画了一个大盒子,在我的项目中叫做 container 并创建了一个相机。然后将按钮添加到场景中,我在大容器中绘制其他较小的盒子。然而,由于某种原因,小盒子会相互影响并改变它们的坐标。更多图片说明:
这是box1,我用键"X"画的:
这是box2,我用键"C"画的:
如果我只添加 box1 并重新启动我的应用程序,然后绘制 box2,则相应地输出图像 1 和图像 2。但是,如果我不是每次都重新启动应用程序并且我想绘制另一个框,我会得到以下输出。
box2 的输出,如果在它之前绘制了 box1:
box1 的输出,如果在它之前绘制了 box2:
如果我先画box1,box1看起来没问题,但是图3是box2的输出。如果我先绘制 box2,box1 输出将变为图像 4。
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
public class ContainerPane extends Parent {
//size of big container
private final double CONTAINER_DEPTH = 16.5;
private final double CONTAINER_WIDTH = 2.5;
private final double CONTAINER_HEIGHT = 4.0;
//group in which the box, container and camera are added
private Group root;
//coordiantes of box 1, row one is the actual coordinates and row two is the width, height, depth
private double[][] box1 = {{0, 4, 30},
{1, 2, 1.5}};
//coordiantes of box 2, row one is the actual coordinates and row two is the width, height, depth
private double[][] box2 = {{0, 6, 28},
{1.5, 1, 2}};
public ContainerPane(int Scene_Width, int Scene_Length){
//create the group
root = new Group();
root.setAutoSizeChildren(false);
//creating container
Box container = new Box(CONTAINER_WIDTH , CONTAINER_HEIGHT, CONTAINER_DEPTH);
container.setCullFace(CullFace.NONE);
//drawing the container with only lines
container.setDrawMode(DrawMode.LINE);
//setting the color of the container
PhongMaterial material = new PhongMaterial(Color.ORANGE);
container.setMaterial(material);
root.getChildren().add(container);
//create a camera
PerspectiveCamera camera = new PerspectiveCamera(true);
//add possible rotations and position of camera
camera.getTransforms().addAll(new Translate(0, 0, -35));
root.getChildren().add(camera);
//create a Scene from the group
SubScene subScene = new SubScene(root, Scene_Width, Scene_Length, true, SceneAntialiasing.BALANCED);
//set a camera for the scene
subScene.setCamera(camera);
getChildren().add(subScene);
}
public void drawBox1(){
//clear everything from root except container and camera
try{
root.getChildren().remove(2);
}
catch(Exception exception){
}
//create box1
Box box = new Box(box1[1][0], box1[1][1], box1[1][2]);
box.setDrawMode(DrawMode.FILL);
box.setMaterial(new PhongMaterial(Color.BLUE));
box.setTranslateX(-CONTAINER_WIDTH/2 + box.getWidth()/2 + 0.5*box1[0][0]);
box.setTranslateY(-CONTAINER_HEIGHT/2 + box.getHeight()/2 + 0.5*box1[0][1]);
box.setTranslateZ(CONTAINER_DEPTH/2 - box.getDepth()/2 - 0.5*box1[0][2]);
//add it to the group
root.getChildren().add(box);
}
public void drawBox2(){
try{
root.getChildren().remove(2);
}
catch(Exception exception){
}
Box box = new Box(box2[1][0], box2[1][1], box2[1][2]);
box.setDrawMode(DrawMode.FILL);
box.setMaterial(new PhongMaterial(Color.BLUE));
box.setTranslateX(-CONTAINER_WIDTH/2 + box.getWidth()/2 + 0.5*box2[0][0]);
box.setTranslateY(-CONTAINER_HEIGHT/2 + box.getHeight()/2 + 0.5*box2[0][1]);
box.setTranslateZ(CONTAINER_DEPTH/2 - box.getDepth()/2 - 0.5*box2[0][2]);
root.getChildren().add(box);
}
}
主文件,我在其中创建 class.
的实例
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Error extends Application {
@Override
public void start(Stage primaryStage) {
ContainerPane container = new ContainerPane(750, 750);
Scene scene = new Scene(container);
scene.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>(){
@Override
public void handle(KeyEvent event){
if(event.getCode() == KeyCode.X){
container.drawBox1();
}
if(event.getCode() == KeyCode.C){
container.drawBox2();
}
}});
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
总结提出的问题:无论将它们添加到场景的顺序如何,绘制两个相似的 3D 框都应该有效,但事实是顺序 很重要 和结果是错误.
(为方便起见,我将框 2 设置为红色)
当先绘制较高的蓝色框 1 时,较短的红色框与较高的框尺寸完全相同。
当先绘制较短的红色框 2 时,会绘制较高的蓝色框,其尺寸与较短的红色框完全相同。
说明
对此行为有一个解释:当您将长方体、圆柱体或球体绘制到 scene/subscene 时,网格管理器中有一个内部缓存 javafx.scene.shape.PredefinedMeshManager
class:
private HashMap<Integer, TriangleMesh> boxCache = null;
第一次添加网格时:
if (key == 0) {
key = generateKey(w, h, d);
}
mesh = manager.getBoxMesh(w, h, d, key);
密钥为空,管理员为您创建盒子:
if (mesh == null) {
mesh = Box.createMesh(w, h, d);
boxCache.put(key, mesh);
}
并存储此网格的密钥。在 Box
的情况下,这是生成此密钥的方式:
private static int generateKey(float w, float h, float d) {
int hash = 3;
hash = 97 * hash + Float.floatToIntBits(w);
hash = 97 * hash + Float.floatToIntBits(h);
hash = 97 * hash + Float.floatToIntBits(d);
return hash;
}
现在,对于第二个网格,您生成密钥,然后去向经理询问现有网格:
TriangleMesh mesh = boxCache.get(key);
鉴于 box1
和 box2
不同:
Box1: {W: 1, H: 2, D: 1.5}
Box2: {W: 1.5, H: 1, D: 2}
任何人都认为缓存会 return 为空并生成一个新的网格...
...但这不是我们得到的。这里我们遇到了错误:生成密钥的方法只考虑了高度、宽度和深度的值,但不考虑顺序,并且 w
、h
或 d
的任何排列都将具有相同的键 :
hash = 97 * hash + Float.floatToIntBits(w);
hash = 97 * hash + Float.floatToIntBits(h);
hash = 97 * hash + Float.floatToIntBits(d);
虽然此错误与此 无关,但它是由于网格管理器的问题而发生的,并且已提交错误。
解决方法
现在我只会使用稍微不同的尺寸,例如:
Box1: {W: 1, H: 2, D: 1.5}
Box2: {W: 1.500001, H: 1, D: 2}
或者,如果您无法更改这些维度,则可以提供自己的 TriangleMesh
框实现,该框不会被缓存。例如,您可以在 FXyz3D library 中找到一个。
更新
我刚刚注意到这个错误已经提交 here
This bug is the result of mistakenly assuming that two boxes with an equal hash key have equal dimensions.
我目前正在处理一个项目,我应该在窗格上显示 3D 框,为此我正在使用 javafx 3D。首先我画了一个大盒子,在我的项目中叫做 container 并创建了一个相机。然后将按钮添加到场景中,我在大容器中绘制其他较小的盒子。然而,由于某种原因,小盒子会相互影响并改变它们的坐标。更多图片说明:
这是box1,我用键"X"画的:
这是box2,我用键"C"画的:
如果我只添加 box1 并重新启动我的应用程序,然后绘制 box2,则相应地输出图像 1 和图像 2。但是,如果我不是每次都重新启动应用程序并且我想绘制另一个框,我会得到以下输出。
box2 的输出,如果在它之前绘制了 box1:
box1 的输出,如果在它之前绘制了 box2:
如果我先画box1,box1看起来没问题,但是图3是box2的输出。如果我先绘制 box2,box1 输出将变为图像 4。
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
public class ContainerPane extends Parent {
//size of big container
private final double CONTAINER_DEPTH = 16.5;
private final double CONTAINER_WIDTH = 2.5;
private final double CONTAINER_HEIGHT = 4.0;
//group in which the box, container and camera are added
private Group root;
//coordiantes of box 1, row one is the actual coordinates and row two is the width, height, depth
private double[][] box1 = {{0, 4, 30},
{1, 2, 1.5}};
//coordiantes of box 2, row one is the actual coordinates and row two is the width, height, depth
private double[][] box2 = {{0, 6, 28},
{1.5, 1, 2}};
public ContainerPane(int Scene_Width, int Scene_Length){
//create the group
root = new Group();
root.setAutoSizeChildren(false);
//creating container
Box container = new Box(CONTAINER_WIDTH , CONTAINER_HEIGHT, CONTAINER_DEPTH);
container.setCullFace(CullFace.NONE);
//drawing the container with only lines
container.setDrawMode(DrawMode.LINE);
//setting the color of the container
PhongMaterial material = new PhongMaterial(Color.ORANGE);
container.setMaterial(material);
root.getChildren().add(container);
//create a camera
PerspectiveCamera camera = new PerspectiveCamera(true);
//add possible rotations and position of camera
camera.getTransforms().addAll(new Translate(0, 0, -35));
root.getChildren().add(camera);
//create a Scene from the group
SubScene subScene = new SubScene(root, Scene_Width, Scene_Length, true, SceneAntialiasing.BALANCED);
//set a camera for the scene
subScene.setCamera(camera);
getChildren().add(subScene);
}
public void drawBox1(){
//clear everything from root except container and camera
try{
root.getChildren().remove(2);
}
catch(Exception exception){
}
//create box1
Box box = new Box(box1[1][0], box1[1][1], box1[1][2]);
box.setDrawMode(DrawMode.FILL);
box.setMaterial(new PhongMaterial(Color.BLUE));
box.setTranslateX(-CONTAINER_WIDTH/2 + box.getWidth()/2 + 0.5*box1[0][0]);
box.setTranslateY(-CONTAINER_HEIGHT/2 + box.getHeight()/2 + 0.5*box1[0][1]);
box.setTranslateZ(CONTAINER_DEPTH/2 - box.getDepth()/2 - 0.5*box1[0][2]);
//add it to the group
root.getChildren().add(box);
}
public void drawBox2(){
try{
root.getChildren().remove(2);
}
catch(Exception exception){
}
Box box = new Box(box2[1][0], box2[1][1], box2[1][2]);
box.setDrawMode(DrawMode.FILL);
box.setMaterial(new PhongMaterial(Color.BLUE));
box.setTranslateX(-CONTAINER_WIDTH/2 + box.getWidth()/2 + 0.5*box2[0][0]);
box.setTranslateY(-CONTAINER_HEIGHT/2 + box.getHeight()/2 + 0.5*box2[0][1]);
box.setTranslateZ(CONTAINER_DEPTH/2 - box.getDepth()/2 - 0.5*box2[0][2]);
root.getChildren().add(box);
}
}
主文件,我在其中创建 class.
的实例import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class Error extends Application {
@Override
public void start(Stage primaryStage) {
ContainerPane container = new ContainerPane(750, 750);
Scene scene = new Scene(container);
scene.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>(){
@Override
public void handle(KeyEvent event){
if(event.getCode() == KeyCode.X){
container.drawBox1();
}
if(event.getCode() == KeyCode.C){
container.drawBox2();
}
}});
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
总结提出的问题:无论将它们添加到场景的顺序如何,绘制两个相似的 3D 框都应该有效,但事实是顺序 很重要 和结果是错误.
(为方便起见,我将框 2 设置为红色)
当先绘制较高的蓝色框 1 时,较短的红色框与较高的框尺寸完全相同。
当先绘制较短的红色框 2 时,会绘制较高的蓝色框,其尺寸与较短的红色框完全相同。
说明
对此行为有一个解释:当您将长方体、圆柱体或球体绘制到 scene/subscene 时,网格管理器中有一个内部缓存 javafx.scene.shape.PredefinedMeshManager
class:
private HashMap<Integer, TriangleMesh> boxCache = null;
第一次添加网格时:
if (key == 0) {
key = generateKey(w, h, d);
}
mesh = manager.getBoxMesh(w, h, d, key);
密钥为空,管理员为您创建盒子:
if (mesh == null) {
mesh = Box.createMesh(w, h, d);
boxCache.put(key, mesh);
}
并存储此网格的密钥。在 Box
的情况下,这是生成此密钥的方式:
private static int generateKey(float w, float h, float d) {
int hash = 3;
hash = 97 * hash + Float.floatToIntBits(w);
hash = 97 * hash + Float.floatToIntBits(h);
hash = 97 * hash + Float.floatToIntBits(d);
return hash;
}
现在,对于第二个网格,您生成密钥,然后去向经理询问现有网格:
TriangleMesh mesh = boxCache.get(key);
鉴于 box1
和 box2
不同:
Box1: {W: 1, H: 2, D: 1.5}
Box2: {W: 1.5, H: 1, D: 2}
任何人都认为缓存会 return 为空并生成一个新的网格...
...但这不是我们得到的。这里我们遇到了错误:生成密钥的方法只考虑了高度、宽度和深度的值,但不考虑顺序,并且 w
、h
或 d
的任何排列都将具有相同的键 :
hash = 97 * hash + Float.floatToIntBits(w);
hash = 97 * hash + Float.floatToIntBits(h);
hash = 97 * hash + Float.floatToIntBits(d);
虽然此错误与此
解决方法
现在我只会使用稍微不同的尺寸,例如:
Box1: {W: 1, H: 2, D: 1.5}
Box2: {W: 1.500001, H: 1, D: 2}
或者,如果您无法更改这些维度,则可以提供自己的 TriangleMesh
框实现,该框不会被缓存。例如,您可以在 FXyz3D library 中找到一个。
更新
我刚刚注意到这个错误已经提交 here
This bug is the result of mistakenly assuming that two boxes with an equal hash key have equal dimensions.