JavaFX 3D rotations - java

My problems are:
the below code is based on the "MolecularSampleApp" from the oracle tutorial site, but very much simplified. It only shows a box and a red sphere for orientation purpose. The sequence of rotations is around x-axis then y-axis and finally z-axis. Following rotations are apparently done in coordinate-axes that are rotated with the rotations before.
(I checked that again and again with a cube and sequences of 90° rotations) So, when the user rotates the camera view with the mouse, this is very unintuitive, because the rotation behaviour changes after rotating around the vertical screen axis (because the horizontal axis will then be rotated too).
Try it with my code below, or with the MolecularSampleApp - its the same unnatural feeling. Is there an easy way to get over this?
But what I don't even understand is the behaviour when the mousePressed-code is performed: Here, the camera is always rotated in a FIXED system! The axes are NOT rotated with the camera, although its essentially the same code (except the rotation angle is of course not accumulated here). Anybody knows how is this possible?
package trafotest;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TrafoTest extends Application {
final Group root = new Group();
final XformWorld world = new XformWorld();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final XformCamera cameraXform = new XformCamera();
private static final double CAMERA_INITIAL_DISTANCE = -1000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
#Override
public void start(Stage primaryStage) {
root.getChildren().add(world);
root.setDepthTest(DepthTest.ENABLE);
buildCamera();
buildBodySystem();
Scene scene = new Scene(root, 800, 600, true);
scene.setFill(Color.GREY);
handleMouse(scene);
primaryStage.setTitle("Transformationen");
primaryStage.setScene(scene);
primaryStage.show();
scene.setCamera(camera);
}
private void buildCamera() {
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
}
private void buildBodySystem() {
PhongMaterial whiteMaterial = new PhongMaterial();
whiteMaterial.setDiffuseColor(Color.WHITE);
whiteMaterial.setSpecularColor(Color.LIGHTBLUE);
Box box = new Box(400, 200, 100);
box.setMaterial(whiteMaterial);
PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
Sphere sphere = new Sphere(5);
sphere.setMaterial(redMaterial);
sphere.setTranslateZ(-50.0);
world.getChildren().addAll(box);
world.getChildren().addAll(sphere);
}
private void handleMouse(Scene scene) {
scene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
// this is done after clicking and the rotations are apearently
// performed in coordinates that are NOT rotated with the camera.
// (pls activate the two lines below for clicking)
//cameraXform.rx.setAngle(-90.0);
//cameraXform.ry.setAngle(180.0);
});
scene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (me.isPrimaryButtonDown()) {
// this is done when the mouse is dragged and each rotation is
// performed in coordinates, that are rotated with the camera.
cameraXform.ry.setAngle(cameraXform.ry.getAngle() + mouseDeltaX * 0.2);
cameraXform.rx.setAngle(cameraXform.rx.getAngle() - mouseDeltaY * 0.2);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
class XformWorld extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformWorld() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}
class XformCamera extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformCamera() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}

