TriangleMesh Texture Coordinates are not interpolated as expected - javafx

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)

Related

PDFBox: draw millimeter paper

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;
}
}

Placing circles in a pane with javafx

Trying to create dynamically a series of circles with Javafx. After typing the number of circles i got this:
But actually i want that my circles be in that position:
Here is my code and thanks for any hints!!
int k = 5;
for (int i = 0; i < nbNoeuds; i++) {
Noeudfx circle = new Noeudfx(k * 2, k * 2, 1, String.valueOf(i));
Label id = new Label(String.valueOf(i));
noeuds.getChildren().add(id);
id.setLayoutX(k * 2 - 20);
id.setLayoutY(k * 2 - 20);
id.setBlendMode(BlendMode.DIFFERENCE);
k += 10;
FillTransition ft1 = new FillTransition(Duration.millis(300), circle, Color.RED, Color.BLACK);
ft1.play();
noeuds.getChildren().add(circle);
ScaleTransition tr = new ScaleTransition(Duration.millis(100), circle);
tr.setByX(10f);
tr.setByY(10f);
tr.setInterpolator(Interpolator.EASE_OUT);
tr.play();
}
}
public class Noeudfx extends Circle {
Noeud noeud;
Point point;
Label distance = new Label("distance : infinite");
boolean isSelected = false;
List<Noeudfx> circles = new ArrayList<>();
public Noeudfx(double a, double b, double c, String nom) {
super(a, b, c);
noeud = new Noeud(nom, this);
point = new Point((int) a, (int) b);
circles.add(this);
}
}
Here is my solution:
int nbNoeuds = Integer.parseInt(nodeID.getText());
System.out.println("nnnnn"
+ nbNoeuds);
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(.5), (ActionEvent actionEvent) -> {
while (noeuds.getChildren().size() <= nbNoeuds) {
// noeuds.getChildren().remove(0);
int radius =10 ;
noeuds.getChildren().add(
new Circle(
rnd.nextInt(SCENE_SIZE - radius * 2) + radius, rnd.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
Color.GRAY
)
);
}
})
);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
animation.setOnFinished((ActionEvent actionevent) -> {
animation.stop();
});
Update: i tried to add label to each circle, the problem was that the number of circles in the screen is not correct i don't know why!
Label id = new Label(String.valueOf(i));
id.setTextFill(Color.CADETBLUE);
id.setAlignment(Pos.CENTER);
Circle circle = new Circle(
rnd.nextInt(SCENE_SIZE - radius * 2) + radius, rnd.nextInt(SCENE_SIZE - radius * 2) + radius,
radius,
Color.GRAY
);
Double a = circle.getCenterX();
Double b = circle.getCenterY();
id.setLayoutX(a - 20);
id.setLayoutY(b - 20);
id.setBlendMode(BlendMode.DIFFERENCE);
noeuds.getChildren().add(id);
noeuds.getChildren().add(circle);

how to control Javafx scalafx FXyz 3D shapes

