Getting Coordinates after Rotate Transition JavaFX - javafx

I'm writing a game like Wheel of Fortune, with the Wheel is actually an JavaFX ImageView lay outed to the right most of window. This wheel is made up of 15 piece (24 degree for each).
Basically, after RotateTransition finish I want to get the coordinate, or bounding box of something like this of the ImageView in order to calculate the angle between that coordinate, the center of the image, and the needle. From that angle I can determine the score returned.
However, I'm confused on how to do this. I've tried to play around with bounding box, but it just doesn't solve the problem.
The code for this is as follow :
public class FXMLRoundSceneController {
#FXML
private AnchorPane backgroundAnchorPane;
#FXML
private ImageView wheel;
/* Wheel background Image*/
private Image img = new Image(getClass().getResource("/resources/WOF.png").toExternalForm());
#FXML
public void initialize() {
wheel.setImage(img);
}
#FXML
private void rotateWheel(MouseEvent mv) {
rotate(1355,5); // Just an example, the rotate angle should be randomly calculated on each MouseClicked event.
}
public void rotate(double angle, double duration) {
RotateTransition rt = new RotateTransition();
rt.setNode(wheel);
rt.setByAngle(angle);
rt.setDuration(Duration.seconds(duration));
rt.setCycleCount(1);
rt.setAutoReverse(false);
rt.setInterpolator(Interpolator.EASE_OUT);
rt.play();
rt.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ae) {
wheelBoundingBox();
}
});
}
public void wheelBoundingBox() {
double MX = wheel.getBoundsInParent().getMaxX();
double MY = wheel.getBoundsInParent().getMaxY();
double mX = wheel.getBoundsInParent().getMinX();
double mY = wheel.getBoundsInParent().getMinY();
double width = MX - mX;
double height = MY - mY;
Rectangle bb = new Rectangle(mX, mY, Color.TRANSPARENT);
bb.setWidth(width);
bb.setHeight(height);
bb.setStroke(Color.WHITE);
bb.setStrokeWidth(1);
backgroundAnchorPane.getChildren().add(bb);
}
}
Can anyone tell me a proper way to determine the coordinate or something like that to help you determine the score.

public static void main(String[] args) {
String[] prizes = new String[] {"100","200","300","400","500","600"};
int slotSize = 360 / prizes.length;
int angle = 359;
for (int i = 0; i < 3600; i++) // 10 spins
{
angle++;
if (angle>359) {
angle = 0;
}
// based on the current angle, determine the entry:
int entry = angle / slotSize;
System.out.println("angle "+angle+" entry "+entry+" prize "+prizes[entry]);
}
}

Related

JavaFX - Get Actual Computed-Size of HBOX

I have a simple HBOX control which uses USE_COMPUTED_SIZE in Pref-Height, hence the size is all calculated and adjusted by the controls inside, which are a couple VBOX.
The issue comes when I try to add a new Pane as a children to the HBOX and draw a vertical line from top to bottom of the HBOX, so I write my line:
int startX = 5;
int startY = 0;
int endX = 5;
Line line = new Line(startX,startY,endX,hbox.getHeight());
Here, I need the hbox.getHeight(), but surprise: it is =-1, because it is using USE_COMPUTED_SIZE. So, how can I get the real (computed) value of hbox.getHeight()?
Not tested, but I think you can do something like:
public class HBoxWithLine extends HBox {
// Example of configurable property:
private final DoubleProperty lineOffset = new SimpleDoubleProperty(5);
public DoubleProperty lineOffsetProperty() {
return lineOffset ;
}
public final double getLineOffset() {
return lineOffsetProperty().get();
}
public final void setLineOffset(double lineOffset) {
lineOffsetProperty().set(lineOffset);
}
private final Line line = new Line();
public HBoxWithLine() {
getChildren().add(line);
// request layout when offset is invalidated:
lineOffset.addListener(obs -> requestLayout());
}
#Override
protected void layoutChildren() {
line.setStartX(getLineOffset());
line.setEndX(getLineOffset());
line.setStartY(0);
line.setEndY(getHeight());
super.layoutChildren();
}
}
Now you can just create a HBoxWithLine and add (additional) child nodes to it, set it's pref width and height to either fixed values, or Region.USE_COMPUTED_SIZE, etc., and it should just work.