I found a solution that works for me, just in case someone else is interested too:
package trafotest;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Point3D;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TrafoTest extends Application {
final Group root = new Group();
final XformWorld world = new XformWorld();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final XformCamera cameraXform = new XformCamera();
private static final double CAMERA_INITIAL_DISTANCE = -1000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
double mouseFactorX, mouseFactorY;
#Override
public void start(Stage primaryStage) {
root.getChildren().add(world);
root.setDepthTest(DepthTest.ENABLE);
buildCamera();
buildBodySystem();
Scene scene = new Scene(root, 800, 600, true);
scene.setFill(Color.GREY);
handleMouse(scene);
primaryStage.setTitle("TrafoTest");
primaryStage.setScene(scene);
primaryStage.show();
scene.setCamera(camera);
mouseFactorX = 180.0 / scene.getWidth();
mouseFactorY = 180.0 / scene.getHeight();
}
private void buildCamera() {
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
}
private void buildBodySystem() {
PhongMaterial whiteMaterial = new PhongMaterial();
whiteMaterial.setDiffuseColor(Color.WHITE);
whiteMaterial.setSpecularColor(Color.LIGHTBLUE);
Box box = new Box(400, 200, 100);
box.setMaterial(whiteMaterial);
PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
Sphere sphere = new Sphere(5);
sphere.setMaterial(redMaterial);
sphere.setTranslateX(200.0);
sphere.setTranslateY(-100.0);
sphere.setTranslateZ(-50.0);
world.getChildren().addAll(box);
world.getChildren().addAll(sphere);
}
private void handleMouse(Scene scene) {
scene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (me.isPrimaryButtonDown()) {
cameraXform.ry(mouseDeltaX * 180.0 / scene.getWidth());
cameraXform.rx(-mouseDeltaY * 180.0 / scene.getHeight());
} else if (me.isSecondaryButtonDown()) {
camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
class XformWorld extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformWorld() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}
class XformCamera extends Group {
Point3D px = new Point3D(1.0, 0.0, 0.0);
Point3D py = new Point3D(0.0, 1.0, 0.0);
Rotate r;
Transform t = new Rotate();
public XformCamera() {
super();
}
public void rx(double angle) {
r = new Rotate(angle, px);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void ry(double angle) {
r = new Rotate(angle, py);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
}

Related

Strange line appears in shape after changing camera angle

I have a program that constitutes a Cube class that extends from the Group class, in this Cube I create a Polygon that serves as one of the faces of this Cube, when facing it it renders normal, but when the camera perspective rotates back the polygon of After creating a strange white line over the front one, I noticed that this happens due to the order in which items are created and added to the container via getChildren (). add (), this line cannot be rendered independent the order in which cubes and faces are created and added to the container?
Class cube.java
import java.util.ArrayList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point3D;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
public class Cube extends Group{
public final Rotate rx = new Rotate(0, Rotate.X_AXIS);
public final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
public final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
public final Translate translate;
public final double x;
public final double y;
public final double z;
public final Group group;
public static final ArrayList<Cube> instances = new ArrayList<Cube>();
public static final int A_LEFT = 0;
public static final int A_RIGHT = 1;
public static final int A_TOP = 2;
public static final int A_BOTTOM = 3;
public static final int A_FRONT = 4;
public static final int A_BACK = 5;
public Cube(double tam, double x, double y, double z, Color cor, Group grupo) {
this.group = grupo;
setDepthTest(DepthTest.ENABLE);
setCacheHint(CacheHint.SCALE_AND_ROTATE);
setCache(true);
Cube.instances.add(this);
//System.out.print(Cube.instances.size());
grupo.getChildren().add(this);
this.x = x;
this.y = y;
this.z = z;
translate = new Translate(x,y,z);
getTransforms().addAll(rx,ry,rz, translate);
Button b = new Button("Test Button");
b.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event event) {
System.out.println("Test click");
}
});
HBox frontFace = this.createFace(tam, tam, tam*-0.5, tam*-0.5, tam*-0.5, Rotate.X_AXIS, 0, cor.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
HBox backFace = this.createFace(tam, tam, tam*-0.5, tam*-0.5, tam*0.5, Rotate.Y_AXIS, 180d, cor.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
HBox leftFace = this.createFace(tam, tam, tam*-1, tam*-0.5, 0, Rotate.Y_AXIS, 90d, cor.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
HBox rightFace = this.createFace(tam, tam, 0, tam*-0.5, 0, Rotate.Y_AXIS, -90d, cor.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
HBox bottomFace = this.createFace(tam, tam, tam*-0.5, 0, 0, Rotate.X_AXIS, 90d, cor.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
HBox topFace = this.createFace(tam, tam, tam*-0.5, tam*-1, 0, Rotate.X_AXIS, -90d, cor.deriveColor(0.0, 1.0, (1 - 0.6 * 1), 1.0));
Polygon polygon = new Polygon();
polygon.getPoints().addAll(new Double[]{
55.0, 0.0,
110.0, 110.0,
0.0, 110.0
});
polygon.setStroke(Color.BROWN);
polygon.setStrokeWidth(0);
polygon.setSmooth(false);
polygon.setCacheHint(CacheHint.SCALE_AND_ROTATE);
backFace.getChildren().add(polygon);
backFace.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)));
polygon.setFill(Color.BLACK);
polygon.setStroke(null);
// leftFace.getChildren().add(polygon2);
ImageView anImage = new ImageView(new Image("android.png",110,110,false,true));
anImage.setFitWidth(100);
anImage.setPreserveRatio(true);
anImage.setCacheHint(CacheHint.SCALE_AND_ROTATE);
anImage.setSmooth(true);
anImage.setCache(true);
// backFace.getChildren().add(anImage);
/*BackgroundImage myBI= new BackgroundImage(new Image("android.png",110,110,false,true),
BackgroundRepeat.REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT,
BackgroundSize.DEFAULT);*/
//frontFace.setBackground(new Background(myBI));
frontFace.setStyle("-fx-background-color:rgba(0,0,0,0)");
rightFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_RIGHT));
leftFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_LEFT));
topFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_TOP));
bottomFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_BOTTOM));
frontFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_FRONT));
backFace.setOnMouseClicked(e -> this.createNextCube(e, Cube.A_BACK));
//frontFace.getChildren().addAll(b);
getChildren().addAll(
frontFace,
leftFace,
rightFace,
topFace,
bottomFace,
backFace
);
}
public HBox createFace(double w, double h, double x, double y, double z, Point3D axis, double rotation, Color cor) {
HBox g = new HBox();
g.setPrefSize(w, h);
g.setTranslateX(x);
g.setTranslateY(y);
g.setTranslateZ(z);
g.setAlignment(Pos.CENTER);
g.setRotationAxis(axis);
g.setRotate(rotation);
g.setBackground(new Background(new BackgroundFill(cor, CornerRadii.EMPTY, Insets.EMPTY)));
return g;
}
public Cube createNextCube(MouseEvent e, int side) {
if(e.getButton() == MouseButton.PRIMARY) {
double px = this.x;
double py = this.y;
double pz = this.z;
switch(side) {
case Cube.A_RIGHT:
px += 110;
break;
case Cube.A_LEFT:
px -= 110;
break;
case Cube.A_TOP:
py -= 110;
break;
case Cube.A_BOTTOM:
py += 110;
break;
case Cube.A_FRONT:
pz -= 110;
break;
case Cube.A_BACK:
pz += 110;
break;
}
return new Cube(110, px, py, pz, Color.ALICEBLUE, this.group);
}
return null;
}
}
Class main.java
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.CacheHint;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application{
private PerspectiveCamera camera;
private Rotate cameraRotateX, cameraRotateY, cameraRotateZ;
private double antX;
private double antY;
public void init(Stage primaryStage) {
camera = new PerspectiveCamera();
cameraRotateX = new Rotate(0, Rotate.X_AXIS);
cameraRotateY = new Rotate(0, Rotate.Y_AXIS);
cameraRotateZ = new Rotate(0, Rotate.Z_AXIS);
Translate cameraTranslate = new Translate(-700 / 2, -300 / 2, 0);
camera.getTransforms().addAll(
cameraRotateX,
cameraRotateY,
cameraRotateZ,
cameraTranslate);
Group root = new Group();
root.setDepthTest(DepthTest.ENABLE);
primaryStage.setResizable(true);
primaryStage.setScene(new Scene(root, 700, 300, true, SceneAntialiasing.BALANCED));
primaryStage.getScene().setCamera(camera);
// root.getTransforms().addAll(
//new Translate(700 / 2, 300 / 2),
//new Rotate(180, Rotate.X_AXIS)
// );
//Group gp = new Group();
// gp.setDepthTest(DepthTest.);
//gp.setCache(true);
camera.setNearClip(0.01);
Cube cubo = new Cube(110, 0,0,0, Color.RED, root);
Timeline animation = new Timeline();
animation.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(cubo.ry.angleProperty(), 0d),
new KeyValue(cubo.rx.angleProperty(), 0d)
),
new KeyFrame(Duration.seconds(30),
new KeyValue(cubo.ry.angleProperty(), 360d),
new KeyValue(cubo.rx.angleProperty(), 360d)
)
);
animation.setCycleCount(Animation.INDEFINITE);
//root.getChildren().add(gp);
root.setCache(true);
// root.setCacheHint(CacheHint.SCALE_AND_ROTATE);
//animation.play();
primaryStage.getScene().setOnMousePressed(e -> pressed(e));
primaryStage.getScene().setOnMouseDragged(e -> moved(e));
primaryStage.getScene().setOnScroll(e -> cameraTranslate.setZ(cameraTranslate.getZ()+e.getDeltaY()));
primaryStage.getScene().heightProperty().addListener(h -> cameraTranslate.setY(-1*(primaryStage.getScene().getHeight())/2));
primaryStage.getScene().widthProperty().addListener(w -> cameraTranslate.setX(-1*(primaryStage.getScene().getWidth())/2));
}
public void pressed(MouseEvent ev) {
antX = ev.getSceneX();
antY = ev.getSceneY();
}
public void moved(MouseEvent ev) {
double movX = ev.getSceneX() - antX;
double movY = ev.getSceneY() - antY;
cameraRotateX.setAngle(cameraRotateX.getAngle() + movY);
cameraRotateY.setAngle(cameraRotateY.getAngle() + movX);
antX = ev.getSceneX();
antY = ev.getSceneY();
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage palco) throws Exception {
init(palco);
palco.show();
}
}

