JavaFX: How to resize a rectangle with set aspect ratio - java

I have an app where user can draw a rectangle, and resize it or move it. I'm interested if is possible to some how bind rectangles width and height in some aspect ratio.
E.g. if aspect ratio is 1:2 that user can draw only that kinds of rectangles, or if is 1:1 that user can only draw square.
EDIT
My eventHandler for MOUSE_DRAGGEDevent looks something like this
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if (event.isSecondaryButtonDown())
return;
double offsetX = event.getX() - rectangleStartX;
double offsetY = event.getY() - rectangleStartY;
if (offsetX > 0) {
if (event.getX() > imageView.getFitWidth()) {
selectionRectangle.setWidth(imageView.getFitWidth() - rectangleStartX);
} else
selectionRectangle.setWidth(offsetX);
} else {
if (event.getX() < 0)
selectionRectangle.setX(0);
else
selectionRectangle.setX(event.getX());
selectionRectangle.setWidth(rectangleStartX - selectionRectangle.getX());
}
if (offsetY > 0) {
if (event.getY() > imageView.getFitHeight())
selectionRectangle.setHeight(imageView.getFitHeight() - rectangleStartY);
else
selectionRectangle.setHeight(offsetY);
} else {
if (event.getY() < 0)
selectionRectangle.setY(0);
else
selectionRectangle.setY(event.getY());
selectionRectangle.setHeight(rectangleStartY - selectionRectangle.getY());
}
};

