I tried to create a spring like behavior with JavaFX by creating draggable circles. When I drag one circle, the others should follow and simulate elasticity.
I created a boilerplate template which contains 3 circles, they are draggable via mouse. The animation is running, of course all appears still because currently speed is 0. All I need to know is how to calculate the angle and the speed of the attached particles.
It would be great if someone could help me.
Here's the code:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class PhysicsTest extends Application {
List<Particle> particles = new ArrayList<>();
List<Spring> springs = new ArrayList<>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// create particles
Particle pRed = new Particle(Color.RED, 100, 100);
Particle pBlue = new Particle(Color.BLUE, 400, 200);
Particle pGreen = new Particle(Color.GREEN, 100, 300);
// red -> blue
Line lineRedBlue = new Line(100, 100, 500, 500);
lineRedBlue.setStroke(Color.BLACK);
lineRedBlue.setStrokeWidth(5);
// green -> blue
Line lineGreenBlue = new Line(100, 100, 500, 500);
lineGreenBlue.setStroke(Color.BLACK);
lineGreenBlue.setStrokeWidth(5);
// line binding
// line 1 -> 2
lineRedBlue.startXProperty().bind(pRed.centerXProperty());
lineRedBlue.startYProperty().bind(pRed.centerYProperty());
lineRedBlue.endXProperty().bind(pBlue.centerXProperty());
lineRedBlue.endYProperty().bind(pBlue.centerYProperty());
// line 3 -> 2
lineGreenBlue.startXProperty().bind(pGreen.centerXProperty());
lineGreenBlue.startYProperty().bind(pGreen.centerYProperty());
lineGreenBlue.endXProperty().bind(pBlue.centerXProperty());
lineGreenBlue.endYProperty().bind(pBlue.centerYProperty());
MouseGestures mg = new MouseGestures();
mg.makeDraggable(pRed);
mg.makeDraggable(pBlue);
mg.makeDraggable(pGreen);
root.getChildren().addAll(pRed, pBlue, pGreen, lineRedBlue, lineGreenBlue);
// add to list
particles.add( pRed);
particles.add( pBlue);
particles.add( pGreen);
// add springs
Spring s1 = new Spring( pRed, pBlue, 10, 0.5);
springs.add( s1);
Spring s2 = new Spring( pGreen, pBlue, 10, 0.5);
springs.add( s2);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// animate
startAnimation();
}
private void startAnimation() {
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
// move particles
for (Particle p : particles) {
if (!p.selected) {
p.move();
}
}
// apply springs
for (Spring s : springs) {
s.update();
}
// move particles to new location
for (Particle p : particles) {
p.updateLocation();
}
}
};
timer.start();
}
/**
* The spring constraint and calculation. Updates particle
*/
public class Spring {
Particle p1;
Particle p2;
double length; // length it tries to obtain
double strength; // how quickly it tries to reach that length
public Spring( Particle p1, Particle p2, double length, double strength) {
this.p1 = p1;
this.p2 = p2;
this.length = length;
this.strength = strength;
}
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
// what's supposed to happen here?
p1.angle = ... // <===
p1.speed = ... // <===
p2.angle = ... // <===
p2.speed = ... // <===
}
}
/**
* The particle itself
*/
public class Particle extends Circle {
double x;
double y;
double angle = 0.0;
double speed = 0.0;
double mass = 1;
boolean selected = false;
public Particle(Color color, double x, double y) {
super(x, y, 50);
this.x = x;
this.y = y;
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
}
public void move() {
x += Math.sin( angle) * speed;
y += Math.cos( angle) * speed;
}
public void updateLocation() {
setCenterX( x);
setCenterY( y);
}
}
/**
* Allow movement of objects via mouse.
*/
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable( Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
node.setOnMouseReleased(circleOnMouseReleasedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Particle p = ((Particle) (t.getSource()));
p.selected = true;
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
Particle p = ((Particle) (t.getSource()));
p.selected = false;
};
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Particle p = ((Particle) (t.getSource()));
p.x = newTranslateX;
p.y = newTranslateY;
}
};
}
}
The update() method in the Spring class is the unknown territory:
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
// what's supposed to happen here?
p1.angle = ... // <===
p1.speed = ... // <===
p2.angle = ... // <===
p2.speed = ... // <===
}
Here's a screenshot of how it looks like currently:
Thank you very much!
Ok, adding a damping and a non compressable spring:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class SpringField extends Application {
MouseGestures mg = new MouseGestures();
double damping = 0.995;
double speedo = 0.001;
List<Particle> particles = new ArrayList<>();
List<Spring> springs = new ArrayList<>();
public static void main(String[] args) {
launch(args);
}
Particle addParticle(Group parent, Paint p, double x, double y, double mass) {
Particle particle = new Particle(p, x, y, mass);
mg.makeDraggable(particle);
particles.add(particle);
parent.getChildren().add(particle);
return particle;
}
void addSpring(Group parent, Particle p1, Particle p2, double length, double strength) {
Spring spring = new Spring(parent, p1, p2, length, strength);
springs.add(spring);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
// create particles
Particle pRed = addParticle(root, Color.RED, 300, 100, 10);
Particle pBlue = addParticle(root, Color.BLUE, 600, 200, 1);
Particle pGreen = addParticle(root, Color.GREEN, 300, 300, 1);
// add springs
addSpring(root, pRed, pBlue, 100, 0.5);
addSpring(root, pGreen, pBlue, 100, 0.5);
addSpring(root, pGreen, pRed, 100, 0.5);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.show();
// animate
startAnimation();
}
private void startAnimation() {
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
// move particles
for (Particle p : particles) {
if (!p.selected) {
p.move();
}
}
// apply springs
for (Spring s : springs) {
s.update();
}
// move particles to new location
for (Particle p : particles) {
p.updateLocation();
}
}
};
timer.start();
}
/**
* The spring constraint and calculation. Updates particle
*/
public class Spring {
Particle p1;
Particle p2;
double length; // length it tries to obtain
double strength; // how quickly it tries to reach that length
public Spring(Group parent, Particle p1, Particle p2, double length, double strength) {
this.p1 = p1;
this.p2 = p2;
this.length = length;
this.strength = strength;
Line lineRedBlue = new Line(100, 100, 500, 500);
lineRedBlue.setStroke(Color.BLACK);
lineRedBlue.setStrokeWidth(5);
lineRedBlue.startXProperty().bind(p1.centerXProperty());
lineRedBlue.startYProperty().bind(p1.centerYProperty());
lineRedBlue.endXProperty().bind(p2.centerXProperty());
lineRedBlue.endYProperty().bind(p2.centerYProperty());
parent.getChildren().add(lineRedBlue);
}
public void update() {
double stop = 1.0;
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
if (force > 0) { force *= 4; stop = 0.9; }
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
Point2D p1v = new Point2D(force*Math.cos(theta)*speedo/p1.mass, force*Math.sin(theta)*speedo/p1.mass);
Point2D p2v = new Point2D(-force*Math.cos(theta)*speedo/p2.mass, -force*Math.sin(theta)*speedo/p2.mass);
p1.vector = p1.vector.add(p1v).multiply(stop);
p2.vector = p2.vector.add(p2v).multiply(stop);
}
}
/**
* The particle itself
*/
public class Particle extends Circle {
double x;
double y;
Point2D vector = new Point2D(0, 0);
double mass = 1;
boolean selected = false;
public Particle(Paint color, double x, double y, double mass) {
super(x, y, 50);
this.x = x;
this.y = y;
this.mass = mass;
setFill(color);
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
}
public void move() {
x += vector.getX();
y += vector.getY();
vector = vector.multiply(damping);
}
public void updateLocation() {
setCenterX( x);
setCenterY( y);
}
}
/**
* Allow movement of objects via mouse.
*/
public class MouseGestures {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public void makeDraggable( Node node) {
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
node.setOnMouseReleased(circleOnMouseReleasedEventHandler);
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
Particle p = ((Particle) (t.getSource()));
p.selected = true;
orgTranslateX = p.getCenterX();
orgTranslateY = p.getCenterY();
}
};
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
Particle p = ((Particle) (t.getSource()));
p.selected = false;
};
};
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
Particle p = ((Particle) (t.getSource()));
p.x = newTranslateX;
p.y = newTranslateY;
}
};
}
}
First I would propose to not use angle and speed in your Particle, but Point2D as a movement vector:
public class Particle extends Circle {
double x;
double y;
Point2D vector = new Point2D(0, 0);
that simplifies the later update calulation...
Then you might programm update as follows:
public void update() {
double dx = p1.getCenterX() - p2.getCenterX();
double dy = p1.getCenterY() - p2.getCenterY();
double dist = Math.hypot(dx, dy);
double theta = Math.atan2(dy, dx);
double force = (length - dist) * strength;
// System.out.println( dist + ", " + Math.toDegrees( theta) + ", " + force);
Point2D p1v = new Point2D(force*Math.cos(theta)/p1.mass/10000, force*Math.sin(theta)/p1.mass/10000);
Point2D p2v = new Point2D(-force*Math.cos(theta)/p2.mass/10000, -force*Math.sin(theta)/p2.mass/10000);
p1.vector = p1.vector.add(p1v);
p2.vector = p2.vector.add(p2v);
}
That ignores any collisions, but will provide a fair physics model.
Well here was my approach in a 3D cloth simulation using Verlet Integration:
You can View it here: https://www.youtube.com/watch?v=uRsCcpbsdsg
/**
*
* #author Jason Pollastrini aka jdub1581
*/
#FunctionalInterface
public interface Constraint {
public void solve();
public default void solve(int iter){
IntStream.range(0, iter).parallel().forEach(i->{solve();});
}
}
Then in implementing class (I called it PointLink):
public void solve() {
// calculate the distance between the two PointMasss
Point3D diff = new Point3D(
p1.getPosition().getX() - p2.getPosition().getX(),
p1.getPosition().getY() - p2.getPosition().getY(),
p1.getPosition().getZ() - p2.getPosition().getZ()
);
double d = diff.magnitude();
double difference = (distance - d) / d;
double im1 = 1 / p1.getMass();
double im2 = 1 / p2.getMass();
double scalarP1 = (im1 / (im1 + im2)) * stiffness;
double scalarP2 = stiffness - scalarP1;
p1.position.x = (float) (p1.getPosition().x + diff.x * scalarP1 * difference);
p1.position.y = (float) (p1.getPosition().y + diff.y * scalarP1 * difference);
p1.position.z = (float) (p1.getPosition().z + diff.z * scalarP1 * difference);
p2.position.x = (float) (p2.getPosition().x - diff.x * scalarP2 * difference);
p2.position.y = (float) (p2.getPosition().y - diff.y * scalarP2 * difference);
p2.position.z = (float) (p2.getPosition().z - diff.z * scalarP2 * difference);
}
Then in the Main class something like this:
public void solveConstraints() {
constraints.values().parallelStream().forEach((Constraint c) -> {
c.solve();
});
}
public void updatePhysics(double dt, double t) {
if (isAnchored()) {
setPosition(getAnchorPosition());
return;
}
Point3D vel = new Point3D(
(position.x - oldPosition.x),
(position.y - oldPosition.y),
(position.z - oldPosition.z)
);
float dtSq = (float) (dt * dt);
// calculate the next position using Verlet Integration
Point3D next = new Point3D(
position.x + vel.x + (((force.x / (float) (mass)) * 0.5f) * dtSq),
position.y + vel.y + (((force.y / (float) (mass)) * 0.5f) * dtSq),
position.z + vel.z + (((force.z / (float) (mass)) * 0.5f) * dtSq)
);
// reset variables
setOldPosition(position);
setPosition(next);
setForce(new Point3D(0, 0, 0));
}
Note that I used a simple class with double x,y,z as public members for ease of use for position variable...
Related
I have a TriangleMesh with a texture/diffuse map that is a 1024x1024 texture which is fully black except the last 3 lines which are filled with red, green and blue.
I gave each vertex of each triangle a constant V value (either 1021, 1022 or 1023) added 0.5 to it to center and divided it by the texture's height so it would only use one of the 3 colors and arbitrary U values.
// arbitrary u values.
float u1 = 0.1f;
float u2 = 0.3f;
float u3 = 0.9f;
int randomY = ThreadLocalRandom.current()
.nextInt((int) atlas.getHeight() - 3, (int) atlas.getHeight());
float y = randomY + 0.5f;
float v = (float) (y / atlas.getHeight());
int texIndex1 = mesh.addUV(u1, v);
int texIndex2 = mesh.addUV(u2, v);
int texIndex3 = mesh.addUV(u3, v);
mesh.getFaces().addAll(
vertexIndex1, texIndex1,
vertexIndex2, texIndex2,
vertexIndex3, texIndex3
);
The addUV method looks like this(i have my own mesh class that extends TriangleMesh which contains some helper methods)
public int addUV(float u, float v) {
int cur = getTexCoords().size() / 2;
getTexCoords().addAll(u, v);
return cur;
}
The expected result is a mesh that has it's triangles colored solid red, green and blue because V is constant and each line(y) is filled with a single color however what i got instead was a bunch of different colors that change as u zoom in/out.
If i use the same U value for each vertex as well, it does give the correct result but i don't understand why it wouldn't do the same with arbitrary U values given that the color at any given U is the exact same.
The current result(gif to show the color changing): https://i.imgur.com/4lTcLfH.gif | As seen it actually does show the correct colors but only if u zoom in a lot
The expected result(can be produced if i have constant U values as well like 0.5, 0.5, 0.5): https://i.imgur.com/x35u6xv.gif | Looks as it should, doesn't change when u zoom in/out
The texture i used as the diffuse map: https://i.imgur.com/BB6P7z6.png
Minimal reproducible example with a quad made of 2 triangles:
Create a main method (either in the same class or another) and add: Application.launch(TextureMappingIssue.class); i didn't add it in my example as depending on the setup, the main method must be in a different class
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.nio.file.Path;
/**
* Note that when the application is first opened without
* the camera moved, it looks as it should, as soon as
* the camera is moved (i.e if the mouse is moved in this case)
* it looks completely different, even if the camera is moved slightly
*/
public class TextureMappingIssue extends Application {
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private AnchorPane modelPane;
private Group scene;
private SubScene subScene;
#Override
public void start(Stage primaryStage) throws Exception {
modelPane = new AnchorPane();
modelPane.setPrefWidth(WIDTH);
modelPane.setPrefHeight(HEIGHT);
initScene();
// smaller palette = looks correct until u zoom out more
int paletteWidth = 1024;
int paletteHeight = 1024;
/*
* amount of copies for red, green, blue(the colors at the bottom), the center one is picked
* note that copies = 1 just writes the original color, copies = 2 writes the original + 1 copy and so on (so with copies = 3, it writes the color 3 times and picks the 2nd one for v)
*/
int copies = 1;
float QUAD_SCALE = 1f;
float[] vertices = {
-QUAD_SCALE, -QUAD_SCALE, 0,
-QUAD_SCALE, QUAD_SCALE, 0,
QUAD_SCALE, QUAD_SCALE, 0,
QUAD_SCALE, -QUAD_SCALE, 0
};
int[] indices = {
0, 0, 1, 1, 2, 2, // first triangle
0, 3, 2, 4, 3, 5, // second triangle
};
// set these to 0f, 0f, 0f (or any value as long as they're identical to get the expected result)
float u1 = 0.1f;
float u2 = 0.3f;
float u3 = 0.5f;
int colorIndex = 1; // either 0, 1 or 2 (red, green, blue)
int offset = (3 - colorIndex) * copies;
// v is constant for each vertex in my actual application as well.
float v1 = (paletteHeight - offset + (copies / 2) + 0.5f) / paletteHeight;
float[] texCoords = {
u1, v1, u2, v1, u3, v1
};
Image palette = generatePalette(paletteWidth, paletteHeight, copies);
ImageIO.write(SwingFXUtils.fromFXImage(palette, null), "png", Path.of("./testpalette.png")
.toFile());
TriangleMesh triangle = new TriangleMesh();
triangle.getPoints().addAll(vertices);
triangle.getFaces().addAll(indices);
triangle.getTexCoords().addAll(texCoords);
triangle.getTexCoords().addAll(texCoords);
MeshView view = new MeshView(triangle);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(palette);
//material.setSpecularMap(specular);
// material.setSpecularPower(32); // default
view.setMaterial(material);
scene.getChildren().add(view);
Scene scene = new Scene(modelPane, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
private void initScene() {
scene = new Group();
//Group grid = new Grid3D().create(48f, 1.25f);
//scene.getChildren().add(grid);
subScene = createScene3D();
scene.getChildren().add(new AmbientLight(Color.WHITE));
modelPane.getChildren().addAll(subScene);
}
private SubScene createScene3D() {
SubScene scene3d = new SubScene(scene, modelPane.getPrefWidth(), modelPane.getPrefHeight(), true, SceneAntialiasing.BALANCED);
scene3d.setFill(Color.rgb(25, 25, 25));
new OrbitCamera(scene3d, scene);
return scene3d;
}
private Image generatePalette(int width, int height, int copies) {
WritableImage palette = new WritableImage(width, height);
Color[] debugColors = {Color.RED, Color.GREEN, Color.BLUE};
PixelWriter writer = palette.getPixelWriter();
int offset = height - (debugColors.length * copies);
for (int y = 0; y < offset; y++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y, Color.BLACK);
}
}
int colorOff = 0;
for (int y = offset; y < height - (copies - 1); y += copies) {
Color c = debugColors[colorOff];
if (c == Color.GREEN) {
System.out.println("Y = " + y);
}
for (int k = 0; k < copies; k++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y + k, c);
}
}
colorOff++;
}
return palette;
}
private Image generateSpecular(int width, int height) {
WritableImage specular = new WritableImage(width, height);
PixelWriter writer = specular.getPixelWriter();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
writer.setColor(x, y, Color.WHITE);
}
}
return specular;
}
/*
* Orbit camera
*/
private static class OrbitCamera {
private final SubScene subScene;
private final Group root3D;
private final double MAX_ZOOM = 300.0;
public OrbitCamera(SubScene subScene, Group root) {
this.subScene = subScene;
this.root3D = root;
init();
}
private void init() {
camera.setNearClip(0.1D);
camera.setFarClip(MAX_ZOOM * 1.15D);
camera.getTransforms().addAll(
yUpRotate,
cameraPosition,
cameraLookXRotate,
cameraLookZRotate
);
Group rotateGroup = new Group();
try {
rotateGroup.getChildren().addAll(cameraXform);
} catch (Exception e) {
e.printStackTrace();
}
cameraXform.ry.setAngle(0);
cameraXform.rx.setAngle(-18);
cameraXform.getChildren().add(cameraXform2);
cameraXform2.getChildren().add(cameraXform3);
cameraXform3.getChildren().add(camera);
cameraPosition.setZ(-cameraDistance);
root3D.getChildren().addAll(rotateGroup);
subScene.setCamera(camera);
subScene.setOnScroll(event -> {
double zoomFactor = 1.05;
double deltaY = event.getDeltaY();
if (deltaY < 0) {
zoomFactor = 2.0 - zoomFactor;
}
double z = cameraPosition.getZ() / zoomFactor;
z = Math.max(z, -MAX_ZOOM);
z = Math.min(z, 10.0);
cameraPosition.setZ(z);
});
subScene.setOnMousePressed(event -> {
if (!event.isAltDown()) {
dragStartX = event.getSceneX();
dragStartY = event.getSceneY();
dragStartRotateX = cameraXRotate.getAngle();
dragStartRotateY = cameraYRotate.getAngle();
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseOldX = event.getSceneX();
mouseOldY = event.getSceneY();
}
});
subScene.setOnMouseDragged(event -> {
if (!event.isAltDown()) {
double modifier = 1.0;
double modifierFactor = 0.3;
if (event.isControlDown()) modifier = 0.1;
if (event.isSecondaryButtonDown()) modifier = 0.035;
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
mouseDeltaX = mousePosX - mouseOldX;
mouseDeltaY = mousePosY - mouseOldY;
double flip = -1.0;
if (event.isSecondaryButtonDown()) {
double newX = cameraXform2.t.getX() + flip * mouseDeltaX * modifierFactor * modifier * 2.0;
double newY = cameraXform2.t.getY() + 1.0 * -mouseDeltaY * modifierFactor * modifier * 2.0;
cameraXform2.t.setX(newX);
cameraXform2.t.setY(newY);
} else if (event.isPrimaryButtonDown()) {
double yAngle = cameraXform.ry.getAngle() - 1.0 * -mouseDeltaX * modifierFactor * modifier * 2.0;
double xAngle = cameraXform.rx.getAngle() + flip * mouseDeltaY * modifierFactor * modifier * 2.0;
cameraXform.ry.setAngle(yAngle);
cameraXform.rx.setAngle(xAngle);
}
}
});
}
private final PerspectiveCamera camera = new PerspectiveCamera(true);
private final Rotate cameraXRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
private final Rotate cameraYRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.Y_AXIS);
private final Rotate cameraLookXRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
private final Rotate cameraLookZRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.Z_AXIS);
private final Translate cameraPosition = new Translate(0.0, 0.0, 0.0);
private Xform cameraXform = new Xform();
private Xform cameraXform2 = new Xform();
private Xform cameraXform3 = new Xform();
private double cameraDistance = 25.0;
private double dragStartX = 0;
private double dragStartY = 0;
private double dragStartRotateX = 0;
private double dragStartRotateY = 0;
private double mousePosX = 0;
private double mousePosY = 0;
private double mouseOldX = 0;
private double mouseOldY = 0;
private double mouseDeltaX = 0;
private double mouseDeltaY = 0;
private Rotate yUpRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS);
public Camera getCamera() {
return camera;
}
public Xform getCameraXform() {
return cameraXform;
}
}
private static class Xform extends Group {
Translate t = new Translate();
Translate p = new Translate();
public Rotate rx = new Rotate();
public Rotate ry = new Rotate();
Rotate rz = new Rotate();
Scale s = new Scale();
public Xform() {
rx.setAxis(Rotate.X_AXIS);
ry.setAxis(Rotate.Y_AXIS);
rz.setAxis(Rotate.Z_AXIS);
getTransforms().addAll(t, rz, ry, rx, s);
}
}
}
Edit: updated the code to support generating multiple copies of a single color and picking the center one, however this doesn't solve the issue either, it's just less visible :/
Update: the issue can be reproduced even with a 128x3 image (where it's just the red, green, blue color with 128 pixel rows)
Update 2: I can reproduce the same issue in my original code even with my original palette (that is a 128x512 image of colors that are all potentially used)
Update 3: I have decided to go for per pixel shading instead (i.e provide my mesh with a set of normals and add light sources to the scene(other than ambient)) what i wanted to do initially with the palette was to export all the vertex colors generated from a function that emulates gouraud shading but because of these interpolation issues i have went for per pixel shading (which looks better anyway, altho ideally i would've wanted to emulate gouraud shading as the game engine i use for which the javafx program is for also uses gouraud shading)
I want to draw millimeter paper into a pdf, but when I measure the printed document I'm a bit off (drawn cm < 1 cm). I'm using the size of an A4 Paper (210 * 297) and the pageWidth and pageHeight to calculate the pixel per mm (used the average of both hoping this would work). I also tried different option when printing the document (with & without margin etc.), but this didn't work as well.
public class TestPrint extends Application {
protected static final float DINA4_IN_MM_WIDTH = 210;
protected static final float DINA4_IN_MM_HEIGHT = 297;
protected static final int LEFTSIDE = 35;
protected static final int TEXT_FONT_SIZE = 11;
protected static final int TOP_MARGIN = 60;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
File file = new File("test.pdf");
double windowWidth = 600;
double windowHeight = 1000;
float x = LEFTSIDE;
PDFont font = PDType1Font.TIMES_ROMAN;
PDPage page = new PDPage(PDRectangle.A4);
PDRectangle pageSize = page.getMediaBox();
PDDocument mainDocument = new PDDocument();
mainDocument.addPage(page);
float stringHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() * TEXT_FONT_SIZE;
float y = pageSize.getHeight() - stringHeight / 1000f - TOP_MARGIN;
float pixelPerMM = (pageSize.getWidth() / DINA4_IN_MM_WIDTH + pageSize.getHeight() / DINA4_IN_MM_HEIGHT) / 2;
float displayW = 520;
float displayH = 300;
try {
PDPageContentStream contents = new PDPageContentStream(mainDocument, page, AppendMode.APPEND, true);
drawBackgroundRaster(contents, x, y, displayW, displayH, pixelPerMM);
contents.close();
mainDocument.save(file);
ImageView imgView = getImageViewFromDocument(mainDocument, windowHeight);
VBox vBox = new VBox(imgView);
Scene scene = new Scene(vBox, windowWidth, windowHeight);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
// draw millimeter paper with dots, two boxes shall be 1cm
private void drawBackgroundRaster(PDPageContentStream contents, float x, float y, float displayW, float displayH,
float pixelPerMM) throws IOException {
// rasterColor = grey
Color rasterColor = new Color(175, 175, 175);
contents.setStrokingColor(rasterColor);
float dotSize = 0.5f;
// draw vertical lines
for (int i = 0; i <= displayW; i++) {
float xPos = x + i * pixelPerMM;
if (xPos > displayW + x) {
break;
}
contents.moveTo(xPos, y);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(xPos, y - displayH);
}
contents.stroke();
}
// draw dots and horizontal lines
for (int i = 0; i <= displayH; i++) {
float yPos = y - i * pixelPerMM;
if (yPos < y - displayH) {
break;
}
contents.moveTo(x, yPos);
if (i % 5 == 0) {
contents.setLineDashPattern(new float[] {}, 0);
contents.lineTo(x + displayW, yPos);
} else {
contents.setLineDashPattern(new float[] { dotSize, pixelPerMM - dotSize }, dotSize / 2);
contents.lineTo(x + displayW, yPos);
}
contents.stroke();
}
contents.setLineDashPattern(new float[] {}, 0);
contents.moveTo(x, y);
contents.lineTo(x + displayW, y);
contents.lineTo(x + displayW, y - displayH);
contents.lineTo(x, y - displayH);
contents.lineTo(x, y);
contents.stroke();
}
private ImageView getImageViewFromDocument(PDDocument mainDocument, double windowHeight) throws IOException {
PDFRenderer pdfRenderer = new PDFRenderer(mainDocument);
BufferedImage bim = pdfRenderer.renderImageWithDPI(0, 150, ImageType.RGB);
Image image = SwingFXUtils.toFXImage(bim, null);
ImageView imageView = new ImageView(image);
double scaleFactor = windowHeight / imageView.getImage().getHeight();
double zoomFactor = scaleFactor * 2d * 2.55d / 3;
double width = imageView.getImage().getWidth() * zoomFactor;
double height = imageView.getImage().getHeight() * zoomFactor;
imageView.setFitWidth(width);
imageView.setFitHeight(height);
return imageView;
}
}
I want to draw a line with arrow head on it (depicting an arrow shape). I referred to a solution to this from : JavaFX - draw line with arrow (Canvas)
The above solution does not allow to make the arrow drag able, so i wrote a small code to make this drag able
void drawArrow(int x1, int y1, int x2, int y2) {
gc.clearRect(0, 0,drawingCanvas.getWidth(), drawingCanvas.getHeight());
gc.setFill(Color.BLACK);
double dx = x2 - x1, dy = y2 - y1;
double angle = Math.atan2(dy, dx);
int len = (int) Math.sqrt(dx * dx + dy * dy);
Transform transform = Transform.translate(x1, y1);
transform = transform.createConcatenation(Transform.rotate(Math.toDegrees(angle), 0, 0));
gc.setTransform(new Affine(transform));
gc.strokeLine(0, 0, len, 0);
gc.fillPolygon(new double[]{len, len - ARR_SIZE, len - ARR_SIZE, len}, new double[]{0, -ARR_SIZE, ARR_SIZE, 0},
4);
}
gc=drawingCanvas.getGraphicsContext2D();
drawingCanvas.setOnMousePressed( e -> {
startX = prevX = currentX = (int)e.getX();
startY = prevY = currentY = (int)e.getY();
dragging = true;
drawArrow(startX, startY, currentX, currentY);
});
drawingCanvas.setOnMouseDragged( e -> {
if (!dragging)
return;
currentX = (int)e.getX();
currentY = (int)e.getY();
gc.clearRect(0,0,drawingCanvas.getWidth(),drawingCanvas.getHeight());
if (startX == currentX || startY == currentY)
return;
drawArrow(startX, startY, currentX, currentY);
prevX = currentX;
prevY = currentY;
});
drawingCanvas.setOnMouseReleased( e -> {
dragging = false;
if (startX == currentX || startY == currentY)
return;
drawArrow(startX, startY, currentX, currentY);
prevX = currentX;
prevY = currentY;
});
The problem here is the previous arrows are not getting cleared even after using
gc.clearRect(0,0,drawingCanvas.getWidth(),drawingCanvas.getHeight());
This is what is happening :
Expected Result :
Here's my Arrow class. I bind its coordinates to vertices (Buttons) which are draggable.
Dragged Arrows Example GIF
package mycontrols;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.Group;
import javafx.scene.shape.Polyline;
public class Arrow extends Group {
private Polyline mainLine = new Polyline();
private Polyline headA = new Polyline();
private Polyline headB = new Polyline();
private SimpleDoubleProperty x1 = new SimpleDoubleProperty();
private SimpleDoubleProperty y1 = new SimpleDoubleProperty();
private SimpleDoubleProperty x2 = new SimpleDoubleProperty();
private SimpleDoubleProperty y2 = new SimpleDoubleProperty();
private SimpleBooleanProperty headAVisible = new SimpleBooleanProperty(true);
private SimpleBooleanProperty headBVisible = new SimpleBooleanProperty(true);
private final double ARROW_SCALER = 20;
private final double ARROWHEAD_ANGLE = Math.toRadians(20);
private final double ARROWHEAD_LENGTH = 10;
public Arrow(double x1, double y1, double x2, double y2){
this.x1.set(x1);
this.y1.set(y1);
this.x2.set(x2);
this.y2.set(y2);
getChildren().addAll(mainLine, headA, headB);
for(SimpleDoubleProperty s : new SimpleDoubleProperty[]{this.x1,this.y1,this.x2,this.y2}){
s.addListener( (l,o,n) -> update() );
}
setUpStyleClassStructure();
headA.visibleProperty().bind(headAVisible);
headB.visibleProperty().bind(headBVisible);
update();
}
private void setUpStyleClassStructure() {
mainLine.getStyleClass().setAll("arrow");
headA.getStyleClass().setAll("arrow");
headB.getStyleClass().setAll("arrow");
headA.getStyleClass().add("arrowhead");
headB.getStyleClass().add("arrowhead");
getStyleClass().addListener( (ListChangeListener<? super String>) c -> {
c.next();
for(Polyline p : new Polyline[]{mainLine, headA, headB}){
p.getStyleClass().addAll(c.getAddedSubList());
p.getStyleClass().removeAll(c.getRemoved());
}
});
}
private void update() {
double[] start = scale(x1.get(), y1.get(), x2.get(), y2.get());
double[] end = scale(x2.get(), y2.get(), x1.get(), y1.get());
double x1 = start[0];
double y1 = start[1];
double x2 = end[0];
double y2 = end[1];
mainLine.getPoints().setAll(x1,y1,x2,y2);
double theta = Math.atan2(y2-y1, x2-x1);
//arrowhead 1
double x = x1 + Math.cos(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
double y = y1 + Math.sin(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
headA.getPoints().setAll(x,y,x1,y1);
x = x1 + Math.cos(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
y = y1 + Math.sin(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
headA.getPoints().addAll(x,y);
//arrowhead 2
x = x2 - Math.cos(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
y = y2 - Math.sin(theta + ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
headB.getPoints().setAll(x,y,x2,y2);
x = x2 - Math.cos(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
y = y2 - Math.sin(theta - ARROWHEAD_ANGLE) * ARROWHEAD_LENGTH;
headB.getPoints().addAll(x,y);
}
private double[] scale(double x1, double y1, double x2, double y2){
double theta = Math.atan2(y2-y1, x2-x1);
return new double[]{
x1 + Math.cos(theta) * ARROW_SCALER,
y1 + Math.sin(theta) * ARROW_SCALER
};
}
//getters and setters
public double getX1() {
return x1.get();
}
public SimpleDoubleProperty x1Property() {
return x1;
}
public void setX1(double x1) {
this.x1.set(x1);
}
public double getY1() {
return y1.get();
}
public SimpleDoubleProperty y1Property() {
return y1;
}
public void setY1(double y1) {
this.y1.set(y1);
}
public double getX2() {
return x2.get();
}
public SimpleDoubleProperty x2Property() {
return x2;
}
public void setX2(double x2) {
this.x2.set(x2);
}
public double getY2() {
return y2.get();
}
public SimpleDoubleProperty y2Property() {
return y2;
}
public void setY2(double y2) {
this.y2.set(y2);
}
public boolean isHeadAVisible() {
return headAVisible.get();
}
public SimpleBooleanProperty headAVisibleProperty() {
return headAVisible;
}
public void setHeadAVisible(boolean headAVisible) {
this.headAVisible.set(headAVisible);
}
public boolean isHeadBVisible() {
return headBVisible.get();
}
public SimpleBooleanProperty headBVisibleProperty() {
return headBVisible;
}
public void setHeadBVisible(boolean headBVisible) {
this.headBVisible.set(headBVisible);
}
}
package application;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private static Canvas canvas;
private static int clickCount = 0;
private static int x1 = 0;
private static int y1 = 0;
static ArrayList<Circle> circleArr = new ArrayList<Circle>();
private Timeline timeline;
#Override
public void start(Stage primaryStage) {
try {
primaryStage.setTitle("Graphics Demo App");
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 800, 500);
canvas = new Canvas(800, 475);
canvas.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
if (clickCount % 2 == 0) {
x1 = x;
y1 = y;
} else {
int x2 = x;
int y2 = y;
int r = (int) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
int x_corner = x1 - r;
int y_corner = y1 - r;
Circle c = new Circle(x_corner, y_corner, r);
circleArr.add(c);
c.drawOn(canvas);
}
clickCount++;
}
});
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
root.setCenter(canvas);
Button circleButton = new Button("Circle");
circleButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
// System.out.println("Circle");
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.AQUAMARINE);
Random rand = new Random();
int x = rand.nextInt((int) canvas.getWidth());
int y = rand.nextInt((int) canvas.getHeight());
Circle c = new Circle(x, y, 50);
c.drawOn(canvas);
// gc.strokeOval(x, y, 200, 200);
}
});
Button squareButton = new Button("Square");
squareButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
// System.out.println("square");
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.AQUAMARINE);
Random rand = new Random();
int x = rand.nextInt((int) canvas.getWidth());
int y = rand.nextInt((int) canvas.getHeight());
int w = rand.nextInt((int) canvas.getWidth());
int h = rand.nextInt((int) canvas.getHeight());
Square s = new Square(x, y, w, h);
s.drawOn(canvas);
// gc.strokeOval(x, y, 200, 200);
}
});
Button stepButton = new Button("Step");
stepButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
step();
}
});
Button playButton = new Button("Play");
playButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
timeline = new Timeline(new KeyFrame(Duration.millis(100), ae -> step()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
});
Button stopButton = new Button("Stop");
stopButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
if (timeline != null) {
timeline.stop();
}
}
});
FlowPane buttonsPane = new FlowPane();
buttonsPane.setAlignment(Pos.CENTER);
buttonsPane.getChildren().add(circleButton);
buttonsPane.getChildren().add(squareButton);
Button saveButton = new Button("Save");
Button restoreButton = new Button("Restore");
restoreButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
restore();
}
});
buttonsPane.getChildren().add(saveButton);
buttonsPane.getChildren().add(restoreButton);
buttonsPane.getChildren().add(stepButton);
buttonsPane.getChildren().add(playButton);
buttonsPane.getChildren().add(stopButton);
root.setBottom(buttonsPane);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void step() {
clearScreen();
for (Circle c : circleArr) {
c.step(canvas);
}
}
private static void clearScreen() {
canvas.getGraphicsContext2D().fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
public static void main(String[] args) {
launch(args);
}
public static void restore() {
try {
// Setting up to read the text file
FileReader reader = new FileReader("shapes.txt");
BufferedReader bufferReader = new BufferedReader(reader);
// Read the first line in the file
String line = bufferReader.readLine();
while (line != null && line.trim().length() > 0) {
String[] parts = line.split(":");
String left = parts[0];
String right = parts[1];
if (left.equals("C")) {
String[] components = right.trim().split(" ");
int x = Integer.parseInt(components[0]);
int y = Integer.parseInt(components[1]);
int r = Integer.parseInt(components[2]);
Circle c = new Circle(x, y, r);
c.drawOn(canvas);
} else if (left.equals("S")) {
System.out.println("This is a square: " + right);
}
line = bufferReader.readLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
This is the circle code :-
package application;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Circle {
int x;
int y;
int radius;
int x_direction = 1;
int y_direction = 1;
int dx = 11, dy = 7;
public Circle(){
}
public Circle(int x, int y, int r){
this.x = x;
this.y = y;
this.radius = r;
}
public void print(){
System.out.println("Circle: " + x + " " + y + " " + radius);
}
public void drawOn(Canvas canvas){
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.AQUAMARINE);
gc.strokeOval(x, y, 2*radius, 2*radius);
}
public void step(Canvas canvas){
x += 5 * x_direction;
y += 5 * y_direction;
if (x + radius * 2 > canvas.getWidth()){
x_direction = x_direction * -1;
}
if (y + radius * 2 > canvas.getHeight()){
y_direction = y_direction * -1;
}
if ((x - radius + dx < 0) || (x + radius + dx > canvas.getWidth()))
dx = -dx;
if ((y - radius + dy < 0) || (y + radius + dy > canvas.getHeight()))
dy = -dy;
// Move the circle.
x += dx;
y += dy;
drawOn(canvas);
}
Here is what I came up with. As you didn't say your problem, I made an example. I didn't use a time line, I just used another thread that continually updates.
package helloworld;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Matt on 17/08/16.
*/
public class AnimatedScene extends Application{
List<AnimatedCircle> circles = new ArrayList<>();
Canvas custom;
boolean quit = false;
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
custom = new Canvas(600, 600);
GraphicsContext gc = custom.getGraphicsContext2D();
createBackground(gc);
root.getChildren().add(custom);
circles.add(new AnimatedCircle(50, 50));
circles.add(new AnimatedCircle(50, 100));
circles.add(new AnimatedCircle(50, 150));
circles.add(new AnimatedCircle(50, 200));
for(AnimatedCircle circle: circles) {
root.getChildren().add(circle.shape);
}
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, evt->{
quit=true;
});
startMainLoop();
}
private void startMainLoop(){
new Thread(()->{
Thread.currentThread().setName("bounce loop");
long time = System.currentTimeMillis();
int step = (int)(1000.0/60.0);
while(!(Thread.interrupted()||quit)){
synchronized (circles) {
for(int i = 0; i<circles.size(); i++){
for(int j = i+1; j<circles.size(); j++){
AnimatedCircle a = circles.get(i);
AnimatedCircle b = circles.get(j);
double dx = b.cx - a.cx;
double dy = b.cy - a.cy;
double distance = Math.sqrt(dx*dx + dy*dy);
if(distance<a.radius + b.radius){
//collision
b.vx = dx/distance;
b.vy = dy/distance;
a.vx = -dx/distance;
a.vy = dy/distance;
}
}
}
for (AnimatedCircle circle : circles) {
circle.update();
}
}
Platform.runLater(()->{
sync();
});
try{
long nt = System.currentTimeMillis();
long wait = step - (nt - time);
if(wait>=0) {
Thread.sleep(wait);
time = nt+wait;
} else{
time = nt;
}
} catch(InterruptedException exc){
//just exit.
}
}
}).start();
}
void sync(){
synchronized (circles){
GraphicsContext gc = custom.getGraphicsContext2D();
createBackground(gc);
for(AnimatedCircle circle: circles){
circle.sync();
gc.setFill(new Color(0, 0, 0, 0.2));
gc.fillOval(circle.cx - circle.radius-15, circle.cy - circle.radius-5, 50, 50);
}
}
}
void createBackground(GraphicsContext gc){
gc.setFill(Color.RED);
RadialGradient gradient = new RadialGradient(
0, 0, 300, 300, 300, false,
CycleMethod.NO_CYCLE,
new Stop(0.0, Color.RED), new Stop(1.0, Color.DARKRED));
gc.setFill(gradient);
gc.fillRect(100, 100, 400, 400);
gc.setFill(new Color(1, 0.2, 0.2, 1));
gc.fillPolygon(
new double[]{ 0, 100, 100, 0},
new double[]{ 0, 100, 500, 600},
4
);
gc.setFill(new Color(1, 0.25, 0.25, 1));
gc.fillPolygon(
new double[]{ 0, 100, 500, 600},
new double[]{ 0, 100, 100, 0},
4
);
gc.setFill(new Color(0.5, 0., 0., 1));
gc.fillPolygon(
new double[]{ 500, 600, 600, 500},
new double[]{ 100, 0, 600, 500},
4
);
gc.setFill(new Color(0.8, 0., 0., 1));
gc.fillPolygon(
new double[]{ 0, 100, 500, 600},
new double[]{ 600, 500, 500, 600},
4
);
}
public static void main(String[] args){
launch(args);
}
}
class AnimatedCircle{
public Ellipse shape;
RadialGradient gradient;
double vx;
double vy;
double cx, cy;
double speed = 5;
double radius = 25;
public AnimatedCircle(double x, double y){
cx = x;
cy = y;
vx = 2*(Math.random() - 0.5);
vy = Math.sqrt(1 - vx*vx);
if(Math.random()>=0.5){
vy = -vy;
}
shape = new Ellipse(cx, cy, radius, radius);
final String label = x + ", " + y;
shape.addEventHandler(MouseEvent.MOUSE_CLICKED, evt->{
System.out.println(label);
});
gradient = new RadialGradient(0, 0, 0.75, 0.6, 1, true, CycleMethod.NO_CYCLE, new Stop(0.0, Color.WHITE), new Stop(1.0, Color.BLACK));
shape.setFill(gradient);
}
void update(){
double nx = cx + vx*speed;
if(nx + shape.getRadiusX()>600 || nx - shape.getRadiusX()<0){
vx = -vx;
nx = cx;
}
double ny = cy + vy*speed;
if(ny + shape.getRadiusY()>600 || ny - shape.getRadiusY()<0 ){
vy = -vy;
ny = cy;
}
cy = ny;
cx = nx;
}
public void sync(){
shape.setCenterX(cx);
shape.setCenterY(cy);
}
}
Things to note:
In createBackground, I completely repaint the background. In your clear method you fill everything with the last paint. That doesn't necessarily clear your background.
I used two different methods to draw the balls. 1) I use ellipses that are added to the scene, then I update their position. 2) I draw the shadows using the graphics canvas.
Otherwise, if you can clean up your question a bit, and actually explain what is wrong somebody might be able to fix your code.
Is there any example of 3-D Bar chart for the latest Java 8 which uses the modern 3-D API?
I would like to use the 3-D API from Java 8.
There was a 3d-Bar-Chart demo in the earlier versions of JavaFX, but that was removed.
You can create a bar chart yourself. Just create a grid and some boxes on it. Here's a slight modification of the answer in How to create a 3d / surface chart with JavaFX. Drag around via mouse, use mouse wheel to zoom.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class BarChart3dDemo extends Application {
private static Random rnd = new Random();
// size of graph
int graphSize = 400;
// variables for mouse interaction
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-45, Rotate.Y_AXIS);
#Override
public void start(Stage primaryStage) {
// create axis walls
Group grid = createGrid(graphSize);
// initial cube rotation
grid.getTransforms().addAll(rotateX, rotateY);
// add objects to scene
StackPane root = new StackPane();
root.getChildren().add(grid);
// create bars
double gridSizeHalf = graphSize / 2;
double size = 30;
for (double i = -gridSizeHalf + size; i < gridSizeHalf; i += 50) {
for (double j = -gridSizeHalf + size; j < gridSizeHalf; j += 50) {
double height = rnd.nextDouble() * 300;
Box box = new Box(size, height, size);
// color
PhongMaterial mat = new PhongMaterial();
mat.setDiffuseColor(randomColor());
box.setMaterial(mat);
// location
box.setLayoutY(-height * 0.5 + graphSize * 0.5);
box.setTranslateX(i);
box.setTranslateZ(j);
grid.getChildren().addAll(box);
}
}
// scene
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
makeZoomable(root);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Axis wall
*/
public static class Axis extends Pane {
Rectangle wall;
public Axis(double size) {
// wall
// first the wall, then the lines => overlapping of lines over walls
// works
wall = new Rectangle(size, size);
getChildren().add(wall);
// grid
double zTranslate = 0;
double lineWidth = 1.0;
Color gridColor = Color.WHITE;
for (int y = 0; y <= size; y += size / 10) {
Line line = new Line(0, 0, size, 0);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateY(y);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
for (int x = 0; x <= size; x += size / 10) {
Line line = new Line(0, 0, 0, size);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateX(x);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
}
public void setFill(Paint paint) {
wall.setFill(paint);
}
}
public void makeZoomable(StackPane control) {
final double MAX_SCALE = 20.0;
final double MIN_SCALE = 0.1;
control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = control.getScaleX();
if (event.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
scale = clamp(scale, MIN_SCALE, MAX_SCALE);
control.setScaleX(scale);
control.setScaleY(scale);
event.consume();
}
});
}
/**
* Create axis walls
*
* #param size
* #return
*/
private Group createGrid(int size) {
Group cube = new Group();
// size of the cube
Color color = Color.LIGHTGRAY;
List<Axis> cubeFaces = new ArrayList<>();
Axis r;
// back face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(0.5 * size);
cubeFaces.add(r);
// bottom face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(0);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// right face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
r.setTranslateX(-1 * size);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// left face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
r.setTranslateX(0);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// top face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-1 * size);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// front face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(-0.5 * size);
// cubeFaces.add( r);
cube.getChildren().addAll(cubeFaces);
return cube;
}
public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
public static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
public static Color randomColor() {
return Color.rgb(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
}
public static void main(String[] args) {
launch(args);
}
}
Refer to this example, it shows use of 3-D bar chart if it helps you.
http://www.jroller.com/dgilbert/entry/creating_3d_charts_in_java
Jzy3d has nice bar charts in its gallery. You can use Jzy3d with JavaFX easily.