Creating a 3d dice cube in javafx and doing its 3D dice animation

I've implemented a dice cube for a game I am developing with some partners and I am trying to create an animation for the cube.
Up until now the only thing I am able to achieve is to Rotate the cube 360 degrees for a duration of 3000 mils I set in an axis pattern I randomly create.
I however am not able to make a rotation on a Y axis and I am always landing on the same face of the cube(the number 4 for some reason).
here is the code that deals with the above text:
package test;
import javafx.application.Application;
import java.util.Random;
import javafx.animation.RotateTransition;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TrafoTest extends Application {
final Group root = new Group();
final XformWorld world1 = new XformWorld();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final XformCamera cameraXform = new XformCamera();
private static final double CAMERA_INITIAL_DISTANCE = -1000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
#Override
public void start(Stage primaryStage) {
root.getChildren().add(world1);
CuboidMesh cuboid = new CuboidMesh(50f,50f,50f);
cuboid.setTextureModeImage(getClass().getResource("/test/allFacesv2.png").toExternalForm());
RotateTransition rt = new RotateTransition(Duration.millis(3000), cuboid);
float number1;
float number2;
float number3;
float minX = 1.0f;
float maxX = 50.0f;
Random rand = new Random();
number1 = rand.nextFloat()* (maxX - minX) * minX;
number2 = rand.nextFloat()* (maxX - minX) + minX;
number3 = rand.nextFloat()* (maxX - minX) - minX;
rt.setByAngle(360);
rt.setAxis(new javafx.geometry.Point3D(number1,number2,number3));
// rt.setCycleCount(2);
//rt.setAutoReverse(true);
rt.play();
root.getChildren().addAll(cuboid);
root.setDepthTest(DepthTest.ENABLE);
buildCamera();
Scene scene = new Scene(root, 800, 800, true);
scene.setFill(Color.GREY);
handleMouse(scene);
primaryStage.setTitle("Transformationen");
primaryStage.setScene(scene);
primaryStage.show();
scene.setCamera(camera);
}
private void buildCamera() {
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
}
private void handleMouse(Scene scene) {
scene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
// this is done after clicking and the rotations are apearently
// performed in coordinates that are NOT rotated with the camera.
// (pls activate the two lines below for clicking)
//cameraXform.rx.setAngle(-90.0);
//cameraXform.ry.setAngle(180.0);
});
scene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (me.isPrimaryButtonDown()) {
// this is done when the mouse is dragged and each rotation is
// performed in coordinates, that are rotated with the camera.
cameraXform.ry.setAngle(cameraXform.ry.getAngle() + mouseDeltaX * 0.2);
cameraXform.rx.setAngle(cameraXform.rx.getAngle() - mouseDeltaY * 0.2);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
class XformWorld extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformWorld() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}
class XformCamera extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformCamera() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}
the image I used is:

