JavaFX:在透视变换的面板上应用透视变换以对节点进行变换。

11

我制作了一个小演示来说明我遇到的问题。使用步骤如下:

1 - 点击屏幕四次以创建网格。

2 - 点击底部的按钮,给创建的网格添加透视效果。

3 - 再次点击屏幕,绘制一个圆。

输入图像描述

我的问题是:我不知道应该对 Circle 节点施加哪种变换才能在用户单击的确切位置上具有与网格相同的视觉效果

此演示目前仅绘制了一个没有变换的圆形。

期望的输出如下(这里我用椭圆代替圆形,这不能成为我的解决方案,因为用户输入网格的四个坐标):

输入图像描述

以下是该演示的代码。

Main.java

package application;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(getClass().getResource("View.fxml"));
            Scene scene = new Scene(loader.load(), 600, 600);
            primaryStage.setScene(scene);
            primaryStage.setTitle("Perspective Transformation");
            primaryStage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

View.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<VBox fx:id="vBox" maxHeight="600.0" maxWidth="600.0" minHeight="600.0" minWidth="600.0" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
   <children>
      <AnchorPane fx:id="anchorPane" maxHeight="575.0" minHeight="575.0" prefHeight="575.0" prefWidth="600.0" />
      <Button fx:id="button" alignment="CENTER" mnemonicParsing="false" prefHeight="25.0" prefWidth="600.0" text="Perspective Transformation" />
   </children>
</VBox>

控制器.java

package application;

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;

public class Controller {
    @FXML
    VBox vBox;
    @FXML
    AnchorPane anchorPane;
    @FXML
    Button button;

    Line line_1, line_2, line_3, line_4, middle_1, middle_2;

    private int numberOfClicks = 0;
    private int W = 10;
    //Store coordinates of the corresponding clicks
    private double cx1, cy1,
                   cx2, cy2,
                   cx3, cy3,
                   cx4, cy4,
                   cx5, cy5;

    @FXML
    private void initialize() {
        button.setDisable(true);

        button.setOnAction((event) -> {

            //Create a standard grid
            Line standard_1 = new Line(0, 0, 0, 600);
            Line standard_2 = new Line(0, 600, 600, 600);
            Line standard_3 = new Line(600, 600, 600, 0);
            Line standard_4 = new Line(600, 0, 0, 0);
            //Middle ones
            Line standard_5 = new Line(300, 0, 300, 600);
            Line standard_6 = new Line(0, 300, 600, 300);

            standard_1.setStrokeWidth(W);
            standard_2.setStrokeWidth(W);
            standard_3.setStrokeWidth(W);
            standard_4.setStrokeWidth(W);
            standard_5.setStrokeWidth(W);
            standard_6.setStrokeWidth(W);

            anchorPane.getChildren().clear();

            Pane perspectivePane = new Pane();

            perspectivePane.getChildren().addAll(standard_1, standard_2, standard_3,
                                            standard_4, standard_5, standard_6);

            PerspectiveTransform pt = new PerspectiveTransform();

            pt.setUlx(cx1);
            pt.setUly(cy1);
            pt.setUrx(cx2);
            pt.setUry(cy2);
            pt.setLrx(cx3);
            pt.setLry(cy3);
            pt.setLlx(cx4);
            pt.setLly(cy4);

            perspectivePane.setEffect(pt);

            anchorPane.getChildren().add(perspectivePane);

        });

        anchorPane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent event) {
                numberOfClicks++;

                if(numberOfClicks == 1){
                    cx1 = event.getX();
                    cy1 = event.getY();
                }
                else if(numberOfClicks == 2){
                    cx2 = event.getX();
                    cy2 = event.getY();
                    line_1 = new Line(cx1, cy1, cx2, cy2);
                    line_1.setStrokeWidth(W);
                    anchorPane.getChildren().add(line_1);
                }
                else if(numberOfClicks == 3){
                    cx3 = event.getX();
                    cy3 = event.getY();
                    line_2 = new Line(cx2, cy2, cx3, cy3);
                    line_2.setStrokeWidth(W);
                    anchorPane.getChildren().add(line_2);
                }
                else if(numberOfClicks == 4){
                    cx4 = event.getX();
                    cy4 = event.getY();
                    line_3 = new Line(cx3, cy3, cx4, cy4);
                    line_4 = new Line(cx4, cy4, cx1, cy1);
                    line_3.setStrokeWidth(W);
                    line_4.setStrokeWidth(W);
                    middle_1 = new Line((cx1+cx2)/2, (cy1+cy2)/2, (cx3+cx4)/2, (cy3+cy4)/2);
                    middle_2 = new Line((cx2+cx3)/2, (cy2+cy3)/2, (cx4+cx1)/2, (cy4+cy1)/2);
                    middle_1.setStrokeWidth(W);
                    middle_2.setStrokeWidth(W);
                    anchorPane.getChildren().addAll(line_3, line_4, middle_1, middle_2);
                    button.setDisable(false);
                }
                else if(numberOfClicks == 5){
                    cx5 = event.getX();
                    cy5 = event.getY();
                    Circle circle = new Circle();
                    circle.setCenterX(cx5);
                    circle.setCenterY(cy5);
                    circle.setRadius(30);
                    circle.setFill(Color.TRANSPARENT);
                    circle.setStroke(Color.BLACK);
                    circle.setStrokeWidth(W);

                    //PerspectiveTransform pt = new PerspectiveTransform();

                    //What transformation should I apply to the circle?

                    //circle.setEffect(pt);
                    anchorPane.getChildren().add(circle);
                }
                else {
                    anchorPane.getChildren().clear();
                    numberOfClicks = 0;
                }
            }
        });
    }
}

