Due to new features in JavaFX 8, it became possible to combine 3D objects with 2D UI controls.
I used this documents as manuals: JavaFX Tutorial, Exploring JavaFX 3D.
So, I made this code:
public class CastAnalytics extends Application {
final Group root = new Group();
final Group axisGroup = new Group();
final XForm world = new XForm();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final PerspectiveCamera subSceneCamera = new PerspectiveCamera(false);
final XForm cameraXForm = new XForm();
final XForm cameraXForm2 = new XForm();
final XForm cameraXForm3 = new XForm();
final double cameraDistance = 450;
final XForm moleculeGroup = new XForm();
private Timeline timeline;
boolean timelinePlaying = false;
double CONTROL_MULTIPLIER = 0.1;
double SHIFT_MULTIPLIER = 0.1;
double ALT_MULTIPLIER = 0.5;
double mousePosX;
double mousePosY;
double mouseOldX;
double mouseOldY;
double mouseDeltaX;
double mouseDeltaY;
@Override
public void start(Stage primaryStage) throws Exception{
buildScene();
buildCamera();
buildAxes();
Scene scene = new Scene(root, 1024, 768, true);
scene.setFill(Color.GREY);
handleKeyboard(scene, world);
handleMouse(scene, world);
primaryStage.setTitle("Sample Application");
primaryStage.setScene(scene);
primaryStage.show();
scene.setCamera(subSceneCamera);
scene.setCamera(camera);
}
private void buildScene() {
root.getChildren().add(world);
Label label = new Label("123");
HBox hBox = new HBox();
hBox.getChildren().add(label);
SubScene subScene = new SubScene(hBox, 200, 200);
subScene.setLayoutX(100);
subScene.setLayoutY(100);
root.getChildren().addAll(subScene);
}
private void buildCamera() {
root.getChildren().addAll(cameraXForm);
cameraXForm.getChildren().add(cameraXForm2);
cameraXForm2.getChildren().add(cameraXForm3);
cameraXForm3.getChildren().add(camera);
cameraXForm3.setRotateZ(180.0);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-cameraDistance);
cameraXForm.ry.setAngle(320.0);
cameraXForm.rx.setAngle(40);
}
private void buildAxes() {
Box box = new Box(200,200,200);
axisGroup.getChildren().addAll(box);
world.getChildren().addAll(axisGroup);
}
private void handleMouse(Scene scene, final Node root) {
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent me) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
});
scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent me) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 1.0;
double modifierFactor = 0.1;
if (me.isControlDown()) {
modifier = 0.1;
}
if (me.isShiftDown()) {
modifier = 10.0;
}
if (me.isPrimaryButtonDown()) {
cameraXForm.ry.setAngle(cameraXForm.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0); // +
cameraXForm.rx.setAngle(cameraXForm.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0); // -
} else if (me.isSecondaryButtonDown()) {
double z = camera.getTranslateZ();
double newZ = z + mouseDeltaX * modifierFactor * modifier;
camera.setTranslateZ(newZ);
} else if (me.isMiddleButtonDown()) {
cameraXForm2.t.setX(cameraXForm2.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
cameraXForm2.t.setY(cameraXForm2.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
}
}
});
}
private void handleKeyboard(Scene scene, final Node root) {
final boolean moveCamera = true;
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
Duration currentTime;
switch (event.getCode()) {
case Z:
if (event.isShiftDown()) {
cameraXForm.ry.setAngle(0.0);
cameraXForm.rx.setAngle(0.0);
camera.setTranslateZ(-300.0);
}
cameraXForm2.t.setX(0.0);
cameraXForm2.t.setY(0.0);
break;
case X:
if (event.isControlDown()) {
if (axisGroup.isVisible()) {
axisGroup.setVisible(false);
} else {
axisGroup.setVisible(true);
}
}
break;
case S:
if (event.isControlDown()) {
if (moleculeGroup.isVisible()) {
moleculeGroup.setVisible(false);
} else {
moleculeGroup.setVisible(true);
}
}
break;
case SPACE:
if (timelinePlaying) {
timeline.pause();
timelinePlaying = false;
} else {
timeline.play();
timelinePlaying = true;
}
break;
case UP:
if (event.isControlDown() && event.isShiftDown()) {
cameraXForm2.t.setY(cameraXForm2.t.getY() - 10.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown() && event.isShiftDown()) {
cameraXForm.rx.setAngle(cameraXForm.rx.getAngle() - 10.0 * ALT_MULTIPLIER);
} else if (event.isControlDown()) {
cameraXForm2.t.setY(cameraXForm2.t.getY() - 1.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown()) {
cameraXForm.rx.setAngle(cameraXForm.rx.getAngle() - 2.0 * ALT_MULTIPLIER);
} else if (event.isShiftDown()) {
double z = camera.getTranslateZ();
double newZ = z + 5.0 * SHIFT_MULTIPLIER;
camera.setTranslateZ(newZ);
}
break;
case DOWN:
if (event.isControlDown() && event.isShiftDown()) {
cameraXForm2.t.setY(cameraXForm2.t.getY() + 10.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown() && event.isShiftDown()) {
cameraXForm.rx.setAngle(cameraXForm.rx.getAngle() + 10.0 * ALT_MULTIPLIER);
} else if (event.isControlDown()) {
cameraXForm2.t.setY(cameraXForm2.t.getY() + 1.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown()) {
cameraXForm.rx.setAngle(cameraXForm.rx.getAngle() + 2.0 * ALT_MULTIPLIER);
} else if (event.isShiftDown()) {
double z = camera.getTranslateZ();
double newZ = z - 5.0 * SHIFT_MULTIPLIER;
camera.setTranslateZ(newZ);
}
break;
case RIGHT:
if (event.isControlDown() && event.isShiftDown()) {
cameraXForm2.t.setX(cameraXForm2.t.getX() + 10.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown() && event.isShiftDown()) {
cameraXForm.ry.setAngle(cameraXForm.ry.getAngle() - 10.0 * ALT_MULTIPLIER);
} else if (event.isControlDown()) {
cameraXForm2.t.setX(cameraXForm2.t.getX() + 1.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown()) {
cameraXForm.ry.setAngle(cameraXForm.ry.getAngle() - 2.0 * ALT_MULTIPLIER);
}
break;
case LEFT:
if (event.isControlDown() && event.isShiftDown()) {
cameraXForm2.t.setX(cameraXForm2.t.getX() - 10.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown() && event.isShiftDown()) {
cameraXForm.ry.setAngle(cameraXForm.ry.getAngle() + 10.0 * ALT_MULTIPLIER); // -
} else if (event.isControlDown()) {
cameraXForm2.t.setX(cameraXForm2.t.getX() - 1.0 * CONTROL_MULTIPLIER);
} else if (event.isAltDown()) {
cameraXForm.ry.setAngle(cameraXForm.ry.getAngle() + 2.0 * ALT_MULTIPLIER); // -
}
break;
}
}
});
}
public static void main(String[] args) {
launch(args);
}
}
But te result isn't that what I expected. I wanted to have Pane
for UI controls above the 3D object, but what I get is this:
What am I doing wrong?
From what I understand from the (limited) tests I have done, there are two options:
Set a camera for a sub-scene and add that sub-scene to the root. You will be using only one camera. Your world will have to be a separate group and flying/pivoting camera view will have to be accomplished by transforming the world group.
File a bug report with JavaFX jira.
I was not successful using a separate camera as a sub-scene camera. No transforms applied to the camera or a sub-scene itself ever rotated a sub-scene from the default position similar to the one in your screenshot. At this point with Oracle not releasing any sub-scene documentation, we can only wait till they come clean and fill the gaps. Until then we can consider subscene support in JavaFX 3D broken.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With