How to set axis (triad) at fixed position on screen in JavaFX?

How to set axis (triad) at fixed position on screen in JavaFX? I am currently developing one application in which I want to show axis (triad) at fixed position on my screen (i.e. bottom-left corner). I want rotation of axis should be in sync with the main object. Zoom and Translate operation should not be applied to axis.
But I am facing some difficulties to show axis at specific position on screen.
I have used screenToLocal method to get fixed position in scene but it only returns Point2D object which is not helpful to set 3D translate values.
Can you please give me solution for this problem?
Source code based on this example is as below:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TrafoTest extends Application {
final Group root = new Group();
Group axis = new Group();
final XformWorld world = new XformWorld();
final PerspectiveCamera camera = new PerspectiveCamera(true);
final XformCamera cameraXform = new XformCamera();
final XformCamera cameraXform2 = new XformCamera();
final XformCamera cameraXform3 = new XformCamera();
private static final double CAMERA_INITIAL_DISTANCE = -1000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 10000.0;
private static final double MOUSE_SPEED = 1;
private static final double ROTATION_SPEED = 4.0;
private static final double TRACK_SPEED = 0.02;
double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
double mouseFactorX, mouseFactorY;
Stage stage;
#Override
public void start(Stage primaryStage) {
root.getChildren().add(world);
root.setDepthTest(DepthTest.ENABLE);
buildCamera();
buildBodySystem();
Scene scene = new Scene(root, 800, 600, true);
scene.setFill(Color.GREY);
handleMouse(scene);
this.stage = primaryStage;
primaryStage.setTitle("TrafoTest");
primaryStage.setScene(scene);
primaryStage.show();
scene.setCamera(camera);
mouseFactorX = 180.0 / scene.getWidth();
mouseFactorY = 180.0 / scene.getHeight();
}
private void buildCamera() {
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(cameraXform2);
cameraXform2.getChildren().add(cameraXform3);
cameraXform3.getChildren().add(camera);
camera.setNearClip(CAMERA_NEAR_CLIP);
camera.setFarClip(CAMERA_FAR_CLIP);
camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
}
private void buildBodySystem() {
PhongMaterial whiteMaterial = new PhongMaterial();
whiteMaterial.setDiffuseColor(Color.WHITE);
whiteMaterial.setSpecularColor(Color.LIGHTBLUE);
Box box = new Box(400, 200, 100);
box.setMaterial(whiteMaterial);
box.setDrawMode(DrawMode.LINE);
PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
Sphere sphere = new Sphere(5);
sphere.setMaterial(redMaterial);
sphere.setTranslateX(200.0);
sphere.setTranslateY(-100.0);
sphere.setTranslateZ(-50.0);
axis = drawReferenceFrame();
world.getChildren().addAll(axis);
world.getChildren().add(box);
world.getChildren().addAll(sphere);
}
private void handleMouse(Scene scene) {
scene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (me.isPrimaryButtonDown()) {
cameraXform.ry(mouseDeltaX * 180.0 / scene.getWidth());
cameraXform.rx(-mouseDeltaY * 180.0 / scene.getHeight());
BoundingBox point = (BoundingBox) root.screenToLocal(new BoundingBox(root.getLayoutX()+350, root.getLayoutY()+650, 0, 0,0, 20));
System.out.println(point);
axis.setTranslateX(point.getMinX());
axis.setTranslateY(point.getMinY());
axis.setTranslateZ(point.getMinZ());
} else if (me.isSecondaryButtonDown()) {
cameraXform2.setTx((cameraXform2.t.getX() + (-mouseDeltaX)*MOUSE_SPEED*TRACK_SPEED));
cameraXform2.setTy((cameraXform2.t.getY() + (-mouseDeltaY)*MOUSE_SPEED*TRACK_SPEED));
camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
}
});
scene.setOnScroll(new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double z = cameraXform3.getTranslateZ();
double newZ = z - event.getDeltaY() * MOUSE_SPEED * 0.05;
cameraXform3.setTranslateZ(newZ);
}
});
}
public static void main(String[] args) {
launch(args);
}
private Group drawReferenceFrame(){
Group G1= new Group();
Cylinder CX = new Cylinder(2,25);
Cylinder CY = new Cylinder(2,25);
Cylinder CZ = new Cylinder(2,25);
Sphere S = new Sphere(4);
Material mat =new PhongMaterial(Color.WHITE);
PhongMaterial Xmat = new PhongMaterial();
Xmat.setDiffuseColor(Color.GREEN);
PhongMaterial Ymat = new PhongMaterial();
Ymat.setDiffuseColor(Color.BLUE);
PhongMaterial Zmat = new PhongMaterial();
Zmat.setDiffuseColor(Color.RED);
S.setMaterial(Zmat);
CY.setMaterial(mat);
// CY.setRotationAxis(Rotate.X_AXIS);
// CY.setRotate(90);
CY.setTranslateY(-12.5);
CX.setMaterial(mat);
CX.setTranslateX(15);
CX.setRotationAxis(Rotate.Z_AXIS);
CX.setRotate(90);
CZ.setMaterial(mat);
CZ.setRotationAxis(Rotate.X_AXIS);
CZ.setRotate(90);
CZ.setTranslateZ(-12.5);
G1.getChildren().addAll(CX,CY,CZ,S);
TriangleMesh coneMeshY = createCone(3.5f, 7.5f);
TriangleMesh coneMeshX = createCone(3.5f, 7.5f);
TriangleMesh coneMeshZ = createCone(3.5f, 7.5f);
MeshView yCone = new MeshView(coneMeshY);
MeshView xCone = new MeshView(coneMeshX);
MeshView zCone = new MeshView(coneMeshZ);
yCone.setMaterial(Ymat);
yCone.setTranslateY(-32.5);
yCone.setDrawMode(DrawMode.FILL);
xCone.setMaterial(Xmat);
xCone.setTranslateY(-3.75);
xCone.setRotationAxis(Rotate.Z_AXIS);
xCone.setRotate(90);
xCone.setTranslateX(28.5);
xCone.setDrawMode(DrawMode.FILL);
zCone.setRotationAxis(Rotate.X_AXIS);
zCone.setTranslateY(-3.75);
zCone.setRotate(90);
zCone.setTranslateZ(-28.5);
zCone.setDrawMode(DrawMode.FILL);
zCone.setMaterial(Zmat);
G1.getChildren().addAll(xCone,yCone,zCone);
// G1.setScale(0.45);
return G1;
}
private TriangleMesh createCone( float radius, float height) {
int divisions=500;
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().addAll(0,0,0);
double segment_angle = 2.0 * Math.PI / divisions;
float x, z;
double angle;
double halfCount = (Math.PI / 2 - Math.PI / (divisions / 2));
for(int i=divisions+1;--i >= 0; ) {
angle = segment_angle * i;
x = (float)(radius * Math.cos(angle - halfCount));
z = (float)(radius * Math.sin(angle - halfCount));
mesh.getPoints().addAll(x,height,z);
}
mesh.getPoints().addAll(0,height,0);
mesh.getTexCoords().addAll(0,0);
for(int i=1;i<=divisions;i++) {
mesh.getFaces().addAll(
0,0,i+1,0,i,0, //COunter clock wise
divisions+2,0,i,0,i+1,0 // Clock wise
);
}
return mesh;
}
}
class XformWorld extends Group {
final Translate t = new Translate(0.0, 0.0, 0.0);
final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);
public XformWorld() {
super();
this.getTransforms().addAll(t, rx, ry, rz);
}
}
class XformCamera extends Group {
Point3D px = new Point3D(1.0, 0.0, 0.0);
Point3D py = new Point3D(0.0, 1.0, 0.0);
Rotate r;
Transform tx = new Rotate();
Translate t = new Translate();
public XformCamera() {
super();
}
public void rx(double angle) {
r = new Rotate(angle, px);
this.tx = tx.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(tx);
}
public void ry(double angle) {
r = new Rotate(angle, py);
this.tx = tx.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(tx);
}
public void setTx(double x) {
t.setX(x);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void setTy(double y) {
t.setY(y);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
}
In above code, first I have translated the axis at left-bottom corner of my screen, but after rotating the main object (ie. BOX and SPHERE), axis is also translated.I wanted to rotate main object and axis about their own origins.
If I tried to zoom in (changed camera Z position), then also I axis location changed with respect to screen.
In above cases, I wanted to display my axis at left-bottom corner of the screen and Rotation of the axis should be in sync with the main object.
I wanted the axis to be fixed at left, bottom corner of the screen and axis should rotate about it's own origin.
This variation leaves the axes at the origin, moves the box to
P = (size / 2, -size / 2, -size / 2)
relative to the axes, and pans the camera toward the bottom, center of the screen. Uncomment the call to camera.setTranslateX() to pan left. Moving the mouse causes the group to rotate about the axes' origin. Press shift to rotate about z, and use the mouse wheel to dolly the camera.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
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 http://stackoverflow.com/a/37734966/230513
* #see http://stackoverflow.com/a/37714700/230513
* #see http://stackoverflow.com/a/37685167/230513
* #see http://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private static final class Content {
private static final double WIDTH = 3;
private final Group group = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private static Content create(double size) {
Content c = new Content(size);
c.group.getChildren().addAll(c.box, c.xAxis, c.yAxis, c.zAxis);
c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
return c;
}
private Content(double size) {
xAxis = createBox(size * 2, WIDTH, WIDTH);
yAxis = createBox(WIDTH, size * 2, WIDTH);
zAxis = createBox(WIDTH, WIDTH, size * 2);
double edge = 3 * size / 4;
box = new Box(edge, edge, edge);
box.setMaterial(new PhongMaterial(Color.CORAL));
box.setTranslateX(size / 2);
box.setTranslateY(-size / 2);
box.setTranslateZ(-size / 2);
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
return b;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
if (e.isShiftDown()) {
content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
} else {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
}
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
//camera.setTranslateX(SIZE / 2);
camera.setTranslateY(-SIZE / 2);
camera.setTranslateZ(-4.5 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It may be easier to have separate groups for the rotatable content versus the fixed triad. In the example below, cube contains a single child, box, and three transforms, rx, ry and rz. In contrast, axes contains three axes with no transforms. The axes are translated to the lower left corner as a group, leaving the mouse handler free to manipulate the Rotate instances belonging to cube.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
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 http://stackoverflow.com/a/37685167/230513
* #see http://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private static final class Content {
private static final double WIDTH = 3;
private final Group group = new Group();
private final Group cube = new Group();
private final Group axes = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private static Content create(double size) {
Content c = new Content(size);
c.cube.getChildren().add(c.box);
c.cube.getTransforms().addAll(c.rz, c.ry, c.rx);
c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
c.axes.setTranslateX(-size / 2);
c.axes.setTranslateY(size / 2);
c.group.getChildren().addAll(c.cube, c.axes);
return c;
}
private Content(double size) {
xAxis = createBox(size*2, WIDTH, WIDTH);
yAxis = createBox(WIDTH, size*2, WIDTH);
zAxis = createBox(WIDTH, WIDTH, size*2);
double edge = 3 * SIZE / 4;
box = new Box(edge, edge, edge);
box.setMaterial(new PhongMaterial(Color.CORAL));
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
return b;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-3 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How to set axis (triad) at fixed position…I also wanted axis rotation in sync with box.
In this example, the Content.create() factory method translates the triad of axes is to the point
P = (-size / 2, size / 2, 0)
Because its separate group has no transforms, the triad's origin "sticks" to that point in the space viewed by the camera.
In contrast, the example below translates the triad of axes is to the point
P = (-size / 2, size / 2, size / 2)
Because the axes are in the same group as box, they undergo the same rotations.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
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/37714700/230513
* #see https://stackoverflow.com/a/37685167/230513
* #see https://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private static final class Content {
private static final double WIDTH = 3;
private final Group group = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private static Content create(double size) {
Content c = new Content(size);
c.group.getChildren().addAll(c.box, c.xAxis, c.yAxis, c.zAxis);
c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
return c;
}
private Content(double size) {
xAxis = createBox(size * 2, WIDTH, WIDTH);
xAxis.setTranslateY(size / 2);
xAxis.setTranslateZ(size / 2);
yAxis = createBox(WIDTH, size * 2, WIDTH);
yAxis.setTranslateX(-size / 2);
yAxis.setTranslateZ(size / 2);
zAxis = createBox(WIDTH, WIDTH, size * 2);
zAxis.setTranslateX(-size / 2);
zAxis.setTranslateY(size / 2);
double edge = 3 * size / 4;
box = new Box(edge, edge, edge);
box.setMaterial(new PhongMaterial(Color.CORAL));
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
return b;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
if (e.isShiftDown()) {
content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
} else {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
}
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-3.5 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I wanted to rotate both main object and axis about their own center.
For that, I would return to the original example that has separate groups for the content and triad. The variation below adds the same threes Rotate transforms to both cube and axes. As a result, the setOnMouseMoved() implementation causes the the two to rotate in synchrony. Mouse over the stage to see the effect.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
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/37743539/230513
* #see https://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private static final class Content {
private static final double WIDTH = 3;
private final Group group = new Group();
private final Group cube = new Group();
private final Group axes = new Group();
private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private static Content create(double size) {
Content c = new Content(size);
c.cube.getChildren().add(c.box);
c.cube.getTransforms().addAll(c.rz, c.ry, c.rx);
c.cube.setTranslateX(size / 2);
c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
c.axes.getTransforms().addAll(c.rz, c.ry, c.rx);
c.axes.setTranslateX(-size / 2);
c.group.getChildren().addAll(c.cube, c.axes);
return c;
}
private Content(double size) {
xAxis = createBox(size, WIDTH, WIDTH);
yAxis = createBox(WIDTH, size, WIDTH);
zAxis = createBox(WIDTH, WIDTH, size);
double edge = 3 * size / 5;
box = new Box(edge, edge, edge);
box.setMaterial(new PhongMaterial(Color.CORAL));
}
private Box createBox(double w, double h, double d) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
return b;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
scene.setOnMouseMoved((final MouseEvent e) -> {
if (e.isShiftDown()) {
content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
} else {
content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
}
});
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-4 * SIZE);
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In order to see what you were seeing, I made these changes to TrafoTest and set the scene to black.
I then modified this example to make the scene look more like yours, adding a sphere on one corner and shifting the axes to the corner opposite. I substituted a variation of your custom rotation Group and your mouse handler from here. The new group also rotates around z when isShiftDown(). Also note the slightly simpler camera dolly in the setOnScroll() handler. In particular, note that the axes can be translated independently as a group. Try this variation in Content.create().
c.axes.setTranslateX(c.axes.getTranslateX() - 20);
c.axes.setTranslateY(c.axes.getTranslateY() + 20);
c.axes.setTranslateZ(c.axes.getTranslateZ() + 20);
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/37755149/230513
* #see https://stackoverflow.com/a/37743539/230513
* #see https://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
private static final class Content {
private static final double WIDTH = 3;
private final Xform group = new Xform();
private final Group cube = new Group();
private final Group axes = new Group();
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private final Sphere sphere;
private static Content create(double size) {
Content c = new Content(size);
c.cube.getChildren().addAll(c.box, c.sphere);
c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
c.group.getChildren().addAll(c.cube, c.axes);
return c;
}
private Content(double size) {
double edge = 3 * size / 4;
xAxis = createBox(edge, WIDTH, WIDTH, edge);
yAxis = createBox(WIDTH, edge / 2, WIDTH, edge);
zAxis = createBox(WIDTH, WIDTH, edge / 4, edge);
box = new Box(edge, edge / 2, edge / 4);
box.setDrawMode(DrawMode.LINE);
sphere = new Sphere(8);
PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.CORAL.darker());
redMaterial.setSpecularColor(Color.CORAL);
sphere.setMaterial(redMaterial);
sphere.setTranslateX(edge / 2);
sphere.setTranslateY(-edge / 4);
sphere.setTranslateZ(-edge / 8);
}
private Box createBox(double w, double h, double d, double edge) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
b.setTranslateX(-edge / 2 + w / 2);
b.setTranslateY(edge / 4 - h / 2);
b.setTranslateZ(edge / 8 - d / 2);
return b;
}
}
private static class Xform extends Group {
private final Point3D px = new Point3D(1.0, 0.0, 0.0);
private final Point3D py = new Point3D(0.0, 1.0, 0.0);
private Rotate r;
private Transform t = new Rotate();
public void rx(double angle) {
r = new Rotate(angle, px);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void ry(double angle) {
r = new Rotate(angle, py);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void rz(double angle) {
r = new Rotate(angle);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-2 * SIZE);
scene.setCamera(camera);
scene.setOnMousePressed((MouseEvent e) -> {
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseOldX = e.getSceneX();
mouseOldY = e.getSceneY();
});
scene.setOnMouseDragged((MouseEvent e) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (e.isShiftDown()) {
content.group.rz(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isPrimaryButtonDown()) {
content.group.rx(+mouseDeltaY * 180.0 / scene.getHeight());
content.group.ry(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isSecondaryButtonDown()) {
camera.setTranslateX(camera.getTranslateX() - mouseDeltaX * 0.1);
camera.setTranslateY(camera.getTranslateY() - mouseDeltaY * 0.1);
camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
}
});
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavafX 8 3D Z Order. Overlapping shape behaviour is wrong.

I have a JavaFX 3D scene with a bunch of boxes and spheres added at random locations. It seems like the depth order is all wrong and I'm not sure why. I have tried to use
myNode.setDepthTest(DepthTest.ENABLE) but that doesn't seem to help. I've attached an application which should demonstrate the problem.
Any idea what I might be doing wrong here? Any help much appreciated.
import javafx.application.Application;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Shape3D;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Array3DTest extends Application {
double mousePosX;
double mousePosY;
double mouseOldX;
double mouseOldY;
double mouseDeltaX;
double mouseDeltaY;
/**
* This is the group which rotates
*/
Group root3D;
/**
* The camnera to
*/
private Rotate rotateY;
private Rotate rotateX;
private Translate translate;
public Array3DTest( ){
}
public Group createScene(){
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(15000);
camera.setNearClip(0.1);
camera.setDepthTest(DepthTest.ENABLE);
camera.getTransforms().addAll(
rotateY=new Rotate(0, Rotate.Y_AXIS),
rotateX=new Rotate(0, Rotate.X_AXIS),
translate=new Translate(250, 250, -1000));
root3D=new Group();
root3D.getChildren().add(camera);
root3D.setDepthTest(DepthTest.ENABLE);
final PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setSpecularColor(Color.ORANGE);
redMaterial.setDiffuseColor(Color.RED);
for (int i=0; i<50; i++){
Shape3D mySphere;
if (i%2==0) mySphere = new Box(100,100, 100);
else mySphere= new Sphere(30);
mySphere.setTranslateX(Math.random()*500);
mySphere.setTranslateY(Math.random()*500);
mySphere.setTranslateZ(Math.random()*200);
mySphere.setMaterial(redMaterial);
mySphere.setDepthTest(DepthTest.ENABLE);
root3D.getChildren().add(mySphere);
}
// Use a SubScene to mix 3D and 3D stuff.
SubScene subScene = new SubScene(root3D, 500,500);
subScene.setFill(Color.WHITE);
subScene.setCamera(camera);
subScene.setDepthTest(DepthTest.ENABLE);
Group group = new Group();
group.getChildren().add(subScene);
handleMouse(subScene);
return group;
}
private void handleMouse(SubScene scene) {
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent me) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
});
scene.setOnScroll(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
System.out.println("Scroll Event: "+event.getDeltaX() + " "+event.getDeltaY());
translate.setZ(translate.getZ()+ event.getDeltaY() *0.001*translate.getZ()); // +
}
});
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()) {
rotateY.setAngle(rotateY.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0); // +
rotateX.setAngle(rotateX.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0); // -
}
if (me.isSecondaryButtonDown()) {
translate.setX(translate.getX() -mouseDeltaX * modifierFactor * modifier * 5);
translate.setY(translate.getY() - mouseDeltaY * modifierFactor * modifier * 5); // +
}
}
});
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println(
"3D supported? " +
Platform.isSupported(ConditionalFeature.SCENE3D)
);
Group group=createScene();
primaryStage.setResizable(false);
Scene scene = new Scene(group, 500,500, true);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Since you are using a SubScene object, it's convenient that you enable deep buffer like you already do for the Scene. Also, enabling anti-aliasing will help too.
According to JavaDoc:
The depthBuffer and antiAliasing flags are conditional features. With the respective default values of: false and SceneAntialiasing.DISABLED.
So just use the other constructor for SubScene:
SubScene subScene = new SubScene(root3D, 500, 500, true, SceneAntialiasing.BALANCED);
and you'll notice the difference.