This app demos how to adjust a Rectangle height and width based on a ratio. Comments in code.
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication122 extends Application
{
double orgSceneX, orgSceneY;//Used to help keep up with change in mouse position
#Override
public void start(Stage primaryStage)
{
double RATIO = .5;//The ration of height to width is 1/2
Rectangle rectangle = new Rectangle(100, 50);
rectangle.setX(400 - 50);
rectangle.setY(250 - 25);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setStroke(Color.BLACK);
//Circles will be used to do the event handling/movements
Circle leftAnchor = new Circle(400 - 50, 250, 5);
Circle topAnchor = new Circle(400, 250 - 25, 5);
leftAnchor.setOnMouseDragEntered((event) -> {
((Circle) event.getSource()).getScene().setCursor(Cursor.MOVE);
});
leftAnchor.setOnMousePressed((event) -> {
orgSceneX = event.getSceneX();//Store current mouse position
});
leftAnchor.setOnMouseDragged((event) -> {
double offSetX = event.getSceneX() - orgSceneX;//Find change in mouse X position
leftAnchor.setCenterX(event.getSceneX());
rectangle.setX(event.getSceneX());//move rectangle left side with mouse
rectangle.setWidth(rectangle.getWidth() - offSetX);//Change rectangle's width with movement of mouse
topAnchor.setCenterX(topAnchor.getCenterX() + offSetX / 2);//Adjust top circle as rectangle's size change
rectangle.setHeight(rectangle.getWidth() * RATIO);//Change the height so that it meets the ratio requirements
leftAnchor.setCenterY((rectangle.getY() + rectangle.getHeight()) - (rectangle.getHeight() / 2));//Adjust the left circle with the growth of the rectangle
orgSceneX = event.getSceneX();//save last mouse position to recalculate change in mouse postion as the circle moves
});
leftAnchor.setOnMouseExited((event) -> {
leftAnchor.getScene().setCursor(null);
});
topAnchor.setOnMouseDragEntered((event) -> {
topAnchor.getScene().setCursor(Cursor.MOVE);
});
topAnchor.setOnMousePressed((event) -> {
orgSceneY = event.getSceneY();//store current mouse position
});
topAnchor.setOnMouseDragged((event) -> {
double offSetY = event.getSceneY() - orgSceneY;
topAnchor.setCenterY(event.getSceneY());
rectangle.setY(event.getSceneY());//move rectangle top side with mouse
rectangle.setHeight(rectangle.getHeight() - offSetY);//Change rectangle's height with movement of mouse
leftAnchor.setCenterY(leftAnchor.getCenterY() + offSetY / 2);//Adjust left circle as rectangle's size change
rectangle.setWidth(rectangle.getHeight() * (1 / RATIO));//Change the width so that it meets the ratio requirements
topAnchor.setCenterX((rectangle.getX() + rectangle.getWidth()) - (rectangle.getWidth() / 2));//Adjust the top circle with the growth of the rectangle
orgSceneY = event.getSceneY();//save last mouse position to recalculate change in mouse postion as the circle moves
});
topAnchor.setOnMouseExited((event) -> {
topAnchor.getScene().setCursor(null);
});
Pane root = new Pane();
root.getChildren().addAll(rectangle, leftAnchor, topAnchor);
Scene scene = new Scene(root, 800, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

Related

How to lower the number of calling of drawFrame() in java?

I've been learning Java from a book called "Introduction to Java by David Eck". In that book, there is a Graphic with GUI chapter where the Rectangles are moved to the center. The book was explaining the stuff that was going on, but most importantly it says that "the drawFrame() subroutine will automatically be called about 60 times per second.". So, how can we lower the number of times the drawFrame() is called. The reason is I want to see the rectangles moving in slow motion.
This is the complete code:
package com.example.ch3movingrects;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.layout.BorderPane;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
/**
* When run as a program, this class opens a window on the screen that
* shows a set of nested rectangles that seems to be moving infinitely
* inward towards the center. The animation continues until the user
* closes the window.
*/
public class ch3MovingRects extends Application {
/**
* Draws a set of nested rectangles. This subroutine is called 60 times per
* second and is responsible for redrawing the entire drawing area. The
* parameter g is used for drawing. The frameNumber starts at zero and
* increases by 1 each time this subroutine is called. The parameters width
* and height give the size of the drawing area, in pixels.
* The sizes and positions of the rectangles that are drawn depend
* on the frame number, giving the illusion of motion.
*/
public void drawFrame(GraphicsContext g, int frameNumber, double elapsedSeconds, int width, int height) {
g.setFill(Color.WHITE);
g.fillRect(0,0,width,height); // Fill drawing area with white.
double inset; // Gap between edges of drawing area and the outer rectangle.
double rectWidth, rectHeight; // The size of one of the rectangles.
g.setStroke(Color.BLACK); // Draw the rectangle outlines in black.
inset = frameNumber % 15 + 0.5; // The "+ 0.5" is a technicality to produce a sharper image.
rectWidth = width - 2*inset;
rectHeight = height - 2*inset;
while (rectWidth >= 0 && rectHeight >= 0) {
g.strokeRect(inset, inset, rectWidth, rectHeight);
inset += 15; // rectangles are 15 pixels apart
rectWidth -= 30;
rectHeight -= 30;
}
}
//------ Implementation details: DO NOT EXPECT TO UNDERSTAND THIS ------
public void start(Stage stage) {
int width = 800; // The width of the image. You can modify this value!
int height = 600; // The height of the image. You can modify this value!
Canvas canvas = new Canvas(width,height);
drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height);
BorderPane root = new BorderPane(canvas);
root.setStyle("-fx-border-width: 4px; -fx-border-color: #444");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Infinte Moving Rects"); // STRING APPEARS IN WINDOW TITLEBAR!
stage.show();
stage.setResizable(false);
AnimationTimer anim = new AnimationTimer() {
private int frameNum;
private long startTime = -1;
private long previousTime;
public void handle(long now) {
if (startTime < 0) {
startTime = previousTime = now;
drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height);
}
else if (now - previousTime > 0.95e9/60) {
// The test in the else-if is to make sure that drawFrame() is
// called about once every 1/60 second. It is required since
// handle() can be called by the system more often than that.
frameNum++;
drawFrame(canvas.getGraphicsContext2D(), frameNum, (now-startTime)/1e9, width, height);
previousTime = now;
}
}
};
anim.start();
}
public static void main(String[] args) {
launch();
}
} // end MovingRects
Any ideas on how can we lower the number of calls to drawFrame subroutine?

3D Scatter Chart JavaFX: How to show legend and measures near axis

Looking this post, I've tried to implement in javaFX, with many difficulties, a Scatter Chart 3D where the grid is my x,y and z axis and the spheres are my points.
How Can I put a legend, axis labels and the range numbers along the axis? I can use only javaFX without external library.
I'm desperate.. I'm trying for days..without results
Please:help me
Thanks.
Code
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.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class GraphingData 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(150, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(120, 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);
root.setStyle( "-fx-border-color: red;");
// create bars
double gridSizeHalf = graphSize / 2;
double size = 30;
//Drawing a Sphere
Sphere sphere = new Sphere();
//Setting the properties of the Sphere
sphere.setRadius(10.0);
sphere.setTranslateX(-50);
sphere.setTranslateY(-50);
//Preparing the phong material of type specular color
PhongMaterial material6 = new PhongMaterial();
//setting the specular color map to the material
material6.setDiffuseColor(Color.GREEN);
sphere.setMaterial(material6);
grid.getChildren().addAll(sphere);
// 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.RED;
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);
}
}
Here's a basic idea to create some measures on the axes. It is not production-ready but should give you enough to start with.
private Group createGrid(int size) {
// existing code omitted...
cube.getChildren().addAll(cubeFaces);
double gridSizeHalf = size / 2;
double labelOffset = 30 ;
double labelPos = gridSizeHalf - labelOffset ;
for (double coord = -gridSizeHalf ; coord < gridSizeHalf ; coord+=50) {
Text xLabel = new Text(coord, labelPos, String.format("%.0f", coord));
xLabel.setTranslateZ(labelPos);
xLabel.setScaleX(-1);
Text yLabel = new Text(labelPos, coord, String.format("%.0f", coord));
yLabel.setTranslateZ(labelPos);
yLabel.setScaleX(-1);
Text zLabel = new Text(labelPos, labelPos, String.format("%.0f", coord));
zLabel.setTranslateZ(coord);
cube.getChildren().addAll(xLabel, yLabel, zLabel);
zLabel.setScaleX(-1);
}
return cube;
}
I would just place a legend outside the graph, which would just be a 2D grid pane not rotating...
I know this question is getting old but 2D labels in a JavaFX 3D scene is a topic that comes up a lot and I never see it answered "the right way".
Translating the labels like in James_D's answer will translate into 3D space a 2D label which will look correct until you move the camera. Assuming you want a scatter chart that doesn't move or rotate then this will be fine. Other wise you will need to automatically transform the 2D labels whenever you move your camera. (ie... the mouse handler). You could remove your scatter chart and readd the whole thing to the scene each time but that will be murder on your heap memory and won't be feasible for data sets of any real useful size.
The right way to do it is to use OpenGL or DirectDraw text renders which redraw the labels on each render loop pass but JavaFX 3D doesn't give you access (currently). So the "right way in JavaFX" is to float 2D labels on top of a 3D subscene and then translate them whenever the camera moves. This requires that you transform the 3D coordinate projection of the 3D location you want the label to a 2D screen projection.
To generically manage 2D labels connected to a Point3D in JavaFX 3D you need to do a transform along the following:
Point3D coordinates = node.localToScene(javafx.geometry.Point3D.ZERO);
SubScene oldSubScene = NodeHelper.getSubScene(node);
coordinates = SceneUtils.subSceneToScene(oldSubScene, coordinates);
double x = coordinates.getX();
double y = coordinates.getY();
label.getTransforms().setAll(new Translate(x, y));
Where the node is some actual 3D object already in the 3D subscene. For my applications I simply use a Sphere of an extremely small size it cannot be seen. If you were to follow James_D's example, you could translate the sphere(s) to the same locations that you translated the original axis labels.
The label is a standard JavaFX 2D label that you add to your scene... typically through a StackPane such that the labels are floating on top of the 3D subscene.
Now whenever the camera moves/rotates, this causes this transform to be called which slides the label on the 2D layer. Without direct access to the underlying GL or DD calls this is pretty much the only way to do something like this in JavaFX 3D but it works pretty well.
Here is a video example of it working.
Here is an open source example of implementing a simple version of floating 2D labels. (Warning, I'm the contributing author for the sample, not trying to promote the library.)

bullet trajectory

First of all, I want to shoot a plane with a cannon.
I've setted this Timeline for the trajectory, but I don't see the bullet on my Scene. It's very likely that my trajectory's code isn't correct. I tried to look on the internet about formula for projectile motion, but I understand nothing about physics;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Game_1 extends Application {
private final double gravity = 9.81;
private Timeline timeline;
private ImageView plane;
private Circle circle;
private AnchorPane ap;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
Group group = new Group();
Scene scene = new Scene(group, 600, 350);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
private void shoot() {
double x = 65.0f;
double y = 408;
double speed = 200;
double t = 2;
double angle = -45;
double dx = Math.cos(angle) * speed;
double dy = Math.sin(angle) * speed;
circle = new Circle(x, y, 5, Color.BLACK);
double x2 = x + dx * t;
double y2 = (Math.tan(angle) * y - (gravity / (2 * Math.pow(speed, 2) * Math.cos(angle))) * Math.pow(x, 2));
timeline = new Timeline();
KeyValue xKV = new KeyValue(circle.centerXProperty(), x2);
KeyValue yKV = new KeyValue(circle.centerYProperty(), y2, new Interpolator() {
#Override
protected double curve(double t) {
return y + dy * t - 0.5 * gravity * t * t;
}
});
KeyFrame xKF = new KeyFrame(Duration.seconds(t), xKV);
KeyFrame yKF = new KeyFrame(Duration.seconds(t), yKV);
timeline.getKeyFrames().addAll(xKF, yKF);
ap.getChildren().add(circle);
timeline.play();
collision();
}
private void collision() {
circle.boundsInParentProperty().addListener((ObservableValue<? extends Bounds> arg0, Bounds oldValue2, Bounds newValue2) -> {
if (circle.getBoundsInParent().intersects(plane.getBoundsInParent())) {
timeline.stop();
ap.getChildren().remove(circle);
}
});
}
}
The curve method should map to the interval [0, 1]. Your method however maps to much higher values. The value val at time t of a animation from t0 to t1 for a interpolator i given start value val0 and end value val1 is calculated as follows:
val = val0 + (val1 - val0) * i.curve((t - t0) / (t1 - t0))
The parameter of the curve method is the relative position in the time interval (0 = start of animation; 1 = end of animation). The result of the method is used to determine how close the value is to the end value (0 = still at the start value; 1 = at the end value).
Therefore you should probably calculate the top point hMax in the cannonball's curve (as described e.g. here on Wikipedia) and use a different interpolator:
Interpolator interpolator = new Interpolator() {
#Override
protected double curve(double t) {
// parabola with zeros at t=0 and t=1 and a maximum of 1 at t=0.5
return 4 * t * (1 - t);
}
};
KeyValue yKV = new KeyValue(circle.centerYProperty(), hMax, interpolator);
Note that upward movement means decreasing the y coordinate for the UI so in this case hMax should be smaller than the y value at the start.
Appart from that your shoot method is never called and some fields are not initialized which would result in a NPE in case it was called. Furthermore if those 2 issues are fixed, a black circle on a black background will be hard to see...
Example
Note that this is not using any physical fromulae and instead just uses some values chosen by me:
#Override
public void start(Stage primaryStage) {
Circle circle = new Circle(10);
circle.setManaged(false);
Pane pane = new Pane(circle);
circle.setCenterX(20);
circle.setCenterY(800);
Timeline timeline = new Timeline(new KeyFrame(Duration.ZERO,
new KeyValue(circle.centerXProperty(), 20),
new KeyValue(circle.centerYProperty(), 800)
), new KeyFrame(Duration.seconds(3),
new KeyValue(circle.centerXProperty(), 380),
new KeyValue(circle.centerYProperty(), 10, new Interpolator() {
#Override
protected double curve(double t) {
// parabola with zeros at t=0 and t=1 and a maximum of 1 at t=0.5
return 4 * t * (1 - t);
}
})
)
);
Scene scene = new Scene(pane, 400, 800);
scene.setOnMouseClicked(evt -> timeline.playFromStart());
primaryStage.setScene(scene);
primaryStage.show();
}
Note that Interpolator.curve is supposed to return 0 for parameter 0 and 1 for parameter 1. Anything else will probably result in jumps, should the property be animated further. Maybe the y-movement in 2 parts would be more appropriate, in case you want to move the ball around after the animation is finished.
I.e.
Interpolator 1: t * (2 - t)
Interpolator 2: t * t
using half the time interval each with end values of the top and start y coordinate of the curve respectively.

