【问题标题】:How to position node inside a rotated group at mouse event coordinates?如何在鼠标事件坐标处将节点定位在旋转组内?
【发布时间】:2018-01-26 09:54:36
【问题描述】:

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

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

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

有没有办法做到这一点?感谢您的宝贵时间 =)

这里有一些代码显示了问题的最小可验证示例。运行它进行演示

您可以拖动圆圈并通过鼠标单击选择圆圈。只要组不旋转,执行此操作即可查看它是否正常工作。 要旋转组,请使用键盘上的 left 和 right 方向键。旋转组后,拖动鼠标坐标不再准确!

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

}

}

【问题讨论】:

  • 您是否要将圆圈放在同一个Group 内?如果是这样,如果您获得鼠标在组上单击的坐标,它应该可以工作(如果鼠标侦听器已在组中注册,这将是自然的)。您可能需要创建一个minimal reproducible example:不清楚您要问什么(或者我猜您可能要问很多不同的事情)。
  • @James_D 非常感谢您的评论。我将继续创建一个简单的示例并更新我的答案。圆圈已经在已旋转的组中。我只是想将圆的 centerX 和 y 移动到场景中发生点击的位置。如果我错了,请纠正我,但小组规模不适合孩子们吗?如果是这样,点击可能发生在组外!
  • @James_D Hey James 我添加了一个可验证的示例。查看我的问题中的新变化以获取描述。感谢您的帮助 =)

标签: javafx rotation coordinates mouseclick-event rotatetransform


【解决方案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());
}

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

【讨论】:

  • 感谢您抽出宝贵时间。对不起,我忘了指定。应用于组的旋转是一个 2d 旋转变换,其中组的左上角有一个 x 和 y 轴。我认为可能有一种方法可以根据圆的坐标和鼠标的坐标计算位置。我没有任何线索。我试过一整天都没有运气。
  • 嘿 Mipa 我添加了一个可验证的示例。查看我的问题中的新变化以获取描述。感谢您的帮助 =)
  • 太棒了!我将在我的主应用程序中测试这种方法,看看它是否有效。非常感谢。我会回来回复,如果可行,我会选择你的答案=D
猜你喜欢
  • 2014-09-27
  • 1970-01-01
  • 1970-01-01
  • 2019-05-11
  • 2011-07-24
  • 2012-05-14
  • 1970-01-01
  • 2018-11-29
  • 1970-01-01
相关资源
最近更新 更多