Moving LinearGradient in a given Shape

Is there a way to animate a "move" of LinearGradient nested into a given Shape? Let's say for example make the LinearGradient move to right or to left? I thought about the translateXproperty or the Path object, but I want to move only the LinearGradient.
EDIT 28.09.2014
Thank you #James_D for the valueable help. But in the context of my project I've needed a different solution. I've worked it out, and the result can be seen below on the working example:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class LinearGradientAnimation extends Application {
private Rectangle rect;
private Timeline timeline;
private double widthOfOneGradientCycle = 20.0;
private double gradientSlopeDegree = 45.0;
private double xStartStatic = 100.0;
private double yStartStatic = 100.0;
private double xEndStatic = xStartStatic + (widthOfOneGradientCycle * Math.cos(Math.toRadians(gradientSlopeDegree)));
private double yEndStatic = yStartStatic + (widthOfOneGradientCycle * Math.sin(Math.toRadians(gradientSlopeDegree)));
public Parent createContent() {
/* layout */
BorderPane layout = new BorderPane();
/* layout -> Rectangle */
rect = new Rectangle(0, 0, 200, 200);
/* layout -> Rectangle -> LinearGradient Animation */
timeline = new Timeline();
for (int i = 0; i < 10; i++) {
int innerIterator = i;
KeyFrame kf = new KeyFrame(Duration.millis(30 * innerIterator), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ae) {
double runningRadius = innerIterator * (widthOfOneGradientCycle / 10);
double xStartDynamic = xStartStatic + (runningRadius * Math.cos(Math.toRadians(gradientSlopeDegree)));
double yStartDynamic = yStartStatic + (runningRadius * Math.sin(Math.toRadians(gradientSlopeDegree)));
double xEndDynamic = xEndStatic + (runningRadius * Math.cos(Math.toRadians(gradientSlopeDegree)));
double yEnddynamic = yEndStatic + (runningRadius * Math.sin(Math.toRadians(gradientSlopeDegree)));
LinearGradient gradient = new LinearGradient(xStartDynamic, yStartDynamic, xEndDynamic, yEnddynamic,
false, CycleMethod.REPEAT, new Stop[] {
new Stop(0.0, Color.WHITE),
new Stop(0.5, Color.BLACK),
new Stop(1.0, Color.WHITE)
});
rect.setFill(gradient);
}
});
timeline.getKeyFrames().add(kf);
}
timeline.setCycleCount(Timeline.INDEFINITE);
/* return layout */
layout.setCenter(rect);
return layout;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(300);
stage.show();
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
https://gist.github.com/bluevoxel/44dcf297ee9503e72114

Categories

Resources