Why does my image leave the screen if the X/Y is set to anything but 0?

I am trying to make the bouncing DVD logo as a means of learning to use timeline and keyframe in javaFX. The problem I am running in to is if I set the X/Y of the image to anything other than 0,0 the image will go further than the bounds of the screen. I am just confused on why this is happening and what I need to do to fix it. Thank you!
I have tried setting the image to different areas on the pane. I have tried subtracting more than just the dvd width and height to compensate. I have tried many things.
public class Main extends Application {
Stage window;
private final int WIDTH = 700;
private final int HEIGHT = 700;
private Timeline timeline;
private double xSpeed = 3;
private double ySpeed = 3;
private Parent createContent() {
Pane root = new Pane();
root.setPrefSize(WIDTH,HEIGHT);
ImageView dvd = new ImageView(new Image("/dvd.png"));
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setX(100);
dvd.setY(100);
dvd.setPreserveRatio(true);
timeline = new Timeline(new KeyFrame(Duration.millis(16), e-> {
dvd.setTranslateX(dvd.getTranslateX() + xSpeed);
dvd.setTranslateY(dvd.getTranslateY() + ySpeed);
if (xSpeed + dvd.getTranslateX() >= WIDTH - dvd.getFitWidth()){
xSpeed = -xSpeed;
} else if(xSpeed + dvd.getTranslateX() <= 0)
xSpeed = -xSpeed;
if (ySpeed + dvd.getTranslateY() >= HEIGHT - dvd.getFitHeight()){
ySpeed = -ySpeed;
} else if(ySpeed + dvd.getTranslateY() <= 0)
ySpeed = -ySpeed;
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(dvd);
return root;
}
#Override
public void start(Stage primaryStage) throws Exception{
window = primaryStage;
Scene mainScene = new Scene(createContent(),WIDTH,HEIGHT);
window.setResizable(false);
window.setTitle("Bouncing DVD");
window.setScene(mainScene);
window.show();
}
public static void main(String[] args) {
launch(args);
}
}
I expect to be able to place the DVD image anywhere on the screen and for it to bounce off of the walls of the scene.
The x and y properties of ImageView are ways of moving the ImageView from it's usual position without affecting the translate properties. Any changes of the rendering position by transforms such as the translate properties happen in addition to this change.
The x and y ranges where the image is rendered are [x+translateX, x+translateX+fitWidth) and[y+translateY, y+translateY+fitHeight) respectively.
The simplest way of fixing this issue is using only a single property per dimension, e.g. translateX and translateY:
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setTranslateX(100);
dvd.setTranslateY(100);

How to position node inside a rotated group at mouse event coordinates?

Given 2D scene with a node inside a group which contains a 2d rotate transformation. How do I position the node inside the group to the scene x and y coordinates of the mouse upon click?
The node that I am trying to move to the position of the click event is a circle which is located inside a group that has been rotated. The rotation happens at a pivot at the upper right corner of the group. The group has other nodes in it too.
I have been fiddling trying to achieve this for a while with no luck. It just does not position the node at the place where the click happened if the parent of the node is rotated. I have tried various techniques including the localToScene bounds with no luck.
Is there a way to do this? Thank you for your time =)
Here is some code showing a minimum verifiable example of the problem. Run it for a demo
You can drag the circle and select circles with mouse clicks. Do this to see it works fine as long as the group is not rotated.
In order to rotate the group use the left and right direction keys on your keyboard. After the group has been rotated the dragging and the mouse coordinates are no longer accurate!
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.scene.Group;
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.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class DemoBounds extends Application {
private static final int WIDTH = 600;
private static final int HEIGHT = 700;
private static final int CIRCLE_COUNT = 12;
private static final int RECTANGLE_COUNT = 3;
private static final int CIRCLE_DISTANCE = 150;
private static final int RECTANGLE_DISTANCE = 20;
private Color selectedColor = Color.RED;
private Color normalColor = Color.YELLOW;
private Rotate rotator = new Rotate();
private List<Circle> circles = new ArrayList<>();
private List<Rectangle> rectangles = new ArrayList<>();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
Rotate rotate = new Rotate();
Group root = new Group();
Pane pane = new Pane(root);
createRectangles();
createCircles();
root.getChildren().addAll(rectangles);
root.getChildren().addAll(circles);
root.getTransforms().add(rotate);
Scene scene = new Scene(pane, WIDTH, HEIGHT, Color.BLACK);
AddRotateControls(root);
assignActionHandling(pane);
stage.sizeToScene();
stage.setScene(scene);
stage.setTitle("Example");
stage.show();
}
private void AddRotateControls(Group root) {
root.getTransforms().add(rotator);
rotator.setPivotX(150);
rotator.setPivotY(150);
rotator.setAngle(0);
root.getScene().setOnKeyPressed(e -> {
switch(e.getCode()){
case RIGHT:
rotator.setAngle(rotator.getAngle() + 1);
break;
case LEFT:
rotator.setAngle(rotator.getAngle() - 1);
break;
default:
break;
}
});
}
private void assignActionHandling(Pane pane) {
pane.setOnMousePressed(e -> {
Circle circle = new Circle(e.getSceneX(), e.getSceneY(), 1, Color.DEEPSKYBLUE);
pane.getChildren().add(circle);
Duration duration = Duration.millis(350);
ScaleTransition scale = new ScaleTransition(duration, circle);
FadeTransition fade = new FadeTransition(duration, circle);
ParallelTransition pack = new ParallelTransition(circle, scale, fade);
scale.setFromX(1);
scale.setFromY(1);
scale.setToX(20);
scale.setToY(20);
fade.setFromValue(1);
fade.setToValue(0);
pack.setOnFinished(e2 -> {
pane.getChildren().remove(circle);
});
pack.play();
Circle selected = circles.stream().filter(c -> ((CircleData) c.getUserData()).isSelected()).findFirst().orElse(null);
if (selected != null) {
selected.setCenterX(e.getSceneX());
selected.setCenterY(e.getSceneY());
}
});
}
private void createRectangles() {
int width = 100;
int height = HEIGHT / 3;
int startX = ((WIDTH / 2) - (((width / 2) * 3) + (RECTANGLE_DISTANCE * 3))) + (RECTANGLE_DISTANCE * 2);
int startY = (HEIGHT / 2) - (height / 2);
for(int i = 0; i<RECTANGLE_COUNT; i++){
Rectangle rect = new Rectangle();
rect.setFill(Color.MEDIUMTURQUOISE);
rect.setWidth(width);
rect.setHeight(height);
rect.setX(startX);
rect.setY(startY);
rectangles.add(rect);
startX += (width + RECTANGLE_DISTANCE);
}
}
private void createCircles() {
Random randon = new Random();
int centerX = WIDTH / 2;
int centerY = HEIGHT / 2;
int minX = centerX - CIRCLE_DISTANCE;
int maxX = centerX + CIRCLE_DISTANCE;
int minY = centerY - CIRCLE_DISTANCE;
int maxY = centerY + CIRCLE_DISTANCE;
int minRadius = 10;
int maxRadius = 50;
for (int i = 0; i < CIRCLE_COUNT; i++) {
int x = minX + randon.nextInt(maxX - minX + 1);
int y = minY + randon.nextInt(maxY - minY + 1);
int radius = minRadius + randon.nextInt(maxRadius - minRadius + 1);
Circle circle = new Circle(x, y, radius, Color.ORANGE);
circle.setStroke(normalColor);
circle.setStrokeWidth(5);
circle.setUserData(new CircleData(circle, i, false));
circles.add(circle);
}
assignCircleActionHandling();
}
private double mouseX;
private double mouseY;
private void assignCircleActionHandling() {
for (Circle circle : circles) {
circle.setOnMousePressed(e -> {
mouseX = e.getSceneX() - circle.getCenterX();
mouseY = e.getSceneY() - circle.getCenterY();
((CircleData) circle.getUserData()).setSelected(true);
unselectRest(((CircleData) circle.getUserData()).getId());
});
circle.setOnMouseDragged(e -> {
double deltaX = e.getSceneX() - mouseX;
double deltaY = e.getSceneY() - mouseY;
circle.setCenterX(deltaX);
circle.setCenterY(deltaY);
});
circle.setOnMouseReleased(e -> {
e.consume();
});
}
}
private void unselectRest(int current) {
circles.stream().filter(c -> ((CircleData) c.getUserData()).getId() != current).forEach(c -> {
((CircleData) c.getUserData()).setSelected(false);
});
}
public class CircleData {
private int id;
private boolean selected;
private Circle circle;
public CircleData(Circle circle, int id, boolean selected) {
super();
this.id = id;
this.circle = circle;
this.selected = selected;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
if (selected) {
circle.setStroke(selectedColor);
} else {
circle.setStroke(normalColor);
}
}
}
}
You don't give the details of your code but there may be a problem with the pivot of your rotation. This can drive you nuts if you try to understand the rotation behaviour in some cases if you are not aware of this mechanism. Every time when you move some nodes which are attached to your group, this pivot for the rotation is recomputed which can result in unwanted effects although in some cases it is just what you want.
If you want to have full control of your rotation you should use some code similar to the one described here: http://docs.oracle.com/javafx/8/3d_graphics/overview.htm
Update:
In your method assignActionHandling modify these few lines. In order for this to work you somehow have to make root available there.
if (selected != null) {
Point2D p = root.sceneToLocal(e.getSceneX(), e.getSceneY());
selected.setCenterX(p.getX());
selected.setCenterY(p.getY());
}
The reason for you problem is that you are mixing up coordinate systems. The center points of your circles are defined relative to the root coordinate system but that is rotated with respect to pane as well as the scene. So you have to transform the scene coordinates into the local root coordinates before you set the new center of the circle.

JavaFx Determining whether a mouse is clicking on the background or a Circle

I am creating a game where circles fall from the top of the screen to the bottom. When the circle is clicked its suppose to re-spawn in a random position on top of the screen and with a random color. I am pretty sure my problem has to do with my line to determine if the mouse click was on one of the circles or not is working correctly. So my questions are how would I determine if a mouse click happened on one of the circles or on the background screen? and What is wrong with the following line? (Because I am almost certain that my problem is from that line)
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
My entire code is here:
public class ShapesWindow extends Application{
final int WIDTH = 640;
final int HEIGHT = WIDTH / 12 * 9;
Random r = new Random();
Circle circle;
double yCord;
long startNanoTime;
Group root = new Group();
Scene scene = new Scene(root, WIDTH, HEIGHT);
Canvas can = new Canvas(WIDTH,HEIGHT);
GraphicsContext gc = can.getGraphicsContext2D();
ArrayList<Shape> shape = new ArrayList<>();
#Override
public void start(Stage theStage) throws Exception {
theStage.setTitle("Click the bubbles!");
theStage.setScene(scene);
root.getChildren().add(can);
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
/* This adds 10 circles to my Group */
for(int i = 0; i < 10; i++){
gc.setFill(Color.LIGHTBLUE);
gc.fillRect(0,0,WIDTH,HEIGHT);
circle = new Circle(15,randomColor());
root.getChildren().add(circle);
circle.setLayoutX(r.nextInt(WIDTH+15));
circle.setLayoutY(0);
shape.add(circle);
}
/* This my attempt at trying to handle the Mouse Events for each thing */
for(int i = 0; i < 10; i++){
shape.get(i).setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
shapeClicked(e);
}
});
}
startNanoTime = System.nanoTime();
new AnimationTimer(){
public void handle(long currentNanoTime){
double t = (currentNanoTime - startNanoTime) / 1000000000.0;
yCord = t*20;
for(int i = 0; i < 10; i++){
/* This if statment allows nodes to wrap around from bottom to top */
if(yCord >=HEIGHT){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
resetNan();
}
shape.get(i).setLayoutY(yCord);
}
}
}.start();
theStage.show();
}
/*
* This Function is suppose the change the color and position of the circle that was clicked
*/
public void shapeClicked(MouseEvent e){
for(int i = 0; i < shape.size();i++){
if((shape.get(i).getLayoutX() == e.getX())&&(shape.get(i).getLayoutY() == e.getY())){
shape.get(i).setLayoutX(r.nextInt(WIDTH+15));
shape.get(i).setLayoutY(0);
shape.get(i).setFill(randomColor());
}
}
/*
* This allows the value of startNanoTime to be indrectly change it can not be changed diretly
* inside of handle() inside of the Animation class
*/
public void resetNan(){
startNanoTime = System.nanoTime();
}
public Color randomColor(){
double R = r.nextDouble();
double G = r.nextDouble();
double B = r.nextDouble();
double opacity = .6;
Color color = new Color(R, G, B, opacity);
return color.brighter();
}
public static void main(String[] args){
launch(args);
}
}
Why not just
for(int i = 0; i < 10; i++){
Shape s = shape.get(i);
s.setOnMouseClicked(
new EventHandler<MouseEvent>(){
public void handle(MouseEvent e){
s.setLayoutX(r.nextInt(WIDTH+15));
s.setLayoutY(0);
s.setFill(randomColor());
}
});
}
I know long time passed by, but if anyone else need to check if a mouse click event was on a Circle or any other shape it is better to use the built in .contains method. Thanks to Point2D from JavaFx geometry class you can check if a click (x,y coordinate) is on a shape or not, you don't have to worry about the click position: in the center or border.
for (Circle circle:listOfCircles) {
Point2D point2D = new Point2D(event.getX(),event.getY());
if (circle.contains(point2D)){
System.out.println("circle clicked");
}
}

