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

时间:2017-08-17 20:21:29

标签: javafx rotation coordinates mouseclick-event rotatetransform

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

我尝试移动到click事件位置的节点是一个圆圈,它位于已旋转的组内。旋转发生在组右上角的一个枢轴上。该组还有其他节点。

我一直在努力想要实现这一点而没有运气。如果旋转节点的父节点,它就不会将节点定位在发生单击的位置。我尝试了各种技术,包括localToScene界限,没有运气。

有办法做到这一点吗?谢谢你的时间=)

以下是一些代码,显示了问题的最低可验证示例。运行它以进行演示

您可以拖动圆圈并点击鼠标选择圆圈。只要组不旋转,这样做就可以正常工作。 要旋转组,请使用键盘上的方向键。旋转组后,拖动和鼠标坐标不再准确!

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

}

}

1 个答案:

答案 0 :(得分:1)

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

如果您希望完全控制旋转,则应使用与此处所述类似的代码: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());
}

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