Memory increase in javafx with 3d - javafx

I’m learning javafx to create a scene with 3D objects and I met a problem.
I created a semplificated class to describe the problem.
The Group “allPyr” has a lot of Mesh managed by the variable “numberPyr” ( with 2 “for” cicles). The Timeline variates “numberPyr” continuously from 1 to 100.
Which is the correct way to update the Group “allPyr”?
I tried this way ( and I’m sure is wrong) because the ram of my pc increase proportionally by increasing the number of mesh ( and I can understand it) but then doesn’t decrease when the mesh are few ( the program after some cycles goes out of memory).
As ‘’ dirty’’ solution I call the garbage collector and let clean it in order to free the ram. It does work but I’m sure this is not the correct or best way to do it
Have you some suggestion?
public class MainFrameAnimatoTestRam extends Application{
static int numberPyr=1;
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 600, 600, true);
stage.setScene(scene);
stage.show();
Group allPyr = new Group();
TriangleMesh geomPyr= createTriangleMesh();
root.getChildren().add(allPyr);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), event -> {
if(numberPyr <100)
numberPyr=numberPyr*2;
else
numberPyr=1;
updateAllPyr(geomPyr, allPyr, numberPyr);
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
static void updateAllPyr(TriangleMesh geometria, Group allPyr,int numberPyr) {
allPyr.getChildren().clear();
allPyr.getChildren().removeAll();
//System.gc();
MeshView filtro=null;
int larg,alt=0;
for(int y=0;y<=numberPyr;y++) {
larg=-3000;
for(int x=0;x<=numberPyr;x++) {
filtro=new MeshView(geometria);
filtro.setTranslateX(larg);
filtro.setTranslateY(alt);
allPyr.getChildren().add(filtro);
filtro=null;
larg=x*30;
}
alt=y*30;
}
}
static TriangleMesh createTriangleMesh(){
float h = 20;
float s = 30;
TriangleMesh pyramid_mesh = new TriangleMesh();
pyramid_mesh.getTexCoords().addAll(0, 0);
pyramid_mesh.getTexCoords().addAll(2.0F, 0);
pyramid_mesh.getTexCoords().addAll(1.0F, 0.0F);
pyramid_mesh.getTexCoords().addAll(2.0F, 2.0F);
pyramid_mesh.getTexCoords().addAll(0, 2.0F);
pyramid_mesh.getPoints().addAll(
0, 0, 0, // Point 0 - Top
0, h, -s/2, // Point 1 - Front
-s/2, h, 0, // Point 2 - Left
0, h, s/2, // Point 3 - Back
s/2, h, 0 ); // Point 4 - Right
pyramid_mesh.getFaces().addAll(
// P,T P,T P,T
0,2, 2,4, 1,3, // Front left face
0,2, 1,4, 4,3, // Front right face
0,2, 4,4, 3,3, // Back right face
0,2, 3,4, 2,3, // Back left face
3,1, 1,4, 2,0, // Bottom rear face
3,1, 4,3, 1,4 ); // Bottom front face
pyramid_mesh.getFaceSmoothingGroups().addAll(0,1,2,3,4,4);
return pyramid_mesh;
}
public static void main(String[] args){
Application.launch(args);
}
}

Related

Box 3D JavaFX Rotate

