Rotate line segment with Button - math

I have a line which has points (x1,y1) and (x2,y2). I wanted to attach a Button to it, it should align with the line by rotating based on the line segment points. I need some help in calculating the rotation angle for the Button.

You can use the Math.atan2(dy, dx) to get the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta). Later use it to convert it to degrees.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
double startX = 100;
double endX = 250;
double startY = 150;
double endY = 250;
Line line = new Line(startX, startY, endX, endY);
Button button = new Button("Button");
double rad = Math.atan2(endY - startY, endX - startX);
double degree = rad * 180/Math.PI;
button.setRotate(degree);
StackPane box = new StackPane(line, button);
Scene scene = new Scene(box, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}
OutPut

Related

Create hexagonal field with JavaFX

I have the goal to create a field of hexagonal tiles. I have come as far as having a matrix of cells, each high enough to fit the complete hexagon image:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class UITest extends Application {
final private static String TILE_IMAGE_LOCATION = System.getProperty("user.dir") + File.separatorChar +"resources"+ File.separatorChar + "blueTile.png";
final private static Image HEXAGON_IMAGE = initTileImage();
private static Image initTileImage() {
try {
return new Image(new FileInputStream(new File(TILE_IMAGE_LOCATION)));
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
}
public void start(Stage primaryStage) {
int height = 4;
int width = 6;
GridPane tileMap = new GridPane();
Scene content = new Scene(tileMap, 800, 600);
primaryStage.setScene(content);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
ImageView tile = new ImageView(HEXAGON_IMAGE);
GridPane.setConstraints(tile, x, y);
tileMap.getChildren().add(tile);
}
}
primaryStage.show();
}
}
My problem is not the vertical gap, which I can surely figure out by adding the GridPane's vGap() to a proper value. The difficulty for me is shifting each second row half a cellwidth to the right.
I have attempted to lay two GridPanes over eachother, one containing the odd and one the even rows, with the goal to add padding to one of them, shifting it entirely. To my knowledge however, there is no way for this, as well as nesting GridPanes into on another.
How can I best achieve the shifting of only every second row?
(The image I reference in the code which is expected in the ${projectroot}/resources/ folder: )
It took me some time to figure it out. I hope it helps. I don't use an image. It's made of polygons, you can customize the stroke and fill color, as well as the width.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
public class UITest extends Application {
public void start(Stage primaryStage) {
int height = 600;
int width = 800;
AnchorPane tileMap = new AnchorPane();
Scene content = new Scene(tileMap, width, height);
primaryStage.setScene(content);
double size = 50,v=Math.sqrt(3)/2.0;
for(double y=0;y<height;y+=size*Math.sqrt(3))
{
for(double x=-25,dy=y;x<width;x+=(3.0/2.0)*size)
{
Polygon tile = new Polygon();
tile.getPoints().addAll(new Double[]{
x,dy,
x+size,dy,
x+size*(3.0/2.0),dy+size*v,
x+size,dy+size*Math.sqrt(3),
x,dy+size*Math.sqrt(3),
x-(size/2.0),dy+size*v
});
tile.setFill(Paint.valueOf("#ffffff"));
tile.setStrokeWidth(2);
tile.setStroke(Paint.valueOf("#000000") );
tileMap.getChildren().add(tile);
dy = dy==y ? dy+size*v : y;
}
}
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
For other interested souls out there, I have used the accepted answer by Cthulhu and improved/documented the given code as a short standalone demonstration:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class UISolution extends Application {
private final static int WINDOW_WIDTH = 800;
private final static int WINDOW_HEIGHT = 600;
private final static double r = 20; // the inner radius from hexagon center to outer corner
private final static double n = Math.sqrt(r * r * 0.75); // the inner radius from hexagon center to middle of the axis
private final static double TILE_HEIGHT = 2 * r;
private final static double TILE_WIDTH = 2 * n;
public static void main(String[] args) {
launch(args);
}
public void start(Stage primaryStage) {
AnchorPane tileMap = new AnchorPane();
Scene content = new Scene(tileMap, WINDOW_WIDTH, WINDOW_HEIGHT);
primaryStage.setScene(content);
int rowCount = 4; // how many rows of tiles should be created
int tilesPerRow = 6; // the amount of tiles that are contained in each row
int xStartOffset = 40; // offsets the entire field to the right
int yStartOffset = 40; // offsets the entire fiels downwards
for (int x = 0; x < tilesPerRow; x++) {
for (int y = 0; y < rowCount; y++) {
double xCoord = x * TILE_WIDTH + (y % 2) * n + xStartOffset;
double yCoord = y * TILE_HEIGHT * 0.75 + yStartOffset;
Polygon tile = new Tile(xCoord, yCoord);
tileMap.getChildren().add(tile);
}
}
primaryStage.show();
}
private class Tile extends Polygon {
Tile(double x, double y) {
// creates the polygon using the corner coordinates
getPoints().addAll(
x, y,
x, y + r,
x + n, y + r * 1.5,
x + TILE_WIDTH, y + r,
x + TILE_WIDTH, y,
x + n, y - r * 0.5
);
// set up the visuals and a click listener for the tile
setFill(Color.ANTIQUEWHITE);
setStrokeWidth(1);
setStroke(Color.BLACK);
setOnMouseClicked(e -> System.out.println("Clicked: " + this));
}
}
}

javafx mouse move on a circle path

How can I restrict the motion of the blue circle (due to a mouse drag) only on the red circle path? Should I use polar coordinates? (x=rcos(θ), y=rsin(θ))?
The code I created till now let me drag the blue point all over the stage. I want the center of the blue point to follow the red circle.
package circlemouse;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class CircleMouse extends Application {
private double initY;
private double dragAnchorY;
private double initX;
private double dragAnchorX;
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 500, 500);
primaryStage.setResizable(false);
//stage center
double x0 = pane.getWidth() / 2.0;
double y0 = pane.getHeight() / 2.0;
Line horizontalLine = new Line(0.0, y0, 2.0 * x0, y0);
Line vertical = new Line(x0, 0.0, x0, 2.0 * y0);
//red circle (path of point)
double r = 100.0;
Circle c = new Circle(x0, y0, r);
c.setFill(null);
c.setStroke(Color.RED);
//the point
double pointRadius = 15.0;
Circle point = new Circle(x0 + r, y0, pointRadius);
point.setFill(Color.BLUE);
point.setOnMousePressed((MouseEvent me) -> {
initY = point.getCenterY();
dragAnchorY = me.getSceneY();
initX = point.getCenterX();
dragAnchorX = me.getSceneX();
});
point.setOnMouseDragged((MouseEvent me) -> {
double dragY = me.getSceneY() - dragAnchorY;
double newY = initY + dragY;
point.setCenterY(newY);
double dragX = me.getSceneX() - dragAnchorX;
double newX = initX + dragX;
point.setCenterX(newX);
});
pane.getChildren().addAll(horizontalLine, vertical, c, point);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you draw a line from the center of the red circle to where the mouse is, and then draw a line from the center of the red circle to where you want the point, they are obviously in the same direction, and the length of the line from the center of the red circle to where you want the point is just the radius of the line.
So in vector terminology, the vector from the center of the circle to the new point position is the radius of the circle times the unit vector in the direction from the center of the circle to the mouse.
The Point2D API allows you to interpret a Point2D as a vector, and has useful methods for computing the unit vector (normalize()), multiplying by a scalar, adding and subtracting other vectors, etc.
So:
point.setOnMouseDragged((MouseEvent me) -> {
Point2D redCenter = new Point2D(c.getCenterX(), c.getCenterY());
Point2D mouse = new Point2D(me.getX(), me.getY());
Point2D centerToMouse = mouse.subtract(redCenter);
Point2D centerToNewPoint = centerToMouse.normalize().multiply(c.getRadius());
Point2D newPoint = centerToNewPoint.add(redCenter);
point.setCenterX(newPoint.getX());
point.setCenterY(newPoint.getY());
});

How do I use a button in JavaFX to change the size of a line

I want to change the size of a line using a button so later I can make the line look like it is rotating... Here is the code I have so far:
package JavaFXApplication14;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class JavaFXApplication14 extends Application
{
public static void main(String[] args)
{
launch(args);
}
int x = 200;
#Override public void start(Stage primaryStage) throws Exception
{
final GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(100);
grid.setVgap(100);
grid.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(grid, 600, 400); //Color.BLACK ?
primaryStage.setScene(scene);
primaryStage.setTitle("4D");
primaryStage.show();
Line ln = new Line(100, 200, x, 200);
ln.setStroke(Color.BLUE);
ln.setStrokeWidth(5);
grid.add(ln, 0, 0);
Button btn = new Button("X-Y");
grid.setHalignment(btn, HPos.CENTER);
btn.setOnAction(e -> btn_Click());
grid.add(btn, 0, 1);
}
public void btn_Click()
{
x = x + 50;
}
}
Also, sometimes when I use the following line of code the color of the background does not change.
Scene scene = new Scene(grid, 600, 400, Color.BLACK);
What is the reason for that?
The button works very well you can test it, but here you only set a new value to x and nothing else, in other words you don't update the position x2 of your Line.You can dot that by making your variable ln accessible and updating its value EndX:
//before the method start
Line ln;
//Inside your btn_click() method add
ln.setEndX(x);
But it will only increase the size of your line (Horizontally) and not rotate it. with a little research explained here and following what I told you in the comment you can do this easily based on the axis of rotation (startX & startY) and the points to be rotated (endX & endY):
public class Launcher extends Application{
private Pane root = new Pane();
private Scene scene;
private Button btn = new Button("Test");
private Line line;
#Override
public void start(Stage stage) throws Exception {
line = new Line(200,200,200,100);
line.setStroke(Color.BLACK);
line.setStrokeWidth(5);
btn.setOnAction(e -> rotate());
root.getChildren().addAll(btn,line);
scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
private void rotate(){
double x1 = line.getEndX() - line.getStartX();
double y1 = line.getEndY() - line.getStartY();
//The more you reduce the angle the longer the rotation
double x2 = x1 * Math.cos(0.1) - y1 * Math.sin(0.1);
double y2 = x1 * Math.sin(0.1) + y1 * Math.cos(0.1);
line.setEndX(x2+ line.getStartX());
line.setEndY(y2+ line.getStartY());
}
public static void main(String[] args) {
launch(args);
}
}
Note: In your example you use a GridPane, so you will be restricted to its functioning, Good luck !

How is set the Pivot for Shapes in JavaFX after Rotate Transformation?

I'm trying to move the upper left corner on a rotated rectangle and keep the lower right corner fixed. But after the update of the pivots of the rectangle there is a jump of the rectangle. How do I have the corners set correctly so that the jump does not occur after update the pivots? The example illustrates the problem. The green and blue rectangle are different after clicking the button! Has everone an idea? May help the 'deltaTransform' procedure?
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class RotateTest extends Application {
Rectangle rect0,rect1, rect2;
#Override
public void start(Stage primaryStage) {
rect0 = new Rectangle(100, 100, 200, 100); //start rect to compare
rect0.setFill(Color.TRANSPARENT);
rect0.setStroke(Color.GREEN);
rect0.setStrokeWidth(1.5);
rect0.setRotate(20);
rect1 = new Rectangle(100, 100, 200, 100); // moved rect
rect1.setFill(Color.TRANSPARENT);
rect1.setStroke(Color.RED);
rect1.setStrokeWidth(1.5);
rect2 = new Rectangle(100, 100, 200, 100);// unmoved rect
rect2.setFill(Color.TRANSPARENT);
rect2.setStrokeWidth(1.5);
rect2.setStroke(Color.BLUE);
Rotate rotate = new Rotate(20, rect1.getX() + rect1.getWidth() / 2., rect1.getY() + rect1.getHeight() / 2.);
rect1.getTransforms().add(rotate);
rect2.getTransforms().add(rotate);
Button btn = new Button();
btn.setText("Move to target");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
Point2D p1ns = new Point2D(150, 50); //target for upper left corner in transformed system
Point2D p1n = rotate.inverseTransform(p1ns);//target for upper left corner in nontransformed system
Point2D p2 = new Point2D(rect1.getX()+rect1.getWidth(), rect1.getY()+rect1.getHeight());
//bottom right corner in nontransformed system
Point2D p2s = rotate.transform(p2);//bottom right corner in transformed system
rect1.setX(p1n.getX());
rect1.setY(p1n.getY());
rect1.setWidth(p2.getX()-p1n.getX());
rect1.setHeight(p2.getY()-p1n.getY());
//this make the problem:
rotate.setPivotX(rect1.getX() + rect1.getWidth() / 2.);
rotate.setPivotY(rect1.getY() + rect1.getHeight() / 2.);
} catch (NonInvertibleTransformException ex) {
Logger.getLogger(RotateTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
Group root = new Group();
for (int i = 0; i < 10; i++) {
Line l1 = new Line(i * 100, 0, i * 100, 400);
Line l2 = new Line(0, i * 100, 1000, i * 100);
root.getChildren().addAll(l1, l2);
}
root.getChildren().addAll(btn, rect0, rect1, rect2);
Scene scene = new Scene(root, 400, 300);
primaryStage.setTitle("Rotation");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Now I have found a solution. A secondary temporary Rotate-object helps! See the code now! The green and red rectangle have the same right bottom corner!
#Override
public void handle(ActionEvent event) {
try {
Point2D p1s = new Point2D(150, 50); //target for upper left corner in transformed system
Point2D p2s = rotate.transform(rect1.getX()+rect1.getWidth(), rect1.getY()+rect1.getHeight());//bottom right corner in transformed system
Rotate rotTemp=new Rotate(rotate.getAngle(), (p1s.getX() + p2s.getX() )/ 2., (p1s.getY() + p2s.getY() )/ 2.);
Point2D q1 = rotTemp.inverseTransform(p1s);
Point2D q2 = rotTemp.inverseTransform(p2s);
rect1.setX(q1.getX());
rect1.setY(q1.getY());
rect1.setWidth(q2.getX()-q1.getX());
rect1.setHeight(q2.getY()-q1.getY());
rotate.setPivotX(rotTemp.getPivotX());
rotate.setPivotY(rotTemp.getPivotY());
} catch (NonInvertibleTransformException ex) {
Logger.getLogger(RotateTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
You can always fix the bottom right of a rectangle with a change of properties of the form:
x -> x + deltaX ;
y -> y + deltaY ;
width -> width - deltaX ;
height -> height - deltaY ;
So you can do
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
Point2D targetAbsolute = new Point2D(150, 50);
Point2D targetLocal = rotate.inverseTransform(targetAbsolute);
double newX = targetLocal.getX() ;
double newY = targetLocal.getY() ;
double deltaX = newX - rect1.getX();
double deltaY = newY - rect1.getY();
rect1.setX(newX);
rect1.setY(newY);
rect1.setWidth(rect1.getWidth() - deltaX);
rect1.setHeight(rect1.getHeight() - deltaY);
} catch (NonInvertibleTransformException ex) {
Logger.getLogger(RotateTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
});

The pain with the pane in JavaFX. How can you scale Nodes with fixed Top-Left Corner?

It seems to be a simple problem. But I found no simple solution. If you scale Nodes, the new form will be in the center of the parent. But I would like that the new form has the same Top-Left Corner as the old one.
The expample code is:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TestScale extends Application{
Group root;
Pane pane;
Scene scene;
Rectangle rect0;
#Override
public void start(Stage stage) {
root = new Group();
scene = new Scene(root, 200, 160);
rect0=new Rectangle(0, 0, 200, 160);
rect0.setFill(Color.BLUE);
pane = new Pane();
pane.getChildren().add(rect0);
Button btnForward = new Button();
btnForward.setText(">");
btnForward.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
transform(pane);
}
});
root.getChildren().add(pane);
root.getChildren().add(btnForward);
stage.setScene(scene);
stage.show();
}
void transform (Node node){
node.setScaleX(0.5);
node.setScaleY(0.5);
}
public static void main(String[] args) {
launch(args);
}
}
All tests with Stackpane, Borderpane, Anchorpane, Groups delivers no easy solution. The only way seems to be with setTransformX and setTransformY. But I need for this a complex calculation of the arguments.
When you use ScaleX/ScaleY, scaling occurs from the center of the node.
From JavaDocs
The pivot point about which the scale occurs is the center of the untransformed layoutBounds.
So, if you want to translate the scaling co-ordinates, you need to take the scaling compression into account when you set the required translation values.
As your current pivot is center, you need to set Translate to a negative value. Since the compression of X and Y is half, so you need to translate to 1/4 of total size of the scene.
node.setScaleX(0.5);
node.setScaleY(0.5);
node.setTranslateX(0 - node.getScene().getWidth()/4);
node.setTranslateY(0 - node.getScene().getHeight()/4);
Here ist the code to transform an rectangle within an image:
The procedure deliver a scalefaktor for setScaleX and setScaleY (scale) and set value tx for setTransformX and ty for setTransformY.
public Scaler(double sceneWidth, double sceneHeight, double imgWidth, double imgHeight,
int x, int y, int width, int height) {
double scrnRatio = sceneHeight / sceneWidth;
double offsetX = 0.;
double offsetY = 0.;
if (height / (double)width > scrnRatio) {
offsetX = (height / scrnRatio - width) / 2.;
scale = sceneHeight/height;
} else {
offsetY = (width * scrnRatio - height) / 2.;
scale = sceneWidth/width;
}
double dh = (1. - scale) / 2.;
tx = -(x - offsetX) * scale - dh * imgWidth;
ty = -(y - offsetY) * scale - dh * imgHeight;
}
There is no way for an easier code?

Resources