keep rectangle rotation pivot on center when resized javafx

Viewed 129

I recently start learn javafx. With help of several posts from here i created a draggable, resizable and rotable rectangle. My plan is use it as resizing control of custom nodes. I want rotation pívot keep on rectangle center but if is resized, center changes and when i set the new center the rectangle makes a bump. Why is happenning this and how i fix it? I am sorry if made any mistake english is not my native language. Thanks in advance


import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.stage.Stage;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        final Delta dragDelta = new Delta();
        Wrapper<Point2D> mouseLocation = new Wrapper<>();
        
        Pane root = new Pane();
        
        Rectangle rect = new Rectangle(100,100);
        final Rotate rotate = new Rotate();
        {   
            //set pivot on rectangle center
            rotate.setPivotX((rect.getX() + rect.getWidth())/2);
            rotate.setPivotY((rect.getY() + rect.getHeight())/2);
        }
        
        rect.setStyle(
                    "-fx-stroke: blue; " +
                    "-fx-stroke-width: 2px; " +
                    "-fx-stroke-dash-array: 12 2 4 2; " +
                    "-fx-stroke-dash-offset: 6; " +
                    "-fx-stroke-line-cap: butt; " +
                    "-fx-fill: rgba(255, 255, 255, .0);"
        );
        
        Group group = new Group();
        
        Circle rotateCircle = new Circle(7);
        Circle topLeft = new Circle(7);

        topLeft.setOnMousePressed(e->{
            mouseLocation.value = new Point2D(e.getSceneX(), e.getSceneY());
        });
        
        topLeft.setOnMouseDragged(e->{
            // Get the mouse deltas
            double dx = e.getSceneX() - mouseLocation.value.getX();
            double dy = e.getSceneY() - mouseLocation.value.getY();

            dragDelta.x = e.getSceneX();
            dragDelta.x = e.getSceneY();
            
            // Get the angle in radians
            double tau = - Math.toRadians(rotate.getAngle());

            double sinTau = Math.sin(tau);
            double cosTau = Math.cos(tau);
            
            // Perform a rotation on dx and dy to the object coordinate
            double dx_ = dx * cosTau - dy * sinTau;
            double dy_ = dy * cosTau + dx * sinTau;
           
            rect.setWidth(w(rect) - dx_); 
            rect.setHeight(h(rect) - dy_);
            
            // Set save the current mouse value
            mouseLocation.value = new Point2D(e.getSceneX(), e.getSceneY());
                    
            // Move the control 
            group.setTranslateX(group.getTranslateX() + dx); 
            group.setTranslateY(group.getTranslateY() + dy);
        });

        topLeft.setOnMouseReleased(e->{
            //This is the problem
            rotate.setPivotX((rect.getX() + rect.getWidth())/2);
            rotate.setPivotY((rect.getY() + rect.getHeight())/2);
        });

        group.getTransforms().add(rotate);
        
        rotateCircle.centerXProperty().bind(rect.xProperty().add(rect.widthProperty()).divide(2)); 
        rotateCircle.centerYProperty().bind(rect.yProperty().subtract(25d));
        
        topLeft.setCenterX(rect.getX());
        topLeft.setCenterY(rect.getY());
        
        group.getChildren().addAll(rect, rotateCircle, topLeft);
        
        group.setLayoutX(100);
        group.setLayoutY(100);
        
        root.getChildren().add(group);
        
        Util.enableDrag(rect, rotate);
        Util.makeRotable(rotateCircle, rotate);
        
        Scene scene = new Scene(root,400,400);
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();

    }
    
    public double w(Rectangle rect) {
        return Math.abs(rect.getWidth() - rect.getX());
    }
    
    public double h(Rectangle rect) {
        return Math.abs(rect.getHeight() - rect.getY());
    }
    
    // Return the angle from 0 to 360 
     public static double deltaAngle (double x, double y, double px, double py) {
        double dx = x - px;
        double dy = y - py;

        double angle = Math.abs(Math.toDegrees(Math.atan2(dy, dx)));

        if(dy < 0) {
             angle = 360 - angle;
         }

         return angle;
      }
     
    static class Util {
        static Wrapper<Point2D> mouseLocation = new Wrapper<>();
        
        // make a targetNode movable by dragging it around with the mouse.
        static void enableDrag(Rectangle rectangle, Rotate rotate) {
            final var dragDelta = new Delta();
            Parent parent = rectangle.getParent();
            
            rectangle.setOnMousePressed(mouseEvent -> {
               
                mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());

                // record a delta distance for the drag and drop operation.
                dragDelta.x = mouseEvent.getX() - mouseEvent.getX();
                dragDelta.y = mouseEvent.getY() - mouseEvent.getY();
                
                parent.getScene().setCursor(Cursor.MOVE);
                
            });
            
            rectangle.setOnMouseReleased(mouseEvent -> {
                mouseLocation.value = null ;
                
                parent.getScene().setCursor(Cursor.HAND);
              
                parent.relocate(parent.getLayoutX() + parent.getTranslateX(),
                        parent.getLayoutY() + parent.getTranslateY());
                parent.setTranslateX(0);
                parent.setTranslateY(0); 
                
                rotate.setPivotX((rectangle.getX() + rectangle.getWidth())/2);
                rotate.setPivotY((rectangle.getY() + rectangle.getHeight())/2);
                
            });
            
            rectangle.setOnMouseDragged(mouseEvent -> {
                // Get the mouse deltas
                double deltaX = mouseEvent.getSceneX() - mouseLocation.value.getX();
                double deltaY = mouseEvent.getSceneY() - mouseLocation.value.getY();

                parent.setTranslateX(parent.getTranslateX() + deltaX); 
                parent.setTranslateY(parent.getTranslateY() + deltaY);

                mouseLocation.value = new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY());
                
            });
            
        }

        // make a targetNode movable by dragging it around with the mouse.
        static void makeRotable(Circle circle, Rotate rotate) {
            final var dragDelta = new Delta();

         // Make it draggable
            circle.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
                        dragDelta.x = event.getSceneX();
                        dragDelta.x = event.getSceneY();
                    });

                    // When it's dragged rotate the box
            circle.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> {

                        var localToScene = circle.getParent().getLocalToSceneTransform();

                        double x1 = dragDelta.x;
                        double y1 = dragDelta.y;

                        var x2 = event.getSceneX();
                        var y2 = event.getSceneY();

                        var px = rotate.getPivotX() + localToScene.getTx();
                        var py = rotate.getPivotY() + localToScene.getTy();

                        // Work out the angle rotated
                        double th1 = deltaAngle(x1, y1, px, py);
                        double th2 = deltaAngle(x2, y2, px, py);

                        var angle = rotate.getAngle();

                        angle += th2 - th1;

                        // Rotate the rectangle
                        rotate.setAngle(angle);

                        dragDelta.x = event.getSceneX();
                        dragDelta.y = event.getSceneY();
                    });

                }

       
        }
    
    // records relative x and y co-ordinates.
    private static class Delta {
         double x, y;
    }
        
    static class Wrapper<T> {
       T value ; 
    } 
      

    public static void main(String[] args) {
        launch(args);
    }
}
1 Answers