I have the following test code where I have a ClothMesh (from FXyz lib) that I
can drag, rotate and drag my circle handles. All works well, FXyz is great. Now I want to use SegmentedSphereMesh, it mostly work except that my circle handles are 2D and not wrapped around the sphere. I know the possible problems mixing 2D and 3D. However, it is so close to working; how can I make my handles work with the sphere, or what would be another way to do the same function.
Note, I do not want to control the shape/mesh by moving the camera around.
import org.fxyz.shapes.complex.cloth.ClothMesh
import org.fxyz.shapes.primitives.SegmentedSphereMesh
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.DoubleProperty
import scalafx.collections.ObservableFloatArray
import scalafx.scene.image.Image
import scalafx.scene.input.{MouseButton, MouseEvent}
import scalafx.scene.paint.PhongMaterial
import scalafx.scene.shape._
import scalafx.scene.transform.Rotate
import scalafx.scene._
import scalafx.scene.paint.Color
/**
* left mouse to drag the meshView and also to drag the handles
* right mouse drag + ctrl to rotate about X axis
* right mouse drag + alt to rotate about Y axis
* right mouse drag + shift to rotate about Z axis
*/
object ClothTest2 extends JFXApp {
private var dx = 0.0
private var dy = 0.0
stage = new PrimaryStage {
scene = new Scene(600, 600, true, SceneAntialiasing.Balanced) {
fill = Color.LightGray
val testImg = "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png"
val img = new Image(testImg, 400, 400, false, true)
val meshView = new SegmentedSphereMesh(20, 4, 2, 200d)
// val meshView = new ClothMesh(4, 4, 200, 200, 0.5, 0.5, 1.0)
meshView.setDrawMode(DrawMode.Fill)
meshView.setCullFace(CullFace.None)
meshView.style = "-fx-background-color: #00000000"
meshView.setMaterial(new PhongMaterial(Color.White, img, null, null, null))
val controller = new MeshController(meshView.getMesh().asInstanceOf[javafx.scene.shape.TriangleMesh].points)
val viewGroup = new Group(meshView, controller)
root = new Group(new AmbientLight(Color.White), viewGroup) { translateX = 70; translateY = 70 }
camera = new PerspectiveCamera(true) {
nearClip = 0.0
farClip = 100000.0
fieldOfView = 42
verticalFieldOfView = true
translateZ = -900
}
val rotHandler = new RotHandler(viewGroup)
onMouseDragged = (event: MouseEvent) => {
rotHandler.onMouseDragged(event)
if (event.button == MouseButton.PRIMARY) {
viewGroup.layoutX = event.sceneX + dx
viewGroup.layoutY = event.sceneY + dy
event.consume()
}
}
onMousePressed = (event: MouseEvent) => {
rotHandler.onMousePressed(event)
dx = viewGroup.layoutX.value - event.sceneX
dy = viewGroup.layoutY.value - event.sceneY
event.consume()
}
}
}
}
class CircleHandle(color: Color) extends Circle {
radius = 8
var dx = 0.0
var dy = 0.0
fill <== when(hover) choose Color.Red.deriveColor(1, 1, 1, 0.4) otherwise color.deriveColor(1, 1, 1, 0.4)
strokeWidth <== when(hover) choose 3 otherwise 2
stroke = color
onMousePressed = (event: MouseEvent) => {
dx = centerX.value - event.x
dy = centerY.value - event.y
event.consume()
}
onMouseDragged = (event: MouseEvent) => {
centerX = event.x + dx
centerY = event.y + dy
event.consume()
}
}
class MeshController(thePoints: ObservableFloatArray) extends Group {
children = for (i <- 0 until thePoints.size by 3) yield new CircleHandle(Color.Yellow) {
centerX() = thePoints.get(i)
centerX.onChange { (obs, oldVal, newVal) => thePoints.set(i, newVal.floatValue()) }
centerY() = thePoints.get(i + 1)
centerY.onChange { (obs, oldVal, newVal) => thePoints.set(i + 1, newVal.floatValue()) }
}
}
class RotHandler(val viewer: Group) {
private val angleX = DoubleProperty(0)
private val angleY = DoubleProperty(0)
private val angleZ = DoubleProperty(0)
private var anchorX = 0d
private var anchorY = 0d
private val rotX = new Rotate { angle <== angleX; axis = Rotate.XAxis }
private val rotY = new Rotate { angle <== angleY; axis = Rotate.YAxis }
private val rotZ = new Rotate { angle <== angleZ; axis = Rotate.ZAxis }
viewer.transforms = Seq(rotX, rotY, rotZ)
def onMousePressed(event: MouseEvent) = {
anchorX = event.sceneX
anchorY = event.sceneY
event.consume()
}
def onMouseDragged(event: MouseEvent) = {
// right mouse only
if (event.button == MouseButton.SECONDARY) {
event match {
// rotation about the Y axis, dragging the mouse in the x direction
case ev if ev.altDown => angleY() = anchorX - event.sceneX
// rotation about the X axis, dragging the mouse in the y direction
case ev if ev.controlDown => angleX() = anchorY - event.sceneY
// rotation about the Z axis, dragging the mouse in the x direction
case ev if ev.shiftDown => angleZ() = anchorX - event.sceneX
case _ => // ignore everything else
}
}
event.consume()
}
}

JavaFX spring physics

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...

JavaFX 3-D Bar chart with Java 8

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.

Resources