I'm trying to make a method that returns after the user clicks on a rectangle in the stage. However, when calling wait() and then later notify() when the rectangle is clicked, the program freezes. How would one make a showAndWait() method in javafx?
I'm trying to make a class that asks a multiple choice question then returns the result, the below code is this class and it's commented where I attempted to put a wait and notify call.
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
public class Question{
int answer; //refers to the answer in the order of strings given
// a = 1, b = 2, c = 3, d = 4
String question;
String a,b,c,d;
public Question(String question, String a, String b, String c, String d, int answer){
this.question = question;
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.answer = answer;
}
private final double sideLength = 500;
private volatile byte complete; //0 = no, 1 = true, -1 = false
public synchronized boolean showAndWait(){
complete = 0;
Stage stage = new Stage();
Pane pane = new Pane();
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.setResizable(false);
stage.setMaxHeight(sideLength);
stage.setMinHeight(sideLength);
stage.setMaxWidth(sideLength);
stage.setMinWidth(sideLength);
double width = sideLength/3;
double height = sideLength/5;
double firstX = sideLength/9;
double secondX = width + sideLength/9*2;
double firstY = height + sideLength*2/5/3;
double secondY = firstY * 2;
Color base = Color.AQUAMARINE;
Color wrong = Color.RED;
Color right = Color.GREEN;
Pane questionPane = makeAnswer(0,0,sideLength,sideLength/5, question, base, base);
Pane ansA = makeAnswer(firstX, firstY, width, height,a,base, wrong);
Pane ansB = makeAnswer(secondX, firstY, width, height,b,base, wrong);
Pane ansC = makeAnswer(firstX, secondY, width, height,c,base, wrong);
Pane ansD = makeAnswer(secondX, secondY, width, height,d,base, wrong);
pane.getChildren().addAll(questionPane, ansA, ansB, ansC, ansD);
stage.show();
//wait here, check to see if completed
System.out.println(complete);
return complete == 1;
}
private double textShiftX = 30;
private double textShiftY = 30;
private int characterLimit = 20;
private double textChangeY = 10;
private synchronized Pane makeAnswer(double x, double y,double width, double height, String s, Color base, Color afterClick){
Pane temp = new Pane();
temp.setLayoutX(x);
temp.setLayoutY(y);
Rectangle rD = new Rectangle(0, 0, width, height);
rD.setFill(base);
temp.getChildren().add(rD);
for(int i=0;s.length() > 0;i++){
String sTemp;
if(s.length()>characterLimit){
sTemp = s.substring(0,characterLimit);
while(sTemp.charAt(sTemp.length()-1) != ' ')
sTemp = sTemp.substring(0,sTemp.length()-1);
}
else
sTemp = s;
s = s.substring(sTemp.length());
Text t = new Text(textShiftX, textShiftY + textChangeY*i, sTemp);
temp.getChildren().add(t);
}
rD.setOnMouseClicked(
(e)->{
((Rectangle)e.getSource()).setFill(afterClick);
complete = 1;
//notify
});
return temp;
}
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)
With the help of several posts from here I created a draggable and rotable rectangle. I want that user can drag an anchor to rotate rectangle from center and resize it by one corner while opposite corner stays at same place, if rectangle keep unrotated calculations to do it are very simple. Unfortunally in this case the bounding rectangle can be rotated and trigonometry and how apply here is not very clear to me. After days googling so far any attemp I made to make this work fail miserably. Sorry for any mistake, english is not my native language. Thanks in advance
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final Delta dragDelta = new Delta();
final Rotate rotate = new Rotate();
Wrapper<Point2D> mouseLocation = new Wrapper<>();
Pane root = new Pane();
Rectangle rect = new Rectangle(100,100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth())/2);
rotate.setPivotY((rect.getY() + rect.getHeight())/2);
rect.setStyle(
"-fx-stroke: blue; " +
"-fx-stroke-width: 2px; " +
"-fx-stroke-dash-array: 12 2 4 2; " +
"-fx-stroke-dash-offset: 6; " +
"-fx-stroke-line-cap: butt; " +
"-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
mouseLocation.value = null ;
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - mouseLocation.value.getX();
double deltaY = mouseEvent.getSceneY() - mouseLocation.value.getY();
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
});
Circle topLeft = new Circle(7);
topLeft.centerXProperty().bind(rect.xProperty());
topLeft.centerYProperty().bind(rect.yProperty());
//Here is the tricky part
topLeft.setOnMousePressed(e->{
});
topLeft.setOnMouseDragged(e->{
});
//Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()).divide(2));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
dragDelta.x = event.getSceneX();
dragDelta.x = event.getSceneY();
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
double x1 = dragDelta.x;
double y1 = dragDelta.y;
var x2 = event.getSceneX();
var y2 = event.getSceneY();
var px = rotate.getPivotX() + localToScene.getTx();
var py = rotate.getPivotY() + localToScene.getTy();
// Work out the angle rotated
double th1 = deltaAngle(x1, y1, px, py);
double th2 = deltaAngle(x2, y2, px, py);
var angle = rotate.getAngle();
angle += th2 - th1;
// Rotate the rectangle
rotate.setAngle(angle);
dragDelta.x = event.getSceneX();
dragDelta.y = event.getSceneY();
});
group.getChildren().addAll(rect, rotateCircle, topLeft);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// Return the angle from 0 to 360
public static double deltaAngle (double x, double y, double px, double py) {
double dx = x - px;
double dy = y - py;
double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));
if(dy < 0) {
angle = 360 - angle;
}
return angle;
}
// records relative x and y co-ordinates.
private static class Delta {
double x, y;
}
static class Wrapper<T> {
T value ;
}
public static void main(String[] args) {
launch(args);
}
}
Something like this...
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
public class FancyRectangle extends Application {
#Override
public void start(Stage primaryStage) {
final MouseContext refPoint = new MouseContext();
final Rotate rotate = new Rotate();
Pane root = new Pane();
Rectangle rect = new Rectangle(100, 100);
//set pivot on rectangle center
rotate.setPivotX((rect.getX() + rect.getWidth()) / 2);
rotate.setPivotY((rect.getY() + rect.getHeight()) / 2);
rect.setStyle(
"-fx-stroke: blue; "
+ "-fx-stroke-width: 2px; "
+ "-fx-stroke-dash-array: 12 2 4 2; "
+ "-fx-stroke-dash-offset: 6; "
+ "-fx-stroke-line-cap: butt; "
+ "-fx-fill: rgba(255, 255, 255, .0);"
);
Group group = new Group();
// make a rectangle movable by dragging it around with the mouse.
rect.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
group.getScene().setCursor(Cursor.MOVE);
});
rect.setOnMouseReleased(mouseEvent -> {
group.getScene().setCursor(Cursor.HAND);
});
rect.setOnMouseDragged(mouseEvent -> {
// Get the mouse deltas
double deltaX = mouseEvent.getSceneX() - refPoint.sceneX;
double deltaY = mouseEvent.getSceneY() - refPoint.sceneY;
group.setTranslateX(group.getTranslateX() + deltaX);
group.setTranslateY(group.getTranslateY() + deltaY);
refPoint.sceneX = mouseEvent.getSceneX();
refPoint.sceneY = mouseEvent.getSceneY();
});
Circle resizeCircle = new Circle(7);
resizeCircle.centerXProperty().bind(rect.xProperty());
resizeCircle.centerYProperty().bind(rect.yProperty());
// Handle resize
resizeCircle.setOnMousePressed(e -> {
var sceneCenter = resizeCircle.localToScene(resizeCircle.getCenterX(), resizeCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = rect.getWidth();
refPoint.offY = rect.getHeight();
});
resizeCircle.setOnMouseDragged(e -> {
try {
var sceneToLocal = group.getLocalToSceneTransform().createInverse();
var localRef = sceneToLocal.transform(refPoint.sceneX, refPoint.sceneY);
var localMouse = sceneToLocal.transform(e.getSceneX(), e.getSceneY());
var dx = localMouse.getX() - localRef.getX();
var dy = localMouse.getY() - localRef.getY();
var x = localMouse.getX();
var y = localMouse.getY();
var w = Math.abs(refPoint.offX-dx);
var h = Math.abs(refPoint.offY-dy);
rect.setX(x);
rect.setY(y);
rect.setWidth(w);
rect.setHeight(h);
rotate.setPivotX(x + w/2);
rotate.setPivotY(y + h/2);
} catch (NonInvertibleTransformException ex) {}
e.consume();
});
// Anchor for rotate the rectangle
Circle rotateCircle = new Circle(7);
rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty().divide(2)));
rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
rotateCircle.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {
var sceneCenter = rotateCircle.localToScene(rotateCircle.getCenterX(), rotateCircle.getCenterY());
refPoint.sceneX = e.getSceneX();
refPoint.sceneY = e.getSceneY();
refPoint.offX = sceneCenter.getX()-refPoint.sceneX;
refPoint.offY = sceneCenter.getY()-refPoint.sceneY;
});
// When it's dragged rotate the box
rotateCircle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {
var localToScene = group.getLocalToSceneTransform();
var x2 = event.getSceneX()+refPoint.offX;
var y2 = event.getSceneY()+refPoint.offY;
var pivotPoint = localToScene.transform(rotate.getPivotX(), rotate.getPivotY());
double angle = Math.toDegrees(Math.atan2(x2-pivotPoint.getX(), pivotPoint.getY()-y2));
// Rotate the rectangle
rotate.setAngle(angle);
});
group.getChildren().addAll(rect, rotateCircle, resizeCircle);
group.setLayoutX(100);
group.setLayoutY(100);
group.getTransforms().add(rotate);
root.getChildren().add(group);
Scene scene = new Scene(root, 400, 400);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
// records relative x and y co-ordinates.
private static class MouseContext {
double sceneX, sceneY;
double offX, offY;
}
public static void main(String[] args) {
launch(args);
}
}
I'm having trouble getting the Hit button to work in my BlackJack program. I tried programming the action for the hit button in the setOnAction block as well as lambda, but I get an error about the variables not being final. So, I tried it this way, but I don't think my variables are carrying over. It's probably something very simple. Please help if you can. Thanks!
import java.util.Arrays;
import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
public class BlackJackGame extends Application {
public String btHitY;
public int NumberPlayerCards;
public int NumberDealerCards;
public int NUMBER_OF_CARDS;
public int PlayerCards[];
public int DealerCards[];
public Image imagesP[];
public Image imagesD[];
public int deck[];
public String URLBase;
public GridPane pane;
public BlackJackGame() {
this.btHitY = " ";
this.btHitY = new String();
}
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
//Create array deck, suit string, and rank string
int[] deck = new int[52];
String[] suits = {"Spades", "Hearts", "Diamonds", "Clubs"};
String[] ranks = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"};
int NUMBER_OF_CARDS = 4;
String URLBase = "http://www.cs.armstrong.edu/liang/common/image/card/";
//Initialize the cards
for (int i = 0; i < deck.length; i++)
deck[i] = i;
//Shuffle the cards
for (int i = 0; i < deck.length; i++) {
//Generate an index randomly
int index = (int)(Math.random() * deck.length);
int temp = deck[i];
deck[i] = deck[index];
deck[index] = temp;
}
int NumberPlayerCards = 2;
int NumberDealerCards = 2;
int[] PlayerCards = new int[50];
int[] DealerCards = new int[50];
//Display the first cards
for (int i = 0; i < 8; i++) {
String suit = suits[deck[i] / 13];
String rank = ranks[deck[i] % 13];
System.out.println("Card number " + deck[i] + ": " + rank + " of " + suit);
}
for (int i = 0; i < NumberPlayerCards; i++)
PlayerCards[i] = deck[i * 2];
for (int i = 0; i < NumberDealerCards; i++)
DealerCards[i] = deck[i * 2 + 1];
// Create a pane to hold the image views
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(5, 5, 5, 5));
pane.setHgap(5);
pane.setVgap(5);
Image[] imagesP = new Image[50];
Image[] imagesD = new Image[50];
for (int i = 0; i < NumberPlayerCards; i++) {
int cardForPrint = PlayerCards[i] + 1;
System.out.println(URLBase + cardForPrint + ".png");
imagesP[i] = new Image(URLBase + cardForPrint + ".png");
}
for (int i = 0; i < NumberDealerCards; i++) {
int cardForPrint = DealerCards[i] + 1;
System.out.println(URLBase + cardForPrint + ".png");
imagesD[i] = new Image(URLBase + cardForPrint + ".png");
}
//rotate flag image to cover dealer card
Image flag = new Image("http://www.cs.armstrong.edu/liang/common/image/us.gif");
ImageView imageFlag = new ImageView(flag);
imageFlag.setRotate(90);
imageFlag.setFitHeight(75);
imageFlag.setFitWidth(95);
pane.add(new Label("Player Cards"), 0, 0);
pane.add(new ImageView(imagesP[0]), 1, 0);
pane.add((imageFlag), 1, 1);
pane.add(new Label("Dealer Cards"), 0, 1);
pane.add(new ImageView(imagesP[1]), 2, 0);
pane.add(new ImageView(imagesD[1]), 2, 1);
Button btHit = new Button("Hit");
Button btStay = new Button("Stay");
pane.add(btHit, 1, 2);
pane.add(btStay, 2, 2);
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 1200, 700);
primaryStage.setTitle("Black Jack"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
HitHandlerClass handlerHit = new HitHandlerClass();
btHitY = " ";
btHit.setOnAction(handlerHit);
/* if (btHitY.equals("Hit")); {
NumberPlayerCards = NumberPlayerCards + 1;
NUMBER_OF_CARDS = NUMBER_OF_CARDS + 1;
PlayerCards[NumberPlayerCards - 1] = deck[NUMBER_OF_CARDS - 1];
for (int j = 0; j < NumberPlayerCards; j++){
System.out.println(PlayerCards[j]);
}
System.out.println(NumberPlayerCards);
int CardForPrint2 = PlayerCards[NumberPlayerCards - 1] + 1;
imagesP[NumberPlayerCards - 1] = new Image(URLBase + CardForPrint2 + ".png");
pane.add(new ImageView(imagesP[NumberPlayerCards - 1]), NumberPlayerCards, 0);
btHitY = " ";
primaryStage.show();
} */
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
* #param args
*/
public static void main(String[] args) {
launch(args);
}
class HitHandlerClass implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent e) {
NumberPlayerCards = NumberPlayerCards + 1;
NUMBER_OF_CARDS = NUMBER_OF_CARDS + 1;
PlayerCards[NumberPlayerCards - 1] = deck[NUMBER_OF_CARDS - 1];
for (int j = 0; j < NumberPlayerCards; j++){
System.out.println(PlayerCards[j]);
}
System.out.println(NumberPlayerCards);
int CardForPrint2 = PlayerCards[NumberPlayerCards - 1] + 1;
imagesP[NumberPlayerCards - 1] = new Image(URLBase + CardForPrint2 + ".png");
pane.add(new ImageView(imagesP[NumberPlayerCards - 1]), NumberPlayerCards, 0);
btHitY = " ";
}
}
}
You declare this global
public int deck[];
and then local
int[] deck = new int[52];
So the global never gets initialized and gives you a Nullpointer Exception.
Solution:
deck = new int[52];
Same for the other variables.
public class BlackJackGame extends Application {
//snip
public int NUMBER_OF_CARDS;
public int deck[];
//snip
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
//Create array deck, suit string, and rank string
int[] deck = new int[52];
int NUMBER_OF_CARDS = 4;
//snip
You've declared deck[] and NUMBER_OF_CARDS at a higher scope, then redeclared them later, at a lower scope.
To fix these issues, simply remove the type declaration in the start method.
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
//Create array deck, suit string, and rank string
deck = new int[52];
NUMBER_OF_CARDS = 4;
//snip
As a side note, I would like to recommend you read up a little bit on Code conventions for Java/Javafx, as they help other programmers read your code by providing a standard style.
I am attempting to make a loan calculator that shows payment number, interest, principal, and balance in tableview in JavaFX. Right now only the principal and balance columns are populating. I am fairly new to JavaFX, so I am not sure if I have made a mistake in FX or in my formula. Here is my code:
public class LoanTable extends Application {
Integer payment;
Double interest;
Double principal;
Double balance;
#Override
public void start(Stage primaryStage) {
BorderPane border = new BorderPane();
GridPane grid = new GridPane();
ScrollPane scroll = new ScrollPane();
Label labelTitle = new Label("Enter Loan Amount, Number of Years, and Annual Interest Rate");
labelTitle.getStyleClass().add("labelTitle");
Label loan = new Label("Loan Amount");
TextField loanAmount = new TextField();
Label years = new Label("Number of Years");
TextField numYears = new TextField();
Label interestRate = new Label("Annual Interest Rate");
TextField rate = new TextField();
Button compute = new Button("Display Loan Schedule");
TableView<Finance> table = new TableView<>();
TableColumn paymentColumn = new TableColumn("Payment#");
paymentColumn.setMinWidth(200);
paymentColumn.setCellValueFactory(new PropertyValueFactory<Finance, Integer>("payment"));
TableColumn interestColumn = new TableColumn("Interest");
interestColumn.setMinWidth(200);
interestColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("interest"));
TableColumn prinColumn = new TableColumn("Principal");
prinColumn.setMinWidth(200);
prinColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("principal"));
TableColumn balColumn = new TableColumn("Balance");
balColumn.setMinWidth(200);
balColumn.setCellValueFactory(new PropertyValueFactory<Finance, Double>("balance"));
table.getColumns().addAll(paymentColumn, interestColumn, prinColumn, balColumn);
grid.add(labelTitle, 0, 0);
grid.add(new Label(), 0, 1);
grid.add(loan, 0, 2);
grid.add(loanAmount, 1, 2);
grid.add(years, 0, 3);
grid.add(numYears, 1, 3);
grid.add(interestRate, 0, 4);
grid.add(rate, 1, 4);
grid.add(compute, 4, 0);
scroll.setContent(table);
border.setTop(grid);
border.setBottom(scroll);
Scene scene = new Scene(border, 800, 800);
scene.getStylesheets().add("loan_table.css");
primaryStage.setScene(scene);
primaryStage.show();
compute.setOnAction((e) -> {
Double loanTotal = Double.parseDouble(loanAmount.getText());
Integer year = Integer.parseInt(numYears.getText());
Double rateInterest = Double.parseDouble(rate.getText());
double monthlyInterest = rateInterest / 1200;
double monthlyPayment = loanTotal * monthlyInterest / (1 - 1 / Math.pow(1 + monthlyInterest, year * 12));
balance = loanTotal;
for(int i = 1; i < year * 12; i++) {
interest = monthlyInterest * balance;
principal = monthlyPayment - interest;
balance = balance - principal;
payment = i + 1;
}
ObservableList<Finance> data = FXCollections.observableArrayList(new Finance(payment, interest, principal, balance));
table.setItems(data);
});
}
public static class Finance {
private final SimpleIntegerProperty payment;
private final SimpleDoubleProperty interest;
private final SimpleDoubleProperty principal;
private final SimpleDoubleProperty balance;
private Finance(int payment, double interest, double principal, double balance) {
this.payment = new SimpleIntegerProperty(payment);
this.interest = new SimpleDoubleProperty(interest);
this.principal = new SimpleDoubleProperty(principal);
this.balance = new SimpleDoubleProperty(balance);
}
public Integer getPayment(int numYears) {
return payment.get();
}
public Double getInterest(double rate) {
return interest.get();
}
public Double getPrincipal() {
return principal.get();
}
public Double getBalance() {
return balance.get();
}
}
public static void main(String[] args) {
launch(args);
}
}
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.