How to create dynamically Resizable shapes in javafx?

I have three problems:
I want to create resizable shapes with box bounding...
I also want to know how to get child seleted in a Pane.
I'm creating multiple shapes on a pane. I want to change some property of that shape say Fill.. How do i do it??
Thanx
Next example will answer your questions:
for (1) it uses binding, connecting pane size with rectangle size
for (2) it adds setOnMouseClick for each rectangle which stores clicked one in the lastOne field.
for (3) see code of setOnMouseClick() handler
public class RectangleGrid extends Application {
private Rectangle lastOne;
public void start(Stage stage) throws Exception {
Pane root = new Pane();
int grid_x = 7; //number of rows
int grid_y = 7; //number of columns
// this binding will find out which parameter is smaller: height or width
NumberBinding rectsAreaSize = Bindings.min(root.heightProperty(), root.widthProperty());
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
Rectangle rectangle = new Rectangle();
rectangle.setStroke(Color.WHITE);
rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (lastOne != null) {
lastOne.setFill(Color.BLACK);
}
// remembering clicks
lastOne = (Rectangle) t.getSource();
// updating fill
lastOne.setFill(Color.RED);
}
});
// here we position rects (this depends on pane size as well)
rectangle.xProperty().bind(rectsAreaSize.multiply(x).divide(grid_x));
rectangle.yProperty().bind(rectsAreaSize.multiply(y).divide(grid_y));
// here we bind rectangle size to pane size
rectangle.heightProperty().bind(rectsAreaSize.divide(grid_x));
rectangle.widthProperty().bind(rectangle.heightProperty());
root.getChildren().add(rectangle);
}
}
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(); }
}

Resources