JavaFX中的嵌套路径转换

JavaFX中的嵌套路径转换,java,javafx,path-traversal,Java,Javafx,Path Traversal,我试图让我的节点沿着一个圆的路径移动,同时让这个圆沿着一个矩形的路径移动。可能吗 这就是我到目前为止所做的: void move(GamePane aThis) { double speed = 10; Rectangle rectangle = new Rectangle(100, 200, 100, 500); Circle circle = new Circle(50); circle.setFill(Color.WHITE); circle.s

我试图让我的节点沿着一个圆的路径移动,同时让这个圆沿着一个矩形的路径移动。可能吗

这就是我到目前为止所做的:

void move(GamePane aThis)
{

    double speed = 10;
    Rectangle rectangle = new Rectangle(100, 200, 100, 500);

    Circle circle = new Circle(50);
    circle.setFill(Color.WHITE);
    circle.setStroke(Color.BLACK);
    circle.setStrokeWidth(3);


    PathTransition pt = new PathTransition();
    pt.setDuration(Duration.millis(1000));
    pt.setPath(circle);
    pt.setNode(this);
    pt.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
    pt.setCycleCount(Timeline.INDEFINITE);
    pt.setAutoReverse(false);
    pt.play();

    PathTransition pt2 = new PathTransition();
    pt2.setDuration(Duration.millis(1000));
    pt2.setPath(rectangle);
    pt2.setNode(circle);
         pt2.setOrientation
   (PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
    pt2.setCycleCount(Timeline.INDEFINITE);
    pt2.setAutoReverse(false);
    pt2.play();

}

理论上,应该可以将一个过渡嵌套在另一个过渡之上

但存在一个问题:转换应用于转换属性,而节点布局未修改。这意味着在您的情况下,圆将遵循矩形定义的路径,但节点将在圆的初始位置上继续旋转

所以我们需要找到一种方法来随时更新圆的位置,这样节点就可以在这个位置上旋转

基于此,一种可能的方法是使用两个
AnimationTimer
s,以及一种在任何时刻插值路径并相应更新位置的方法

第一步是将原始路径转换为仅使用线性元素的路径:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import javafx.geometry.Point2D;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.QuadCurveTo;

/**
 *
 * @author jpereda
 */
public class LinearPath {

    private final Path originalPath;

    public LinearPath(Path path){
        this.originalPath=path;
    }

    public Path generateLinePath(){
        /*
        Generate a list of points interpolating the original path
        */
        originalPath.getElements().forEach(this::getPoints);

        /*
        Create a path only with MoveTo,LineTo
        */
        Path path = new Path(new MoveTo(list.get(0).getX(),list.get(0).getY()));
        list.stream().skip(1).forEach(p->path.getElements().add(new LineTo(p.getX(),p.getY())));
        path.getElements().add(new ClosePath());
        return path;
    }

    private Point2D p0;
    private List<Point2D> list;
    private final int POINTS_CURVE=5;

    private void getPoints(PathElement elem){
        if(elem instanceof MoveTo){
            list=new ArrayList<>();
            p0=new Point2D(((MoveTo)elem).getX(),((MoveTo)elem).getY());
            list.add(p0);
        } else if(elem instanceof LineTo){
            list.add(new Point2D(((LineTo)elem).getX(),((LineTo)elem).getY()));
        } else if(elem instanceof CubicCurveTo){
            Point2D ini = (list.size()>0?list.get(list.size()-1):p0);
            IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalCubicBezier((CubicCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
        } else if(elem instanceof QuadCurveTo){
            Point2D ini = (list.size()>0?list.get(list.size()-1):p0);
            IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalQuadBezier((QuadCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
        } else if(elem instanceof ClosePath){
            list.add(p0);
        } 
    }

    private Point2D evalCubicBezier(CubicCurveTo c, Point2D ini, double t){
        Point2D p=new Point2D(Math.pow(1-t,3)*ini.getX()+
                3*t*Math.pow(1-t,2)*c.getControlX1()+
                3*(1-t)*t*t*c.getControlX2()+
                Math.pow(t, 3)*c.getX(),
                Math.pow(1-t,3)*ini.getY()+
                3*t*Math.pow(1-t, 2)*c.getControlY1()+
                3*(1-t)*t*t*c.getControlY2()+
                Math.pow(t, 3)*c.getY());
        return p;
    }

    private Point2D evalQuadBezier(QuadCurveTo c, Point2D ini, double t){
        Point2D p=new Point2D(Math.pow(1-t,2)*ini.getX()+
                2*(1-t)*t*c.getControlX()+
                Math.pow(t, 2)*c.getX(),
                Math.pow(1-t,2)*ini.getY()+
                2*(1-t)*t*c.getControlY()+
                Math.pow(t, 2)*c.getY());
        return p;
    }
}
基本上,一旦你有了一条线性路径,它就会为每一行生成一个
。现在,通过这些线段的列表,您可以调用
interpolate
方法来计算节点在0和1之间任何分数处的位置和旋转,并且在第二次过渡的情况下,相应地更新形状的位置

最后,您可以在应用程序中创建两个
AnimationTimer
s:

@Override
public void start(Stage primaryStage) {
    Pane root = new Pane();

    Polygon poly = new Polygon( 0, 0, 30, 15, 0, 30); 
    poly.setFill(Color.YELLOW);
    poly.setStroke(Color.RED);
    root.getChildren().add(poly);

    Rectangle rectangle = new Rectangle(200, 100, 100, 400);
    rectangle.setFill(Color.TRANSPARENT);
    rectangle.setStroke(Color.BLUE);

    Circle circle = new Circle(50);
    circle.setFill(Color.TRANSPARENT);
    circle.setStroke(Color.RED);
    circle.setStrokeWidth(3);

    root.getChildren().add(rectangle);
    root.getChildren().add(circle);

    PathInterpolator in1=new PathInterpolator(rectangle, circle);
    PathInterpolator in2=new PathInterpolator(circle, poly);

    AnimationTimer timer1 = new AnimationTimer() {

        @Override
        public void handle(long now) {
            double millis=(now/1_000_000)%10000;
            in1.interpolate(millis/10000);
        }
    };

    AnimationTimer timer2 = new AnimationTimer() {

        @Override
        public void handle(long now) {
            double millis=(now/1_000_000)%2000;
            // Interpolate over the translated circle
            in2.interpolate(millis/2000,
                            circle.getTranslateX(),
                            circle.getTranslateY());
        }
    };
    timer2.start();
    timer1.start();

    Scene scene = new Scene(root, 800, 600);
    primaryStage.setScene(scene);
    primaryStage.show();
}
请注意,可以对动画应用不同的速度

这张照片拍摄了这个动画的两个瞬间

@Override
public void start(Stage primaryStage) {
    Pane root = new Pane();

    Polygon poly = new Polygon( 0, 0, 30, 15, 0, 30); 
    poly.setFill(Color.YELLOW);
    poly.setStroke(Color.RED);
    root.getChildren().add(poly);

    Rectangle rectangle = new Rectangle(200, 100, 100, 400);
    rectangle.setFill(Color.TRANSPARENT);
    rectangle.setStroke(Color.BLUE);

    Circle circle = new Circle(50);
    circle.setFill(Color.TRANSPARENT);
    circle.setStroke(Color.RED);
    circle.setStrokeWidth(3);

    root.getChildren().add(rectangle);
    root.getChildren().add(circle);

    PathInterpolator in1=new PathInterpolator(rectangle, circle);
    PathInterpolator in2=new PathInterpolator(circle, poly);

    AnimationTimer timer1 = new AnimationTimer() {

        @Override
        public void handle(long now) {
            double millis=(now/1_000_000)%10000;
            in1.interpolate(millis/10000);
        }
    };

    AnimationTimer timer2 = new AnimationTimer() {

        @Override
        public void handle(long now) {
            double millis=(now/1_000_000)%2000;
            // Interpolate over the translated circle
            in2.interpolate(millis/2000,
                            circle.getTranslateX(),
                            circle.getTranslateY());
        }
    };
    timer2.start();
    timer1.start();

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