I want to create a hexagon of hexagons as buttons in JavaFX, I use an image and try to position some buttons to the position of each hexagon but I cannot change the position of them in a grid pane. Here is my code:
package sample;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
public class GameBoard extends GridPane {
public GameBoard(){
this.setAlignment(Pos.CENTER);
ImageView image = new ImageView();
Image hexagonImg = new Image("hexagon.jpg");
image.setFitWidth(500);
image.setFitHeight(500);
image.setImage(hexagonImg);
this.add(image,0,0);
GridPane button1Pane = new GridPane();
this.add(button1Pane,0,0);
Button button1 = new Button();
button1Pane.add(button1,1,0);
}
}
polygon hexagons
Since you need to use as buttons ; I added mouse click event wich changes stage's tittle . If you want to place hexagons side to side you may need to concider radius for x axis and apothem for y axis .
this is afunctional single class javafx app you can try
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
double HexagonRadius = 100;
Hexagon hexagon1 = new Hexagon(HexagonRadius, Color.CADETBLUE);
Hexagon hexagon2 = new Hexagon(HexagonRadius, Color.MEDIUMPURPLE);
hexagon2.setTranslateY(hexagon1.getOffsetY() * 2);
Hexagon hexagon3 = new Hexagon(HexagonRadius, Color.MEDIUMSEAGREEN);
hexagon3.setTranslateY(-hexagon1.getOffsetY() * 2);
Hexagon hexagon4 = new Hexagon(HexagonRadius, Color.CORNFLOWERBLUE);
hexagon4.setTranslateY(-hexagon1.getOffsetY());
hexagon4.setTranslateX(hexagon1.getOffsetX());
Hexagon hexagon5 = new Hexagon(HexagonRadius, Color.YELLOW);
hexagon5.setTranslateY(-hexagon1.getOffsetY());
hexagon5.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon6 = new Hexagon(HexagonRadius, Color.ORANGE);
hexagon6.setTranslateY(hexagon1.getOffsetY());
hexagon6.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon7 = new Hexagon(HexagonRadius, Color.SKYBLUE);
hexagon7.setTranslateY(hexagon1.getOffsetY());
hexagon7.setTranslateX(hexagon1.getOffsetX());
Group hexagonsGroup = new Group(hexagon1, hexagon2, hexagon3, hexagon4, hexagon5, hexagon6, hexagon7);
StackPane stackPane = new StackPane(hexagonsGroup);
var scene = new Scene(stackPane, 640, 480);
scene.setFill(Color.ANTIQUEWHITE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public class Hexagon extends Group {
private Polygon polygon;
private double radius;
private double radianStep = (2 * Math.PI) / 6;
private double offsetY;
private double offsetX;
public Hexagon(double radius, Paint color) {
this.radius = radius;
makeHexagon(radius, color);
offsetY = calculateApothem();
offsetX = radius * 1.5;
changeTittle();
}
private void makeHexagon(double radius, Paint color) {
polygon = new Polygon();
this.getChildren().add(polygon);
polygon.setFill(color);
polygon.setStroke(Color.WHITESMOKE);
polygon.setEffect(new DropShadow(10, Color.BLACK));
polygon.setStrokeWidth(10);
polygon.setStrokeType(StrokeType.INSIDE);
for (int i = 0; i < 6; i++) {
double angle = radianStep * i;
polygon.getPoints().add(Math.cos(angle) * radius / 1.1);
polygon.getPoints().add(Math.sin(angle) * radius / 1.1);
}
}
public void changeTittle() {
polygon.setOnMouseClicked(e -> {
Stage stage = (Stage) this.getScene().getWindow();
stage.setTitle(polygon.getFill().toString());
});
}
public double getOffsetY() {
return offsetY;
}
public double getOffsetX() {
return offsetX;
}
private double calculateApothem() {
return (Math.tan(radianStep) * radius) / 2;
}
}
}
I would just create a Path for each Hexagon and put them in Group which you can then place in the middle of some Pane. Dealing with images and a GridPane only complicates things. Just doing some graphics directly is much easier.
Related
I have an object, like a box in this example.
I want this box to move in a sine to the left on the sphere when the Z-axis is rotated. But after the box has made a curve, i.e. the rotation of the Z-axis is back to 0. The sphere no longer rotates downwards as it did at the start, but upwards to the right.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.*;
import javafx.stage.Stage;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.util.ArrayList;
public class TestFX extends Application {
public static final float WIDTH = 1400;
public static final float HEIGHT = 1000;
private final ArrayList<String> input = new ArrayList<>();
private final Sphere sphere = new Sphere(500);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Flugzeug flieger = new Flugzeug(30, 30, 50);
flieger.setMaterial(new PhongMaterial(Color.GREEN));
flieger.setTranslateZ(330);
flieger.getTransforms().add(new Rotate(20, Rotate.X_AXIS));
sphere.translateZProperty().set(710);
sphere.translateYProperty().set(420);
PhongMaterial phongMaterial = new PhongMaterial();
phongMaterial.setDiffuseMap(new Image(getClass().getResourceAsStream("Earth_diffuse_8k.png")));
sphere.setMaterial(phongMaterial);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(40000);
Group root = new Group(flieger, sphere);
Scene scene = new Scene(root, WIDTH, HEIGHT, true);
scene.setCamera(camera);
primaryStage.setTitle("FliegenFX");
primaryStage.setScene(scene);
primaryStage.show();
new AnimationTimer() {
#Override
public void handle(long now) {
initGameLogic(flieger);
dreheErde(flieger, sphere);
}
}.start();
scene.setOnKeyPressed(event -> {
String code = event.getCode().toString();
if (!input.contains(code))
input.add(code);
});
scene.setOnKeyReleased(event -> {
String code = event.getCode().toString();
input.remove(code);
});
}
// TODO Erddrehung nach Drehung beibehalten
private void dreheErde(Flugzeug flieger, Sphere erde) {
double rotationFactor = 0.005;
double drehung = flieger.getDrehung() * rotationFactor;
double amplitude = 2;
double frequency = 0.001;
float angle = (float) (Math.sin(drehung * frequency) * amplitude);
Quaternionf quat = new Quaternionf();
Matrix4f matrix4f = new Matrix4f();
Affine affine = new Affine();
quat.rotateY(angle);
quat.rotateZ(angle);
quat.rotateX(-0.001f);
quat.normalize();
quat.get(matrix4f);
float[] matrixArray = new float[16];
matrix4f.get(matrixArray);
double[] matrix = new double[16];
for (int i = 0 ; i < matrixArray.length; i++)
{
matrix[i] = matrixArray[i];
}
affine.append(matrix, MatrixType.MT_3D_4x4, 0);
erde.getTransforms().add(affine);
}
private void initGameLogic(Flugzeug flieger) {
if (input.contains("LEFT")) {
flieger.rotateByZ(-0.5);
}
if (input.contains("RIGHT")) {
flieger.rotateByZ(0.5);
}
}
private class Flugzeug extends Box{
private double drehung = 0;
private Rotate r;
private Transform t = new Rotate();
public Flugzeug(double width, double height, double depth){
super(width, height, depth);
}
public void rotateByZ(double ang){
drehung += ang;
r = new Rotate(ang, Rotate.Z_AXIS);
t = t.createConcatenation(r);
getTransforms().clear();
getTransforms().addAll(t);
}
public double getDrehung() {
return drehung;
}
}
}
I have tried many different ways with trigonometry but they have not been successful. This was for example to move the X-axis of the sphere also according to the sine or to subtract the movements of the Y and Z axis of the sphere as cosine.
I think it has to be transformed in a Rotation Matrix, but i‘ve never had matrix calculation.
Rotating a Group inside another Group in 3d scene
In this approach Box instance doesn't seem to move 'cause it's moving with the camera . Camera and box are moving together because is it's Group parent who is rotating with keyboard event . The Sphere node is in another group and is not affected . Sphere itself is rotating in Y axis
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
Shape3D sphere = new Sphere(10);
PhongMaterial mat = new PhongMaterial();
mat.setDiffuseMap(new Image("https://www.h-schmidt.net/map/map.jpg"));
sphere.setMaterial(mat);
RotateTransition sphereRotation = new RotateTransition(Duration.seconds(40), sphere);
sphereRotation.setAxis(Rotate.Y_AXIS);
sphereRotation.setToAngle(360);
sphereRotation.setInterpolator(Interpolator.LINEAR);
sphereRotation.setCycleCount(Animation.INDEFINITE);
sphereRotation.play();
Shape3D box = new Box(2, 2, 2);
box.setMaterial(new PhongMaterial(Color.ORANGE));
box.setTranslateZ(-11);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-30);
var planeGroup = new Group(box, camera);
Group sphereGroup = new Group(sphere);
Group group3d = new Group(planeGroup, sphereGroup);
Scene scene = new Scene(group3d, 640, 480, true, SceneAntialiasing.BALANCED);
scene.setOnKeyPressed((t) -> {
if (t.getCode() == KeyCode.UP) {
Rotate r = new Rotate(-2);
r.setAxis(Rotate.X_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.LEFT) {
Rotate r = new Rotate(2);
r.setAxis(Rotate.Y_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.RIGHT) {
Rotate r = new Rotate(-2);
r.setAxis(Rotate.Y_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.DOWN) {
Rotate r = new Rotate(2);
r.setAxis(Rotate.X_AXIS);
planeGroup.getTransforms().add(r);
}
});
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I am new to JavaFX and I am creating a simple program. What I'm trying to achieve is to create a ball every 5 seconds that bounces off the walls and all balls should move every tens times per second (10 milliseconds). Also, feel free to leave other suggestions about my code.
Here's the source code:
public class Main extends Application {
#Override
public void start(Stage stage) {
//Sets the title, adds a group, and background color
BorderPane canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
Circle ball = new Circle(10, Color.RED);
ball.relocate(0, 10);
canvas.getChildren().add(ball);
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(20), new EventHandler<ActionEvent>() {
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
#Override
public void handle(ActionEvent t) {
//move the ball
ball.setLayoutX(ball.getLayoutX() + dx);
ball.setLayoutY(ball.getLayoutY() + dy);
Bounds bounds = canvas.getBoundsInLocal();
//If the ball reaches the left or right border make the step negative
if(ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius()) ){
dx = -dx;
}
//If the ball reaches the bottom or top border make the step negative
if((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))){
dy = -dy;
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Here is one way to do it as suggested in comments:
Create a custom object that holds the ball and its orientation information.
Add the newly created object in the list and the ball in canvas.
Loop through all balls and position them based on their orientation information.
When your desired time is reached add a new ball.
To keep it simple, you don't need any concurrent related stuff. Maybe using them will improve the implementation (but I am not touching that now). :-)
Here is a demo of using the points I mentioned.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
public class CanvasBallCreation_Demo extends Application {
List<Ball> balls = new ArrayList<>();
BorderPane canvas;
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
double refresh = 20;//ms
double addBallDuration = 5000;//ms
double temp = 0;
SecureRandom rnd = new SecureRandom();
#Override
public void start(Stage stage) {
canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
addBall();
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(refresh), e->moveBalls()));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private void addBall(){
Ball ball = new Ball();
balls.add(ball);
canvas.getChildren().add(ball.getBall());
}
private void moveBalls() {
temp = temp + refresh;
if(temp>addBallDuration){
temp = 0;
addBall();
}
Bounds bounds = canvas.getBoundsInLocal();
balls.forEach(obj -> {
Circle ball = obj.getBall();
double tx = obj.getTx();
double ty = obj.getTy();
ball.setLayoutX(ball.getLayoutX() + dx*tx);
ball.setLayoutY(ball.getLayoutY() + dy*ty);
//If the ball reaches the left or right border make the step negative
if (ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius())) {
obj.setTx(-tx);
}
//If the ball reaches the bottom or top border make the step negative
if ((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))) {
obj.setTy(-ty);
}
});
}
class Ball {
Circle ball = new Circle(10, Color.RED);
double tx = 1;
double ty = 1;
public Ball(){
// Placing the ball at a random location between 0,0 and 10,10 to generate random paths
ball.relocate(rnd.nextInt(10), rnd.nextInt(10));
}
public Circle getBall() {
return ball;
}
public double getTx() {
return tx;
}
public void setTx(double tx) {
this.tx = tx;
}
public double getTy() {
return ty;
}
public void setTy(double ty) {
this.ty = ty;
}
}
}
There are a few options, java.util.concurrent.TimeUnit or Thread.sleep.
You may want to look at both, and if you need two operations going at once (creating new balls, balls moving) I would look into using a new thread for each ball.
You can read more about using threads by using these articles, and your simple program actually seems like a great use case for learning about them.
https://www.geeksforgeeks.org/multithreading-in-java/
https://www.tutorialspoint.com/java/java_multithreading.htm
https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
I created a star plotting application to plot labels for a 3d star plotter so that the labels are mapped local to scene. I wanted to do this so that whenever I rotate the starfield, the labels are always facing forward and don't rotate with the individual stars (annoying because they can't be read).
Now this works very well until I added a control panel above it. In the application below, if the control panel is not present, then labels track with the position of the stars. But with the control panel, the labels are y-offset by the size of the control panel height.
The issue is that the "mousePosY = me.getSceneY();" returns the mouse position of the scene itself and not the defined subScene. I had thought that the "subScene.setOnMouseDragged((MouseEvent me) -> {" would have given me the position of the mouse relative to the subScene and not the scene.
Is there any way to fix this so that the returned X,Y position is for the subScene only?
The following code is a very cutdown version of my application that shows the actual issue itself.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Random;
import static org.fxyz3d.geometry.MathUtils.clamp;
/**
* example for flat labels
*/
#Slf4j
public class StarFieldExample extends Application {
public static final int SCALE_X = 510;
public static final int SCALE_Y = 540;
public static final int SCALE_Z = 0;
final double sceneWidth = 600;
final double sceneHeight = 600;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
private final Font font = new Font("arial", 10);
// We'll use custom Rotate transforms to manage the coordinate conversions
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rotateZ = new Rotate(0, Rotate.Z_AXIS);
private final Group root = new Group();
private final Group world = new Group(); //all 3D nodes in scene
private final Group labelGroup = new Group(); //all generic 3D labels
//All shapes and labels linked via hash for easy update during camera movement
private final HashMap<Node, Label> shape3DToLabel = new HashMap<>();
private SubScene subScene;
////// support
private final Random random = new Random();
private final static double RADIUS_MAX = 7;
private final static double X_MAX = 300;
private final static double Y_MAX = 300;
private final static double Z_MAX = 300;
private final Label scaleLabel = new Label("Scale: 5 ly");
public Pane createStarField() {
// attach our custom rotation transforms so we can update the labels dynamically
world.getTransforms().addAll(rotateX, rotateY, rotateZ);
subScene = new SubScene(world, sceneWidth, sceneHeight, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.BLACK);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-1000);
subScene.setCamera(camera);
Group sceneRoot = new Group(subScene);
sceneRoot.getChildren().add(labelGroup);
generateRandomStars(5);
handleMouseEvents();
// add to the 2D portion of this component
Pane pane = new Pane();
pane.setPrefSize(sceneWidth, sceneHeight);
pane.setMaxSize(Pane.USE_COMPUTED_SIZE, Pane.USE_COMPUTED_SIZE);
pane.setMinSize(Pane.USE_COMPUTED_SIZE, Pane.USE_COMPUTED_SIZE);
pane.setBackground(Background.EMPTY);
pane.getChildren().add(sceneRoot);
pane.setPickOnBounds(true);
subScene.widthProperty().bind(pane.widthProperty());
subScene.heightProperty().bind(pane.heightProperty());
Platform.runLater(this::updateLabels);
return (pane);
}
private void handleMouseEvents() {
subScene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
);
subScene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 5.0;
double modifierFactor = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isAltDown()) { //roll
rotateZ.setAngle(((rotateZ.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
} else {
rotateY.setAngle(((rotateY.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
rotateX.setAngle(
clamp(
(((rotateX.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180),
-60,
60
)
); // -
}
}
updateLabels();
}
);
}
public void generateRandomStars(int numberStars) {
for (int i = 0; i < numberStars; i++) {
double radius = random.nextDouble() * RADIUS_MAX;
Color color = randomColor();
double x = random.nextDouble() * X_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
double y = random.nextDouble() * Y_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
double z = random.nextDouble() * Z_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
String labelText = "Star " + i;
boolean fadeFlag = random.nextBoolean();
createSphereLabel(radius, x, y, z, color, labelText, fadeFlag);
}
//Add to hashmap so updateLabels() can manage the label position
scaleLabel.setFont(new Font("Arial", 15));
scaleLabel.setTextFill(Color.WHEAT);
scaleLabel.setTranslateX(SCALE_X);
scaleLabel.setTranslateY(SCALE_Y);
scaleLabel.setTranslateZ(SCALE_Z);
labelGroup.getChildren().add(scaleLabel);
log.info("shapes:{}", shape3DToLabel.size());
}
private Color randomColor() {
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return Color.rgb(r, g, b);
}
private void createSphereLabel(double radius, double x, double y, double z, Color color, String labelText, boolean fadeFlag) {
Sphere sphere = new Sphere(radius);
sphere.setTranslateX(x);
sphere.setTranslateY(y);
sphere.setTranslateZ(z);
sphere.setMaterial(new PhongMaterial(color));
//add our nodes to the group that will later be added to the 3D scene
world.getChildren().add(sphere);
Label label = new Label(labelText);
label.setTextFill(color);
label.setFont(font);
labelGroup.getChildren().add(label);
//Add to hashmap so updateLabels() can manage the label position
shape3DToLabel.put(sphere, label);
}
private void updateLabels() {
shape3DToLabel.forEach((node, label) -> {
Point3D coordinates = node.localToScene(Point3D.ZERO, true);
//Clipping Logic
//if coordinates are outside of the scene it could
//stretch the screen so don't transform them
double x = coordinates.getX();
double y = coordinates.getY();
// is it left of the view?
if (x < 0) {
x = 0;
}
// is it right of the view?
if ((x + label.getWidth() + 5) > subScene.getWidth()) {
x = subScene.getWidth() - (label.getWidth() + 5);
}
// is it above the view?
if (y < 0) {
y = 0;
}
// is it below the view
if ((y + label.getHeight()) > subScene.getHeight()) {
y = subScene.getHeight() - (label.getHeight() + 5);
}
//update the local transform of the label.
label.getTransforms().setAll(new Translate(x, y));
});
scaleLabel.setTranslateX(SCALE_X);
scaleLabel.setTranslateY(SCALE_Y);
scaleLabel.setTranslateZ(SCALE_Z);
}
//////////////////////////////////
#Override
public void start(Stage primaryStage) throws Exception {
Pane controls = createControls();
Pane pane = createStarField();
VBox vBox = new VBox(
controls,
pane
);
root.getChildren().add(vBox);
Scene scene = new Scene(root, sceneWidth, sceneHeight - 40);
primaryStage.setTitle("2D Labels over 3D SubScene");
primaryStage.setScene(scene);
primaryStage.show();
}
private VBox createControls() {
VBox controls = new VBox(10, new Button("Button"));
controls.setPadding(new Insets(10));
return controls;
}
public static void main(String[] args) {
launch(args);
}
}
I broke this down into a simpler problem relating mouse offset, solved that, and then worked that solution into here.
The key issue is that I assumed that the mouse event (x,y) positions would be zero offset to the SubScene, and in reality, they are relative to the containing scene. I had to calculate the bounds of the component relative to the parent. Having the actual location of the SubScene in the layout of the larger application allows me to calculate the corrected (x,y) positions.
Here is the answer:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.scene.text.Font;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static org.fxyz3d.geometry.MathUtils.clamp;
/**
* example for flat labels
*/
#Slf4j
public class StarFieldExample extends Application {
public static final int SCALE_X = 510;
public static final int SCALE_Y = 540;
public static final int SCALE_Z = 0;
final double sceneWidth = 600;
final double sceneHeight = 600;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
private final Font font = new Font("arial", 10);
// We'll use custom Rotate transforms to manage the coordinate conversions
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);
private final Rotate rotateZ = new Rotate(0, Rotate.Z_AXIS);
private final Group root = new Group();
private final Group world = new Group(); //all 3D nodes in scene
private final Group labelGroup = new Group(); //all generic 3D labels
//All shapes and labels linked via hash for easy update during camera movement
private final Map<Node, Label> shape3DToLabel = new HashMap<>();
private SubScene subScene;
////// support
private final Random random = new Random();
private final static double RADIUS_MAX = 7;
private final static double X_MAX = 300;
private final static double Y_MAX = 300;
private final static double Z_MAX = 300;
Pane pane;
private final Label scaleLabel = new Label("Scale: 5 ly");
public Pane createStarField() {
PerspectiveCamera camera = setupPerspectiveCamera();
// attach our custom rotation transforms so we can update the labels dynamically
world.getTransforms().addAll(rotateX, rotateY, rotateZ);
subScene = new SubScene(world, sceneWidth, sceneHeight, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.BLACK);
subScene.setCamera(camera);
Group sceneRoot = new Group(subScene);
sceneRoot.getChildren().add(labelGroup);
generateRandomStars(5);
// add to the 2D portion of this component
pane = new Pane();
pane.setPrefSize(sceneWidth, sceneHeight);
pane.setMaxSize(Pane.USE_COMPUTED_SIZE, Pane.USE_COMPUTED_SIZE);
pane.setMinSize(Pane.USE_COMPUTED_SIZE, Pane.USE_COMPUTED_SIZE);
pane.setBackground(Background.EMPTY);
pane.getChildren().add(sceneRoot);
pane.setPickOnBounds(true);
subScene.widthProperty().bind(pane.widthProperty());
subScene.heightProperty().bind(pane.heightProperty());
Platform.runLater(this::updateLabels);
handleMouseEvents();
return (pane);
}
#NotNull
private PerspectiveCamera setupPerspectiveCamera() {
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setTranslateZ(-1000);
return camera;
}
private void handleMouseEvents() {
subScene.setOnMousePressed((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
);
subScene.setOnMouseDragged((MouseEvent me) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 5.0;
double modifierFactor = 0.1;
if (me.isPrimaryButtonDown()) {
if (me.isAltDown()) { //roll
rotateZ.setAngle(((rotateZ.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
} else {
rotateY.setAngle(((rotateY.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
rotateX.setAngle(
clamp(
(((rotateX.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180),
-60,
60
)
); // -
}
}
updateLabels();
}
);
}
private void updateLabels() {
shape3DToLabel.forEach((node, label) -> {
Point3D coordinates = node.localToScene(Point3D.ZERO, true);
//Clipping Logic
//if coordinates are outside of the scene it could
//stretch the screen so don't transform them
double xs = coordinates.getX();
double ys = coordinates.getY();
Bounds ofParent = pane.getBoundsInParent();
double x = xs - ofParent.getMinX();
double y = ys - ofParent.getMinY();
// is it left of the view?
if (x < 0) {
x = 0;
}
// is it right of the view?
if ((x + label.getWidth() + 5) > subScene.getWidth()) {
x = subScene.getWidth() - (label.getWidth() + 5);
}
// is it above the view?
if (y < 0) {
y = 0;
}
// is it below the view
if ((y + label.getHeight()) > subScene.getHeight()) {
y = subScene.getHeight() - (label.getHeight() + 5);
}
//update the local transform of the label.
label.getTransforms().setAll(new Translate(x, y));
});
scaleLabel.setTranslateX(SCALE_X);
scaleLabel.setTranslateY(SCALE_Y);
scaleLabel.setTranslateZ(SCALE_Z);
}
public void generateRandomStars(int numberStars) {
for (int i = 0; i < numberStars; i++) {
double radius = random.nextDouble() * RADIUS_MAX;
Color color = randomColor();
double x = random.nextDouble() * X_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
double y = random.nextDouble() * Y_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
double z = random.nextDouble() * Z_MAX * 2 / 3 * (random.nextBoolean() ? 1 : -1);
String labelText = "Star " + i;
boolean fadeFlag = random.nextBoolean();
createSphereLabel(radius, x, y, z, color, labelText, fadeFlag);
}
//Add to hashmap so updateLabels() can manage the label position
scaleLabel.setFont(new Font("Arial", 15));
scaleLabel.setTextFill(Color.WHEAT);
scaleLabel.setTranslateX(SCALE_X);
scaleLabel.setTranslateY(SCALE_Y);
scaleLabel.setTranslateZ(SCALE_Z);
labelGroup.getChildren().add(scaleLabel);
log.info("shapes:{}", shape3DToLabel.size());
}
private Color randomColor() {
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return Color.rgb(r, g, b);
}
private void createSphereLabel(double radius, double x, double y, double z, Color color, String labelText, boolean fadeFlag) {
Sphere sphere = new Sphere(radius);
sphere.setTranslateX(x);
sphere.setTranslateY(y);
sphere.setTranslateZ(z);
sphere.setMaterial(new PhongMaterial(color));
//add our nodes to the group that will later be added to the 3D scene
world.getChildren().add(sphere);
Label label = new Label(labelText);
label.setTextFill(color);
label.setFont(font);
labelGroup.getChildren().add(label);
//Add to hashmap so updateLabels() can manage the label position
shape3DToLabel.put(sphere, label);
}
//////////////////////////////////
#Override
public void start(Stage primaryStage) throws Exception {
Pane controls = createControls();
Pane pane = createStarField();
VBox vBox = new VBox(
controls,
pane
);
root.getChildren().add(vBox);
Scene scene = new Scene(root, sceneWidth, sceneHeight - 40);
primaryStage.setTitle("2D Labels over 3D SubScene");
primaryStage.setScene(scene);
primaryStage.show();
}
private VBox createControls() {
VBox controls = new VBox(10, new Button("Button"));
controls.setPadding(new Insets(10));
return controls;
}
public static void main(String[] args) {
launch(args);
}
}
I'm making an application with javaFX. The application consists of creating a kind of "graph" from the user.
The user through a button creates a node (is created by a circle with JAVAFX figure) and associates it to a variable, repeating the process, creating another node, and so on. Now I need to figure out how to define the node position inside a specially reserved space. Obviously, after creating nodes, through another button, the user creates the arcs (is created by a line connecting two nodes) associated with the nodes, thus defining a graph.
My problem is that I do not understand how to indicate the position of lines that will act as arcs in my graph.
Please help me.I'm not very experienced and I'm trying to tackle this problem.
First of all you need to tell us what is your "reserved space"? If it's a Canvas then you can draw shapes with Canva's GraphicsContext.
Canvas canvas = new Canvas(300, 250);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillOval(10, 60, 30, 30);
gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN);
Otherwise if you are working inside a pane with layout you need to know if the components are managed or not. For example inside a Pane or AnchorPane the node's automatic layout is disabled, thus you need to specify their layoutX and layoutY by yourself (+ node dimensions) like :
node.setLayoutX(12);
node.setLayoutY(222);
node.setPrefWidth(500);
node.setPrefHeight(500);
If you are using a pane like VBox which manage the layout of it's nodes you need to set the nodes inside the pane to be unmanaged in order for you to apply specific transformations. You can do that just by setting :
node.setManaged(false)
I will not recommend you to use Canvas cause handling the shapes will be very hard, for example if you need to remove something you probably have to clear everything and redraw only the visible shapes.
Well I had some time so here is a small example ( It might not be the optimal solution, but you can take is as a reference)
GraphTest
import java.util.ArrayList;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class GraphTest extends Application {
private double orgSceneX, orgSceneY;
private double orgTranslateX, orgTranslateY;
private Group root = new Group();
#Override
public void start(Stage primaryStage) throws Exception {
GraphNode node1 = createNode("A", 100, 100, Color.RED);
GraphNode node2 = createNode("B", 300, 200, Color.GREEN);
GraphNode node3 = createNode("C", 80, 300, Color.PURPLE);
connectNodes(node1, node2, "C1");
connectNodes(node3, node1, "C2");
connectNodes(node3, node2, "C3");
root.getChildren().addAll(node1, node2, node3);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
private void connectNodes(GraphNode node1, GraphNode node2, String edgeText) {
Line edgeLine = new Line(node1.getCenterX(), node1.getCenterY(), node2.getCenterX(), node2.getCenterY());
Label edgeLabel = new Label(edgeText);
node1.addNeighbor(node2);
node2.addNeighbor(node1);
node1.addEdge(edgeLine, edgeLabel);
node2.addEdge(edgeLine, edgeLabel);
root.getChildren().addAll(edgeLine, edgeLabel);
}
private GraphNode createNode(String nodeName, double xPos, double yPos, Color color) {
GraphNode node = new GraphNode(nodeName, xPos, yPos, color);
node.setOnMousePressed(circleOnMousePressedEventHandler);
node.setOnMouseDragged(circleOnMouseDraggedEventHandler);
return node;
}
EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
GraphNode node = (GraphNode) t.getSource();
orgTranslateX = node.getTranslateX();
orgTranslateY = node.getTranslateY();
}
};
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;
GraphNode node = (GraphNode) t.getSource();
node.setTranslateX(newTranslateX);
node.setTranslateY(newTranslateY);
updateLocations(node);
}
};
private void updateLocations(GraphNode node) {
ArrayList<GraphNode> connectedNodes = node.getConnectedNodes();
ArrayList<Line> edgesList = node.getEdges();
for (int i = 0; i < connectedNodes.size(); i++) {
GraphNode neighbor = connectedNodes.get(i);
Line l = edgesList.get(i);
l.setStartX(node.getCenterX());
l.setStartY(node.getCenterY());
l.setEndX(neighbor.getCenterX());
l.setEndY(neighbor.getCenterY());
}
}
public static void main(String[] args) {
launch(args);
}
}
GraphNode
import java.util.ArrayList;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
public class GraphNode extends StackPane {
private Circle circle;
private Label text;
private ArrayList<GraphNode> connectedNodesList = new ArrayList<>();
private ArrayList<Line> edgesList = new ArrayList<>();
private ArrayList<Label> edgesLabelList = new ArrayList<>();
private double radius = 50.0;
public GraphNode(String name, double xPos, double yPos, Color color) {
circle = new Circle(radius, color);
text = new Label(name);
text.setTextFill(Color.WHITE);
setLayoutX(xPos);
setLayoutY(yPos);
getChildren().addAll(circle, text);
layout();
}
public void addNeighbor(GraphNode node) {
connectedNodesList.add(node);
}
public void addEdge(Line edgeLine, Label edgeLabel) {
edgesList.add(edgeLine);
edgesLabelList.add(edgeLabel);
// If user move the node we should translate the edge labels as well
// one way of doing that is by make a custom binding to the layoutXProperty as well
// as to layoutYProperty. We will listen for changes to the currentNode translate properties
// and for changes of our neighbor.
edgeLabel.layoutXProperty().bind(new DoubleBinding() {
{
bind(translateXProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateXProperty());
}
#Override
protected double computeValue() {
// We find the center of the line to translate the text
double width = edgeLine.getEndX() - edgeLine.getStartX();
return edgeLine.getStartX() + width / 2.0;
}
});
edgeLabel.layoutYProperty().bind(new DoubleBinding() {
{
bind(translateYProperty());
bind(connectedNodesList.get(connectedNodesList.size() - 1).translateYProperty());
}
#Override
protected double computeValue() {
double width = edgeLine.getEndY() - edgeLine.getStartY();
return edgeLine.getStartY() + width / 2.0;
}
});
}
public ArrayList<GraphNode> getConnectedNodes() {
return connectedNodesList;
}
public ArrayList<Line> getEdges() {
return edgesList;
}
public double getX() {
return getLayoutX() + getTranslateX();
}
public double getY() {
return getLayoutY() + getTranslateY();
}
public double getCenterX() {
return getX() + radius;
}
public double getCenterY() {
return getY() + radius;
}
}
And the Output would look like that :
You can move the nodes with your mouse and you will see all the labels will follow the shapes locations ( Cicle , Lines )
I want to make a program in javafx in which when a button is pressed rectangle or circle is created with random dimension and color. I have written code but facing some problem. In it when I'm clicking button, figure is appearing but when i click it again figure stays same. After 3-4 clicks figure is changing.
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.scene.image.*;
import javafx.collections.*;
import javafx.geometry.*;
import javafx.event.*;
import java.util.*;
public class ranshap extends Application
{
double x1,y1,x2,y2;
Rectangle rectangle;
Circle circle;
Color rectangleColor;
Color circleColor;
Button b;
Pane pane;
Scene scene;
Stage stage;
public void start (Stage stage)
{
stage.setTitle("Drawing Rectangle and Circle");
CreateRect c= new CreateRect();
b= new Button("Click me");
b.setOnAction(c);
rectangle = new Rectangle();
circle = new Circle();
/*rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(200);
rectangle.setHeight(50);
rectangleColor= new Color(0.0,0.8,0.2,0.6);
rectangle.setFill(rectangleColor);*/
pane= new Pane();
pane.getChildren().add(b);
scene= new Scene(pane,500,300);
stage.setScene(scene);
stage.show();
}
private class CreateRect implements EventHandler <ActionEvent>
{
public void handle(ActionEvent e)
{
double width,height,r,g,b,o,s,radius;
Random generator= new Random();
s=generator.nextDouble();
if(s>0.5)
{
pane.getChildren().remove(circle);
pane.getChildren().add(rectangle);
width= generator.nextDouble()*100;
height= generator.nextDouble()*100;
r= generator.nextDouble();
g= generator.nextDouble();
b= generator.nextDouble();
o= generator.nextDouble();
rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(width);
rectangle.setHeight(height);
rectangleColor= new Color(r,g,b,o);
rectangle.setFill(rectangleColor);
}
else
{
pane.getChildren().remove(rectangle);
pane.getChildren().add(circle);
radius=generator.nextDouble()*100;
circle.setCenterX (100);
circle.setCenterY (100);
circle.setRadius (radius);
r= generator.nextDouble();
g= generator.nextDouble();
b= generator.nextDouble();
o= generator.nextDouble();
circleColor= new Color(r,g,b,o);
circle.setFill(circleColor);
}
}
}
}
Actrully, you add the same children twice. Using the following code
public class ranshap extends Application {
double x1, y1, x2, y2;
Rectangle rectangle;
Circle circle;
Color rectangleColor;
Color circleColor;
Button b;
Pane pane;
Scene scene;
Stage stage;
#Override
public void start(Stage stage) {
stage.setTitle("Drawing Rectangle and Circle");
CreateRect c = new CreateRect();
b = new Button("Click me");
b.setOnAction(c);
rectangle = new Rectangle();
circle = new Circle();
/*
* rectangle.setX(50); rectangle.setY(50); rectangle.setWidth(200); rectangle.setHeight(50); rectangleColor= new
* Color(0.0,0.8,0.2,0.6); rectangle.setFill(rectangleColor);
*/
pane = new Pane();
pane.getChildren().add(b);
scene = new Scene(pane, 500, 300);
stage.setScene(scene);
stage.show();
}
private class CreateRect implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent e) {
double width, height, r, g, b, o, s, radius;
Random generator = new Random();
s = generator.nextDouble();
pane.getChildren().remove(rectangle);
pane.getChildren().remove(circle);
if (s > 0.5) {
pane.getChildren().add(rectangle);
width = generator.nextDouble() * 100;
height = generator.nextDouble() * 100;
r = generator.nextDouble();
g = generator.nextDouble();
b = generator.nextDouble();
o = generator.nextDouble();
rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(width);
rectangle.setHeight(height);
rectangleColor = new Color(r, g, b, o);
rectangle.setFill(rectangleColor);
} else {
pane.getChildren().add(circle);
radius = generator.nextDouble() * 100;
circle.setCenterX(100);
circle.setCenterY(100);
circle.setRadius(radius);
r = generator.nextDouble();
g = generator.nextDouble();
b = generator.nextDouble();
o = generator.nextDouble();
circleColor = new Color(r, g, b, o);
circle.setFill(circleColor);
}
}
}
public static void main(String[] args) {
launch(args);
}
}