You are scaling the rectangle by setting a new width and height:

rect.setWidth(w(rect) - dx_);
rect.setHeight(h(rect) - dy_);

This maintains the origin of the rectangle but changes its center.
Instead, consider scaling the rectangle around its center:

An easier approach anyway is to use a Scale transformation. With this you can specify the "pivot" point about which the scale occurs.

as explained and demonstrated in this so answer.
By using Scale.setX and Scale.setY you can apply different width and height scales.

Edit:
When changing the pivot of the Rotate it also affects rotations that were performed before the change. That is the reason for the "jump" you see.
To demonstrate it, run the following mre, where one buttons performs a rotate and the other only changes the pivot :

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {

        Rectangle rect = new Rectangle(100,100);
        rect.setStyle(
            "-fx-stroke: blue; " +
            "-fx-fill: rgba(255, 255, 255, .0);"
        );

        rect.setLayoutX(100);
        rect.setLayoutY(100);

        Rotate rotate = new Rotate();
        rotate.setPivotX((rect.getX() + rect.getWidth())/2);
        rotate.setPivotY((rect.getY() + rect.getHeight())/2);
        rect.getTransforms().add(rotate);

        Button rotateBtn = new Button("Rotate");

        Button movePivotBtn = new Button("Move Rotate Pivot");
        movePivotBtn.setDisable(true);
        movePivotBtn.setLayoutX(60);

        rotateBtn.setOnAction(e->{
            rotate.setAngle(45);
            rotateBtn.setDisable(true);
            movePivotBtn.setDisable(false);
        });

        movePivotBtn.setOnAction(e->{
            rotate.setPivotX(rotate.getPivotX()+15);
            movePivotBtn.setDisable(true);
        });

        Pane root = new Pane(rotateBtn,  movePivotBtn, rect);

        Scene scene = new Scene(root,400,400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Related