Lay nodes out in circle

I am trying to layout my nodes like this:
Here is my current layout, called CircularPane:
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
public class CircularPane extends Pane {
#Override
protected void layoutChildren() {
final int radius = 50;
final double increment = 360 / getChildren().size();
double degreese = 0;
for (Node node : getChildren()) {
double x = radius * Math.cos(Math.toRadians(degreese)) + getWidth() / 2;
double y = radius * Math.sin(Math.toRadians(degreese)) + getHeight() / 2;
layoutInArea(node, x - node.getBoundsInLocal().getWidth() / 2, y - node.getBoundsInLocal().getHeight() / 2, getWidth(), getHeight(), 0.0, HPos.LEFT, VPos.TOP);
degreese += increment;
}
}
}
Here is my main class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
CircularPane pane = new CircularPane();
for(int i = 0; i < 6; i++) {
Button button = new Button("" + i);
pane.getChildren().add(button);
}
stage.setScene(new Scene(pane));
stage.show();
}
}
And here is my current display:
The nodes are not at the bottom touching, they are equally spread out around the circle. I want to make it so they go to the bottom, but can't figure out how to.
Your approach to layout the buttons over a circle is correct, but in this line you are defining how they will be layouted:
final double increment = 360 / getChildren().size();
This gives the same angle between any two buttons refered from the center of the circle! And that's why you get your current display.
If you want to layout the nodes like in your figure, if I get it right, these are the conditions:
Every node has its center over the circle
The nodes are equally separated in horizontal: the horizontal gap goes from 0 to some value.
The initial gap from the circle to the first node goes from 0 to some value.
The size of each node may be adjusted to fulfill the previous conditions
So let's define some fields for those values, and adjust the size of the pane:
class CircularPane extends Pane {
private final double radius;
private final double ext_gap;
private final double int_gap;
public CircularPane(double radius, double ext_gap, double int_gap){
this.radius=radius;
this.ext_gap=ext_gap;
this.int_gap=int_gap;
setMinSize(2*radius, 2d*radius);
setPrefSize(2*radius, 2d*radius);
setMaxSize(2*radius, 2d*radius);
}
}
And now, given any n buttons, the above conditions can be turned into one single equation that solves the size of the node. If the total available length (2*radius) minus two exterior gaps (2*ext_gap) is the same as n buttons of size buttonSize and n-1 interior gaps (int_size), then, the size of every button has to be:
#Override
protected void layoutChildren() {
int n=getChildren().size();
double buttonSize = (2*radius-2*ext_gap-(n-1)*int_gap)/n;
}
Finally, now you can set the size of the button and layout every node, just by increasing the x coordinate (by the size of the button plus an inner gap), and then getting the y coordinate from the circle equation:
#Override
protected void layoutChildren() {
int n=getChildren().size();
double buttonSize = (2*radius-2*ext_gap-(n-1)*int_gap)/n;
double x=ext_gap+buttonSize/2d, y;
for (Node node : getChildren()) {
((Button)node).setMinSize(buttonSize, buttonSize);
((Button)node).setPrefSize(buttonSize, buttonSize);
((Button)node).setMaxSize(buttonSize, buttonSize);
node.setStyle("-fx-font-size: "+Math.round(buttonSize/3));
node.setManaged(false);
y=getHeight()/2d+Math.sqrt(radius*radius-Math.pow(x-radius,2d));
layoutInArea(node, x-buttonSize/2d, y-buttonSize/2d, getWidth(), getHeight(), 0.0, HPos.LEFT, VPos.TOP);
x+=buttonSize+int_gap;
}
}
Note that you can also change the size of the font, to get a visible number for any size of the button.
Note also that node.setManaged(false); avoids the calls to layoutChildren() when you click the buttons (due to changes in the size of the clicked button when being focused or clicked).
Finally this will create the circular pane and draw a circle:
#Override
public void start(Stage primaryStage) {
CircularPane pane = new CircularPane(200,20,10);
for(int i = 0; i < 6; i++) {
Button button = new Button("" + (i+1));
pane.getChildren().add(button);
}
Circle circle = new Circle(200);
circle.setFill(null);
circle.setStroke(Color.BLACK);
StackPane stack=new StackPane(circle,pane);
Scene scene = new Scene(stack, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
With this result:

How to keep moving circles inside the stage?

So ive got this code where im trying to get the moving circles to bounce on the walls so they dont go outside the stage. Ive tried to do it with the moveCircle method but i feel really out of my comfort zone.
import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Orientation;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimelineSample extends Application {
Timeline timeline;
private void init(Stage primaryStage) {
double height = primaryStage.getHeight();
double width = primaryStage.getWidth();
BorderPane root = new BorderPane();
primaryStage.setScene(new Scene(root, width, height));
double radius = 30;
Circle circle = new Circle(radius, radius, radius, Color.BLUE);
Circle circle2 = new Circle(radius, radius, radius, Color.RED);
Light.Distant light = new Light.Distant();
light.setAzimuth(-135.0);
Label label = new Label(
"Space för starta spelet\nSpace för att pausa spelet\nTryck på cirklarna för att byta färg på dem");
Label label2 = new Label("44");
root.setStyle("-fx-background-color: green;");
label2.setStyle(("-fx-padding : 100;"));
root.setBottom(label2);
root.setCenter(label);
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
ScrollBar sbSpeed = new ScrollBar();
sbSpeed.setMax(50);
sbSpeed.setValue(25);
sbSpeed.setOrientation(Orientation.VERTICAL);
circle.opacityProperty().bind(sbSpeed.valueProperty().divide(30));
circle2.opacityProperty().bind(sbSpeed.valueProperty().divide(30));
sbSpeed.setOnScroll(e -> {
circle.setTranslateX(+50);
});
circle.centerXProperty().bind(root.widthProperty().divide(2));
circle.centerYProperty().bind(root.heightProperty().divide(2));
circle.radiusProperty().bind(Bindings.min(root.widthProperty().divide(10),
root.heightProperty().divide(10)));
circle2.centerXProperty().bind(root.widthProperty().divide(2));
circle2.centerYProperty().bind(root.heightProperty().divide(2));
circle2.radiusProperty().bind(Bindings.min(root.widthProperty().divide(10),
root.heightProperty().divide(10)));
root.setTop(sbSpeed);
primaryStage.setWidth(bounds.getWidth() * 0.40);
primaryStage.setHeight(bounds.getHeight() * 0.40);
Lighting lighting = new Lighting();
lighting.setLight(light);
lighting.setSurfaceScale(5.0);
circle.setEffect(lighting);
circle2.setEffect(lighting);
timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.getKeyFrames().addAll
(new KeyFrame(Duration.ZERO, new KeyValue(circle.translateXProperty(),
0)),
new KeyFrame(new Duration(5000), new KeyValue(circle
.translateXProperty(), width - (radius * 2))));
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO, new KeyValue(
circle2.translateYProperty(), 0)),
new KeyFrame(new Duration(5000), new KeyValue(circle2
.translateYProperty(), height - (radius * 2))));
timeline.play();
root.getChildren().addAll(circle, circle2);
boolean a = true;
root.requestFocus();
root.setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.SPACE)) {
if (timeline.statusProperty().getValue().equals(Status.RUNNING)) {
timeline.pause();
} else
timeline.play();
}
});
circle.setOnMousePressed(event -> {
if (circle.getFill().equals(Color.BLACK))
circle.setFill(Color.YELLOW);
else if (circle.getFill().equals(Color.BLUE))
circle.setFill(Color.BROWN);
else if (circle.getFill().equals(Color.YELLOW))
circle.setFill(Color.BROWN);
else if (circle.getFill().equals(Color.BROWN))
circle.setFill(Color.BLACK);
else
circle.setFill(Color.BLUE);
});
circle2.setOnMousePressed(event -> {
if (circle2.getFill().equals(Color.BLACK))
circle2.setFill(Color.YELLOW);
else if (circle2.getFill().equals(Color.BLUE))
circle2.setFill(Color.BROWN);
else if (circle2.getFill().equals(Color.YELLOW))
circle2.setFill(Color.BROWN);
else if (circle2.getFill().equals(Color.BROWN))
circle2.setFill(Color.BLACK);
else
circle2.setFill(Color.BLUE);
// }
});
}
protected void moveCircle(Circle circle) {
if (circle.getCenterX() < circle.getRadius() ||
circle.getCenterX() > circle.getCenterY() - circle.getRadius()) {
circle.translateYProperty();
}
if (circle.getCenterY() < circle.getRadius() ||
circle.getCenterY() > circle.getCenterX() - circle.getRadius()) {
circle.translateXProperty();}
}
public void pause() {
timeline.pause();
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
just need to start the balls and it will work.
Now this is general pseudo code for something like this, although not specific to your problem this is the general way of making this "bounce" off the edge of the screen.
First off you need 5 pieces of information, the circles velocity (on x and y, as variables or a vector2), the circles radius (or diameter), the circle position (on x and y, as variables or a vector2) and the screens width and height.
Also depending on if the origin point of the circle is the center (you will need radius), bottom left or top left (you will need diameter). In my example we assume the origin is bang on the middle of the circle.
The generally idea goes something like this:
int windowWidth = 800, windowHeight = 600;
Circle c;
// Check if the left or right side of the circle leaves the screen bounds
// if so, reverse the velocity (mirror it)
if(c.x - c.radius < 0 || c.x + c.radius > windowWidth)
c.velocityX = -x.velocityX;
// Check if the top or bottom side of the circle leaves the screen bounds
// if so, reverse the velocity (mirror it)
if(c.y - c.radius < 0 || c.y + c.radius > windowHeight)
c.velocityY = -c.velocityY;
Hope that makes sense, it is a case of checking if the circle is passed the screen boundaries and simply mirroring the velocity on that given axis. So if the ball is moving at a velocity of 5,0 (directly right) and then goes beyond the window width, we want to take the velocity on x and negate it, so now the velocity becomes -5, 0 (directly left).
The one issue with this is the linear look, you can easily add some other variables like acceleration, restitution, friction and drag to give it a more realistic feel.

Categories

Resources