1
你尝试使用与perspectivePane相同的透视变换吗?还是尝试将圆形添加到perspectivePane中?另外,下次提问时的一个小提示:尽量减少代码示例。同时,最好将UI作为代码提供,而不是作为“.fxml”文件,这样我就可以整体复制和粘贴您的代码,而无需创建多个文件并确保每个文件都在正确的文件夹中。 - Markus Köbele
感谢您的回复和@MarkusK的建议,下次我一定会记住它们!至于您的建议,如果我对Circle节点使用与perspectivePane相同的转换,我会得到这个,如果我将Circle节点添加到perspectivePane中,我会得到这个。 "X"是我点击的位置。 - ihavenoidea
我认为你的“Button”处理程序中有一些奇怪的东西。 - SedJ601
1个回答

2

我不确定这是否是您想要的,但我使用了Group来实现这一点。我按照此处的代码进行操作。请测试此更新。如果有效,我将在明天尝试解释它。

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;

/**
 *
 * @author blj0011
 */
public class FXMLDocumentController implements Initializable
{

    @FXML
    VBox vBox;
    @FXML
    AnchorPane anchorPane;
    @FXML
    Button button;

    Group group = new Group();
    Group group2 = new Group();

    PerspectiveTransform pt = new PerspectiveTransform();

    Line line_1, line_2, line_3, line_4, middle_1, middle_2;

    private int numberOfClicks = 0;
    private int W = 10;
    //Store coordinates of the corresponding clicks
    private double cx1, cy1,
            cx2, cy2,
            cx3, cy3,
            cx4, cy4,
            cx5, cy5;

