如何将节点定位在鼠标事件坐标处的旋转组内?

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

给定 2D 场景,其中一个节点位于包含 2d 旋转变换的组内。单击时如何将组内的节点定位到鼠标的场景 x 和 y 坐标?

我试图移动到点击事件位置的节点是一个位于已旋转组内的圆圈。旋转发生在组右上角的枢轴处。该组中也有其他节点。

我一直在尝试实现这一目标,但没有成功。如果旋转节点的父级,它只是不会将节点定位在发生点击的位置。我尝试了各种技术,包括 localToScene 边界,但没有成功。

有办法吗?谢谢你的时间 =)

这里是一些代码,显示了问题的最小可验证示例。 运行 用于演示

您可以通过单击鼠标拖动圆圈和 select 圆圈。执行此操作以查看只要不旋转组就可以正常工作。 要旋转组,请使用键盘上的 leftright 方向键。组旋转后拖动鼠标坐标不再准确!

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);
        }
    }

}

}

您没有提供代码的详细信息,但您的轮换基准可能有问题。如果您在不了解此机制的情况下尝试了解某些​​情况下的旋转行为,这可能会让您抓狂。每次当你移动一些附加到你的组的节点时,这个旋转的枢轴被重新计算,这可能会导致不需要的效果,尽管在某些情况下它正是你想要的。

如果你想完全控制你的旋转,你应该使用一些类似于这里描述的代码:http://docs.oracle.com/javafx/8/3d_graphics/overview.htm

更新:

在你的方法中assignActionHandling修改这几行。为了让它工作,你必须以某种方式使 root 在那里可用。

if (selected != null) {
   Point2D p = root.sceneToLocal(e.getSceneX(), e.getSceneY());
   selected.setCenterX(p.getX());
   selected.setCenterY(p.getY());
}

您遇到问题的原因是您混淆了坐标系。圆的中心点是相对于根坐标系定义的,但它是相对于窗格和场景旋转的。所以在设置新的圆心之前,必须先将场景坐标转换为局部根坐标。