I have two Boxes (Group), and when I rotate, the image displays like this:
Display Boxes
Rotate Boxes
When rotating, the Box (JANELA_MEIO_BOX) is distorted:
public class Demo1 extends Application {
private PhongMaterial texturedMaterial = new PhongMaterial();
private Image texture = new Image("/T3D/mapfooter.JPG");
private final PhongMaterial redMaterial = new PhongMaterial();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(final Stage stage) {
redMaterial.setSpecularColor(Color.ORANGE);
redMaterial.setDiffuseColor(Color.RED);
texturedMaterial.setDiffuseMap(texture);
javafx.scene.shape.Box JANELA_MEIO_BOX = new javafx.scene.shape.Box();
/* rotate */
JANELA_MEIO_BOX.setWidth(600.0);
JANELA_MEIO_BOX.setHeight(340.0);
JANELA_MEIO_BOX.setDepth(100.0);
JANELA_MEIO_BOX.setMaterial(texturedMaterial);
Group JANELA_001 = new Group();
stage.setTitle("Cube");
final CameraView cameraView = new CameraView();
final Scene scene = new Scene(cameraView, 1000, 800, true);
scene.setFill(new RadialGradient(225, 0.85, 300, 300, 500, false,
CycleMethod.NO_CYCLE, new Stop[]{new Stop(0f, Color.BLUE),
new Stop(1f, Color.LIGHTBLUE)}));
PerspectiveCamera camera = new PerspectiveCamera();
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
javafx.scene.shape.Box JAN_MAIN = new javafx.scene.shape.Box();
JAN_MAIN.setMaterial(redMaterial);
JAN_MAIN.setWidth(1000.0);
JAN_MAIN.setHeight(600.0);
JAN_MAIN.setDepth(100.0);
JAN_MAIN.getTransforms().add(new Translate(1, 1, 1));
JANELA_MEIO_BOX.getTransforms().add(new Translate(1, 1, 1));
JANELA_001.getChildren().addAll(JAN_MAIN, JANELA_MEIO_BOX);
cameraView.add(JANELA_001);
/* mouse events */
cameraView.frameCam(stage, scene);
MouseHandler mouseHandler = new MouseHandler(scene, cameraView);
KeyHandler keyHandler = new KeyHandler(stage, scene, cameraView);
/* scene */
stage.setScene(scene);
stage.show();
}
When rotating, the Box (JANELA_MEIO_BOX) is distorted
You have two boxes: a 1000x600x100 cube, and a 600x340x100 cube.
When you put both of them in a group, they are placed in the center: the bigger one goes from -500 to 500 in X, -300 to 300 in Y, -50 to 50 in Z, and the same goes for the smaller one, also in Z from -50 to 50.
When you render two shapes with their faces in the same exact Z coordinate you will always get these artifacts.
One quick solution, if you want to see both shapes, is just making the smaller one a little bit deeper:
JANELA_MEIO_BOX.setDepth(100.1);
And it is also convenient that you set Scene Antialiasing to Balanced:
final Scene scene = new Scene(cameraView, 1000, 800, true, SceneAntialiasing.BALANCED);

JavaFX 3D: label positioning is wrong while rotating the camera around the Z-axis

I am currently writing a program in JavaFX, which visualises a graph in 3D. The user should be able to translate and rotate it.
As the common rule for movement in 3D is, to move the camera instead of the whole graph, I followed this rule.
I created a StackPane, which contains two Panes (bottomPane and topPane). The bottomPane contains the SubScene, in which the graph is displayed and which uses a PerspectiveCamera. In the topPane I put a Group containing the Labels for specific nodes.
I did this setup to prevent labels being hidden by the graph, when their corresponding nodes are behind other nodes or edges.
To get the positioning of the labels right, I bind a function to the translateX/Y/Z properties of the camera, which updates the positions of the labels accordingly (rearrangeLabels()).
This function calculates the screen coordinates of the nodes and then calculates these back to the coordinates of the group containing the labels.
This works perfectly for translation (e.g. moving the graph up/down/left/right). It also works, if I rotate the camera only around the x-axis OR the y-axis.
But when I want to rotate in both axis the same time (e.g. diagonal) the labels are not moving accordingly.
I tried a lot of different approaches and came to the conclusion, that this might be a bug in JavaFX, or more precise in either the localToScreen() or screenToLocal() function.
I attached a demo program, which depicts the mentioned problem.
Just press Start and use your primary mouse button to rotate the camera.
So my question is, does anyone of you know, if this is a known bug? And if yes, is there a workaround? Or am I doing something wrong?
EDIT: I narrowed the error down to being the rotation around the Z-axis. When rotating ONLY around the z-axis, the weird behaviour happens, whereas rotating around either the X-axis or the Y-axis alone, everything moves as expected.
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Cylinder;
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 RotateCamWithLabel extends Application{
Sphere node1;
Sphere node2;
Cylinder edge;
Label nodeLabel1;
Label nodeLabel2;
Group labelGroup;
Group graphGroup;
Pane topPane;
Pane bottomPane;
PerspectiveCamera cam;
double oldPosX;
double oldPosY;
Transform camTransform = new Rotate();
#Override
public void start(Stage primaryStage) throws Exception {
// Setting up the root of the whole Scene
topPane = new Pane();
topPane.setPickOnBounds(false);
bottomPane = new Pane();
StackPane root = new StackPane();
root.getChildren().addAll(bottomPane, topPane);
Button startButton = new Button("Start");
startButton.setOnAction(event -> {
setUpSubSceneAndCam();
addLabels();
});
topPane.getChildren().add(startButton);
// Starting the Scene
Scene scene = new Scene(root, 700, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private void setUpSubSceneAndCam() {
createGraph();
SubScene subScene = new SubScene(graphGroup, 700, 500, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.LIGHTGRAY);
cam = new PerspectiveCamera(true);
cam.setTranslateZ(-1000);
cam.setNearClip(0.1);
cam.setFarClip(Double.MAX_VALUE);
subScene.setCamera(cam);
bottomPane.getChildren().add(subScene);
createCameraLabelBinding();
subScene.setOnMousePressed(event -> {
oldPosX = event.getSceneX();
oldPosY = event.getSceneY();
});
subScene.setOnMouseDragged(event -> {
double deltaX = event.getSceneX() - oldPosX;
double deltaY = event.getSceneY() - oldPosY;
if (event.isPrimaryButtonDown()) {
rotateCamera(deltaX, deltaY);
}
oldPosX = event.getSceneX();
oldPosY = event.getSceneY();
});
}
// Rotation is done by calculating the local camera position in the subscene and out of these,
// the rotation axis is calculated as well as the degree.
// Then the camera is repositioned on the pivot point, turned based on the axis and degree, and put
// back along its local backwards vector based on the starting ditance between the camera and the pivot point
private void rotateCamera(double deltaX, double deltaY) {
// Calculate rotation-axis
Point3D leftVec = getCamLeftVector().multiply(deltaX);
Point3D upVec = getCamUpVector().multiply(deltaY);
Point3D dragVec = leftVec.add(upVec).multiply(-1);
Point3D backVec = getCamBackwardVector();
Point3D axis = dragVec.crossProduct(backVec);
//Point3D axis = Rotate.Z_AXIS; //Does not work
//Point3D axis = Rotate.Y_AXIS; //Works
//Point3D axis = Rotate.X_AXIS; //Works
Rotate r = new Rotate(dragVec.magnitude(), axis);
// set camera on pivot point
Point3D pivot = Point3D.ZERO;
Point3D camCurrent = new Point3D(cam.getTranslateX(), cam.getTranslateY(), cam.getTranslateZ());
Point3D camPivVec = pivot.subtract(camCurrent);
setCameraPosition(pivot);
// rotate camera
camTransform = r.createConcatenation(camTransform);
cam.getTransforms().setAll(camTransform);
// put camera back along the local backwards vector
double length = camPivVec.magnitude();
setCameraPosition(getCamBackwardVector().multiply(length).add(pivot));
}
private void addLabels() {
if (labelGroup != null) {
labelGroup.getChildren().remove(nodeLabel1);
labelGroup.getChildren().remove(nodeLabel2);
}
labelGroup = new Group();
topPane.getChildren().add(labelGroup);
nodeLabel1 = new Label("Hello");
nodeLabel2 = new Label("Bye");
labelGroup.getChildren().addAll(nodeLabel1, nodeLabel2);
rearrangeLabels();
}
private void createCameraLabelBinding() {
cam.translateXProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
cam.translateYProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
cam.translateZProperty().addListener((observable, oldValue, newValue) -> rearrangeLabels());
}
// TODO: Here is probably a bug
// I calculate the screen coordinates of the nodes, then turn them back to local positions
// in the label-group
private void rearrangeLabels() {
Point2D screenCoord1 = node1.localToScreen(Point2D.ZERO);
Point2D screenCoord2 = node2.localToScreen(Point2D.ZERO);
Point2D groupCoord1 = labelGroup.screenToLocal(screenCoord1);
Point2D groupCoord2 = labelGroup.screenToLocal(screenCoord2);
nodeLabel1.setTranslateX(groupCoord1.getX());
nodeLabel1.setTranslateY(groupCoord1.getY());
nodeLabel2.setTranslateX(groupCoord2.getX());
nodeLabel2.setTranslateY(groupCoord2.getY());
}
private void createGraph() {
graphGroup = new Group();
node1 = new Sphere(5);
node2 = new Sphere(5);
node1.setTranslateX(-50);
node1.setTranslateY(-50);
node2.setTranslateX(150);
node2.setTranslateY(50);
edge = new Cylinder(2, 10);
connectNodesWithEdge(new Point3D(node1.getTranslateX(), node1.getTranslateY(), node1.getTranslateZ()),
new Point3D(node2.getTranslateX(), node2.getTranslateY(), node2.getTranslateZ()));
graphGroup.getChildren().addAll(node1, node2, edge);
}
private void connectNodesWithEdge(Point3D origin, Point3D target) {
Point3D yAxis = new Point3D(0, 1, 0);
Point3D diff = target.subtract(origin);
double height = diff.magnitude();
Point3D mid = target.midpoint(origin);
Translate moveToMidpoint = new Translate(mid.getX(), mid.getY(), mid.getZ());
Point3D axisOfRotation = diff.crossProduct(yAxis);
double angle = Math.acos(diff.normalize().dotProduct(yAxis));
Rotate rotateAroundCenter = new Rotate(-Math.toDegrees(angle), axisOfRotation);
this.edge.setHeight(height);
edge.getTransforms().addAll(moveToMidpoint, rotateAroundCenter);
}
private Point3D getCamLeftVector() {
Point3D left = cam.localToScene(-1, 0, 0);
Point3D current = cam.localToScene(0, 0, 0);
return left.subtract(current);
}
private Point3D getCamUpVector() {
Point3D up = cam.localToScene(0, -1, 0);
Point3D current = cam.localToScene(0, 0, 0);
return up.subtract(current);
}
private Point3D getCamBackwardVector() {
Point3D backward = cam.localToScene(0, 0, -1);
Point3D current = cam.localToScene(0, 0, 0);
return backward.subtract(current);
}
private void setCameraPosition(Point3D position) {
cam.setTranslateX(position.getX());
cam.setTranslateY(position.getY());
cam.setTranslateZ(position.getZ());
}
public static void main(String[] args) {
launch(args);
}
}

Change postion of a line to make a gauge javafx

I want to make half radial gauge. I've tried to put an arc of 180° and I put a line on it, and I tried to move this line to use this as an indicator of position.
I tried to move the line to different position in Scene Builder and it appears that it could works but when I tried to move with code it doesn't.
private Scanner sc;
public void deplacement_aiguille(){
sc = new Scanner(System.in);
double angle = sc.nextDouble();
String valangle = Double.toString(angle);
valeur_gisement.setText(valangle);
float xfin = (float) ((Math.cos(angle))*200);
float yfin = (float) ((Math.sin(angle)*200));
if(angle<90){
gisement.relocate(5,10);
gisement.setEndX(xfin);
gisement.setEndY(-yfin);
}
if (angle>90){
gisement.relocate(6,10);
gisement.setEndX(xfin);
gisement.setEndY(yfin);
}
if(angle==90){
gisement.setEndX(0);
gisement.setEndY(200);
}
if(angle==180){
gisement.setEndX(200);
gisement.setEndY(0);
}
if(angle==0){
gisement.relocate(5,10);
gisement.setEndX(-200);
gisement.setEndY(0);
}
}
With this code I want to move the line in my gridpane to the column number 5 and row number 10 when my angle is smaller than 90 and to the column number 6 and row 10 when angle is bigger than 90
I'm not sure if it's the good way to do it. So if you have any better idea I'll take it. But if it's the good way to do it would you help me to make it works.
Thanks
I'd put the Arc and the Line in a Pane (for absolute positioning) and use a Rotate transform to move the line.
IMO More important is to bind the Rotate to the input value. In my example I use a Slider for it:
public class Gauge extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Arc arc = new Arc();
arc.setCenterX(100);
arc.setCenterY(0);
arc.setRadiusX(100);
arc.setRadiusY(100);
arc.setStartAngle(0);
arc.setLength(180);
arc.relocate(0, 0);
double lineCenterX = 100d;
double lineCenterY = 100d;
Line line = new Line(lineCenterX, lineCenterY, 0, 100);
line.setStroke(Color.YELLOW);
Rotate rotate = new Rotate();
rotate.setPivotX(lineCenterX);
rotate.setPivotY(lineCenterY);
line.getTransforms().add(rotate);
Pane pane = new Pane();
pane.getChildren().addAll(arc, line);
Slider slider = new Slider();
rotate.angleProperty().bind(slider.valueProperty().multiply(1.8));
VBox vbox = new VBox(10);
vbox.getChildren().addAll(pane, slider);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to play pathtransitions in sequence?

Is there a special way (some kind of event/transition queue?) in JavaFX with which you can play animations/transitions in sequence?
In a solitaire game at the end the cards are usually already in sequence (K->-Q->J-10->-9->...) and I want to finish them automatically. So first card 9 has to move from the tableau to the foundation, then 10, then J, etc.
I'd like to do the path transition in a transition queue, rather than all at once.
I can't use a SequentialTranstition because you can't modify its children once the animation plays.
public final ObservableList getChildren()
A list of Animations that will be played sequentially.
It is not possible to change the children of a running
SequentialTransition. If the children are changed for a running
SequentialTransition, the animation has to be stopped and started
again to pick up the new value.
Verifiable with this minimal example:
public class PathTransitionDemo extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
List<Rectangle> rectangles = new ArrayList<>();
double w = 50;
double h = 50;
double spacing = 10;
for( int i=0; i < 10; i++) {
Rectangle rectangle = new Rectangle ( spacing * (i+1) + w * i, 50, w, h);
rectangle.setStroke( Color.BLUE);
rectangle.setFill( Color.BLUE.deriveColor(1, 1, 1, 0.2));
rectangles.add( rectangle);
}
root.getChildren().addAll( rectangles);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// transition
SequentialTransition seq = new SequentialTransition();
for( Rectangle rectangle: rectangles) {
Path path = new Path();
path.getElements().add (new MoveTo ( rectangle.getX(), rectangle.getY()));
path.getElements().add (new LineTo( 900, 600));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setNode(rectangle);
pathTransition.setPath(path);
//pathTransition.play();
seq.getChildren().add( pathTransition);
if( seq.getStatus() != Status.RUNNING)
seq.play();
}
// seq.play();
}
}
The sequential transition stops after it played the first path transition.
A solution would be to modfiy the setOnFinished handler of the PathTransitions. But that's ugly. And it plays only exaclty 1 transtition after the other. Let's say one card transition lasts 2000 ms, it e. g. limits the possibility to start another transition of the queue within 500ms.
The animation in the Video Wall that I created has a similar requirement. There I solved it with an AnimationTimer. Excerpt from the code:
AnimationTimer timer = new AnimationTimer() {
long last = 0;
#Override
public void handle(long now) {
if( (now - last) > 40_000_000)
{
if( transitionList.size() > 0) {
ParallelTransition t = transitionList.remove(0);
t.getNode().setVisible(true);
t.play();
}
last = now;
}
if( transitionList.size() == 0) {
stop();
}
}
};
Is there a better way to solve this in JavaFX?
Thank you very much!

How can I draw a circle to the screen with PlayN?

This is about as basic as it gets. I don't want to use an image file. Rather, I want to programmatically draw a circle and blit it to a surface (as they say in pygame).
I tried to follow the "Using CanvasLayers" example here:
https://developers.google.com/playn/devguide/rendering
From my game class:
// Surface
SurfaceLayer surface;
// Background
int width = 640;
int height = 480;
//ImageLayer bgLayer;
CanvasImage bgImage;
Canvas canvas;
// Circle
CanvasImage circleImage;
//ImageLayer circleLayer;
int circleRadius = 20;
int circleX = 0;
int circleY = 0;
#Override
public void init() {
// create a surface
surface = graphics().createSurfaceLayer(width, height);
graphics().rootLayer().add(surface);
// create a solid background
// http://code.google.com/p/playn101/source/browse/core/src/main/java/playn101/core/J.java#81
bgImage = graphics().createImage(width, height);
canvas = bgImage.canvas();
canvas.setFillColor(0xff87ceeb);
canvas.fillRect(0, 0, width, height);
//bgLayer = graphics().createImageLayer(bgImage);
//graphics().rootLayer().add(bgLayer);
// create a circle
circleImage = graphics().createImage(circleRadius, circleRadius);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circleX, circleY, circleRadius);
//circleLayer = graphics().createImageLayer(circleImage);
//graphics().rootLayer().add(circleLayer);
}
#Override
public void paint(float alpha) {
// the background automatically paints itself, so no need to do anything
// here!
surface.clear(0);
surface.drawImage(bgImage, 0, 0);
surface.drawImage(circleImage, 100, 100);
}
But I get a blank window in Java and Eclipse complains:
The method drawImage(CanvasImage, int, int) is undefined for the type
SurfaceLayer
That, however, is the way it is used in the example at the link.
If the code you provided even compiles, then you are using some very old version of PlayN.
Update to PlayN 1.1.1 and fix the compilation errors that result, and your code will work fine.
The following is your code updated to work with PlayN 1.1.1:
private SurfaceLayer surface;
private CanvasImage bgImage;
private CanvasImage circleImage;
#Override
public void init() {
// create a surface
int width = graphics().width(), height = graphics().height();
surface = graphics().createSurfaceLayer(width, height);
graphics().rootLayer().add(surface);
// create a solid background
bgImage = graphics().createImage(width, height);
Canvas canvas = bgImage.canvas();
canvas.setFillColor(0xff87ceeb);
canvas.fillRect(0, 0, width, height);
// create a circle
int circleRadius = 20;
int circleX = 0;
int circleY = 0;
circleImage = graphics().createImage(circleRadius, circleRadius);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circleX, circleY, circleRadius);
}
#Override
public void paint(float alpha) {
Surface s = surface.surface();
s.clear();
s.drawImage(bgImage, 0, 0);
s.drawImage(circleImage, 100, 100);
}
If you really intend to make a game using this approach, you should use ImmediateLayer not SurfaceLayer. ImmediateLayer will issue your drawImage, etc. calls directly against the framebuffer. SurfaceLayer will make an off-screen framebuffer and render everything into that and then copy that off-screen framebuffer to the main framebuffer every frame (in addition to the double buffering naturally performed by OpenGL, etc.), resulting in a needless copy of your entire screen.
Your code looks fine to me...
set the canvasTransform,
canvas.setTransform(1, 0, 0, 1, 0, 0);
and please for testing purposes make the bg bigger
graphics().createImage(circleRadius, circleRadius); (400,400)

Resources