    @Override
    public void initialize(URL location, ResourceBundle resources)
    {
        button.setDisable(true);

        anchorPane.getChildren().addAll(group);

        button.setOnAction((event) -> {


            pt.setUlx(cx1);
            pt.setUly(cy1);
            pt.setUrx(cx2);
            pt.setUry(cy2);
            pt.setLrx(cx3);
            pt.setLry(cy3);
            pt.setLlx(cx4);
            pt.setLly(cy4);

            group.setEffect(pt);
        });

        anchorPane.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
            numberOfClicks++;

            switch (numberOfClicks) {
                case 1:
                    cx1 = event.getX();
                    cy1 = event.getY();
                    System.out.println(cx1 + " : " + cy1);
                    break;
                case 2:
                    cx2 = event.getX();
                    cy2 = event.getY();
                    line_1 = new Line(cx1, cy1, cx2, cy2);
                    line_1.setStrokeWidth(W);
                    group.getChildren().add(line_1);
                    break;
                case 3:
                    cx3 = event.getX();
                    cy3 = event.getY();
                    line_2 = new Line(cx2, cy2, cx3, cy3);
                    line_2.setStrokeWidth(W);
                    group.getChildren().add(line_2);
                    break;
                case 4:


                    cx4 = event.getX();
                    cy4 = event.getY();



                    line_3 = new Line(cx3, cy3, cx4, cy4);
                    line_4 = new Line(cx4, cy4, cx1, cy1);
                    line_3.setStrokeWidth(W);
                    line_4.setStrokeWidth(W);
                    middle_1 = new Line((cx1 + cx2) / 2, (cy1 + cy2) / 2, (cx3 + cx4) / 2, (cy3 + cy4) / 2);
                    middle_2 = new Line((cx2 + cx3) / 2, (cy2 + cy3) / 2, (cx4 + cx1) / 2, (cy4 + cy1) / 2);
                    middle_1.setStrokeWidth(W);
                    middle_2.setStrokeWidth(W);
                    group.getChildren().addAll(line_3, line_4, middle_1, middle_2);
                    button.setDisable(false);
                    break;
                case 5:
                    List<Double> centerOfTransform = findCenterOfTransForm(pt);

                    cx5 = event.getX();
                    cy5 = event.getY();
                    List<Double> list = new ArrayList();
                    list.add(cx5);
                    list.add(cy5);
                    List<Double> changeInCenterXAndCenterY = findChangeInCenterXAndY(centerOfTransform, list);


                    PerspectiveTransform ptCircle = new PerspectiveTransform();
                    ptCircle.setUlx(cx1 + changeInCenterXAndCenterY.get(0));
                    ptCircle.setUly(cy1 + changeInCenterXAndCenterY.get(1));
                    ptCircle.setUrx(cx2 + changeInCenterXAndCenterY.get(0));
                    ptCircle.setUry(cy2 + changeInCenterXAndCenterY.get(1));
                    ptCircle.setLrx(cx3 + changeInCenterXAndCenterY.get(0));
                    ptCircle.setLry(cy3 + changeInCenterXAndCenterY.get(1));
                    ptCircle.setLlx(cx4 + changeInCenterXAndCenterY.get(0));
                    ptCircle.setLly(cy4 + changeInCenterXAndCenterY.get(1));




                    System.out.println("cx5: " + cx5 + "  cy5: " + cy5);
                    Circle circle = new Circle();

                    circle.setRadius(30);
                    circle.setFill(Color.TRANSPARENT);
                    circle.setStroke(Color.BLACK);
                    circle.setStrokeWidth(W);


                     circle.setCenterX(cx5);
                    circle.setCenterY(cy5);
                    circle.setEffect(ptCircle);
                    anchorPane.getChildren().add(circle);
                    System.out.println("centerx: " + circle.getTranslateX()+ "  centery: " + circle.getTranslateY());
                    break;
                default:
                    group.getChildren().clear();
                    numberOfClicks = 0;
                    break;
            }
        });
    }

     List<Double> findCenterOfTransForm(PerspectiveTransform pt)
    {
        List<Double> tempList = new ArrayList();
        tempList.add((pt.getUlx() + pt.getUrx() + pt.getLrx() + pt.getLlx()) / 4);
        tempList.add((pt.getUly() + pt.getUry() + pt.getLry() + pt.getLly()) / 4);

        return tempList;
    }

     List<Double> findChangeInCenterXAndY(List<Double> originalXAndY, List<Double> newXAndY)
     {
         List<Double> tempList = new ArrayList();
         tempList.add(newXAndY.get(0) - originalXAndY.get(0));
         tempList.add(newXAndY.get(1) - originalXAndY.get(1));

         return tempList;
     }


}

谢谢您的答案!然而,在对网格应用效果后,圆应该被创建。我已编辑原始问题以使其更易于理解,为此给您带来的不便我深感抱歉。在问题的评论部分,我讨论了一些尝试过的方法。 - ihavenoidea
1
只要没有边缘线穿过另一条边缘线,这就可以正常工作。 - SedJ601
在问题中添加了所需的输出。 - ihavenoidea
1
仅通过仿射变换框架提供的旋转、剪切和平移,无法实现远景变换。要做到这一点,您需要进入3D领域。为什么不将您的圆形和矩形渲染成离屏纹理,然后将其放置在透视变换的四边形上呢?请参见https://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/TriangleMesh.html。 - Andrew Butenko
1
也许,我发现这个答案很有希望,会尝试一下。我认为处理透视相机可能会有些挑战,需要将矩形转换/旋转到相应的用户输入坐标。 - ihavenoidea
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接