【问题标题】:Updating an animation in javafx: Path Transition over an adjustable curve在 javafx 中更新动画:可调整曲线上的路径过渡
【发布时间】:2015-09-02 17:51:16
【问题描述】:

我是 Javafx 的新手,我正在尝试动画。在this 之后,我创建了一条带有两个锚点的曲线。移动锚点会改变曲线的形状。接下来,我跟随this 创建了一个动画,其中一个正方形沿着曲线从一个端点到另一个端点。 将这两者结合起来效果很好,除非我移动一个锚点!我的广场一直遵循原来的轨迹。对于如何解决这个问题,有任何的建议吗?我不想重新开始动画;广场应该继续沿着它的路径移动而没有明显的中断。

这是一个完整的工作示例:

import javafx.animation.PathTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;


public class CurveAnimation extends Application {


    public static void main(String[] args) throws Exception { launch(args); }

    @Override
    public void start(final Stage stage) throws Exception {

        //Create a curve
        CubicCurve curve = new CubicCurve();
        curve.setStartX(100);
        curve.setStartY(100);
        curve.setControlX1(150);
        curve.setControlY1(50);
        curve.setControlX2(250);
        curve.setControlY2(150);
        curve.setEndX(300);
        curve.setEndY(100);
        curve.setStroke(Color.FORESTGREEN);
        curve.setStrokeWidth(4);
        curve.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));

        //Create anchor points at each end of the curve
        Anchor start    = new Anchor(Color.PALEGREEN, curve.startXProperty(),    curve.startYProperty());
        Anchor end      = new Anchor(Color.TOMATO,    curve.endXProperty(),      curve.endYProperty());

        //Create object that follows the curve
        Rectangle rectPath = new Rectangle (0, 0, 40, 40);
        rectPath.setArcHeight(25);
        rectPath.setArcWidth(25);
        rectPath.setFill(Color.ORANGE);

        //Create the animation
        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.millis(2000));
        pathTransition.setPath(curve);
        pathTransition.setNode(rectPath);
        pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
        pathTransition.setCycleCount(Timeline.INDEFINITE);
        pathTransition.setAutoReverse(true);
        pathTransition.play();

        Group root = new Group();
        root.getChildren().addAll(curve, start, end, rectPath);

        stage.setScene(new Scene( root, 400, 400, Color.ALICEBLUE));
        stage.show();
    }


    /**
     * Create draggable anchor points
     */
    class Anchor extends Circle {
        Anchor(Color color, DoubleProperty x, DoubleProperty y) {
            super(x.get(), y.get(), 10);
            setFill(color.deriveColor(1, 1, 1, 0.5));
            setStroke(color);
            setStrokeWidth(2);
            setStrokeType(StrokeType.OUTSIDE);

            x.bind(centerXProperty());
            y.bind(centerYProperty());
            enableDrag();
        }

        // make a node movable by dragging it around with the mouse.
        private void enableDrag() {
            final Delta dragDelta = new Delta();

            setOnMousePressed(mouseEvent -> {
                // record a delta distance for the drag and drop operation.
                dragDelta.x = getCenterX() - mouseEvent.getX();
                dragDelta.y = getCenterY() - mouseEvent.getY();
                getScene().setCursor(Cursor.MOVE);
            });

            setOnMouseReleased(mouseEvent -> getScene().setCursor(Cursor.HAND));

            setOnMouseDragged(mouseEvent -> {
                double newX = mouseEvent.getX() + dragDelta.x;
                if (newX > 0 && newX < getScene().getWidth()) {
                    setCenterX(newX);
                }
                double newY = mouseEvent.getY() + dragDelta.y;
                if (newY > 0 && newY < getScene().getHeight()) {
                    setCenterY(newY);
                }
            });

            setOnMouseEntered(mouseEvent -> {
                if (!mouseEvent.isPrimaryButtonDown()) {
                    getScene().setCursor(Cursor.HAND);
                }
            });

            setOnMouseExited(mouseEvent -> {
                if (!mouseEvent.isPrimaryButtonDown()) {
                    getScene().setCursor(Cursor.DEFAULT);
                }
            });
        }

        // records relative x and y co-ordinates.
        private class Delta { double x, y; }
    }
}

【问题讨论】:

    标签: java animation javafx-8


    【解决方案1】:

    PathTransition 显然只是在您调用 setPath 时复制路径中的值,如果它们发生变化,则不会观察它们。

    要做你想做的事,你需要使用Transition 并自己实现插值。插值必须取值double t 并设置节点的translateXtranslateY 属性,使其中心位于带有参数t 的曲线上。如果您想要ORTHOGONAL_TO_TANGENT 方向,您还需要将节点的rotate 属性设置为三次曲线的切线与正水平线的角度。通过interpolate方法计算这些,可以简单的参考曲线的当前控制点。

    要进行计算,您需要了解一些几何知识。带有控制点的线性贝塞尔曲线上的点(即startendP0P1 在参数t 处由下式给出

    B(t; P0, P1) = (1-t)*P0 + t*P1
    

    您可以通过以下方式递归计算高阶贝塞尔曲线

    B(t; P0, P1, ..., Pn) = (1-t)*B(P0, P1, ..., P(n-1); t) + t*B(P1, P2, ..., Pn;t)
    

    只需区分这两者以获得线性曲线的切线(从几何考虑,显然只是P1-P0)和递归关系:

    B'(t; P0, P1) = -P0 + P1
    

    B'(t; P0, P1, ..., Pn) = -B(t; P0, ..., P(n-1)) + (1-t)B'(t; P0, ..., P(n-1))
                           +  B(t; P1, ..., Pn) + tB'(t; P1, ..., Pn)
    

    这是在代码中实现的:

    import javafx.animation.Animation;
    import javafx.animation.PathTransition;
    import javafx.animation.Timeline;
    import javafx.animation.Transition;
    import javafx.application.Application;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.geometry.Point2D;
    import javafx.scene.Cursor;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Circle;
    import javafx.scene.shape.CubicCurve;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.StrokeType;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    
    public class CurveAnimation extends Application {
    
    
        public static void main(String[] args) throws Exception { launch(args); }
    
        @Override
        public void start(final Stage stage) throws Exception {
    
            //Create a curve
            CubicCurve curve = new CubicCurve();
            curve.setStartX(100);
            curve.setStartY(100);
            curve.setControlX1(150);
            curve.setControlY1(50);
            curve.setControlX2(250);
            curve.setControlY2(150);
            curve.setEndX(300);
            curve.setEndY(100);
            curve.setStroke(Color.FORESTGREEN);
            curve.setStrokeWidth(4);
            curve.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));
    
            //Create anchor points at each end of the curve
            Anchor start    = new Anchor(Color.PALEGREEN, curve.startXProperty(),    curve.startYProperty());
            Anchor end      = new Anchor(Color.TOMATO,    curve.endXProperty(),      curve.endYProperty());
    
    
    
            //Create object that follows the curve
            Rectangle rectPath = new Rectangle (0, 0, 40, 40);
            rectPath.setArcHeight(25);
            rectPath.setArcWidth(25);
            rectPath.setFill(Color.ORANGE);
    
    
            Transition transition = new Transition() {
    
                {
                    setCycleDuration(Duration.millis(2000));
                }
    
                @Override
                protected void interpolate(double frac) {
                    Point2D start = new Point2D(curve.getStartX(), curve.getStartY());
                    Point2D control1 = new Point2D(curve.getControlX1(), curve.getControlY1());
                    Point2D control2 = new Point2D(curve.getControlX2(), curve.getControlY2());
                    Point2D end = new Point2D(curve.getEndX(), curve.getEndY());
    
                    Point2D center = bezier(frac, start, control1, control2, end);
    
                    double width = rectPath.getBoundsInLocal().getWidth() ;
                    double height = rectPath.getBoundsInLocal().getHeight() ;
    
                    rectPath.setTranslateX(center.getX() - width /2);
                    rectPath.setTranslateY(center.getY() - height / 2);
    
                    Point2D tangent = bezierDeriv(frac, start, control1, control2, end);
                    double angle = Math.toDegrees(Math.atan2(tangent.getY(), tangent.getX()));
                    rectPath.setRotate(angle);
                }
    
            };
    
            transition.setCycleCount(Animation.INDEFINITE);
            transition.setAutoReverse(true);
            transition.play();
    
    
            Group root = new Group();
            root.getChildren().addAll(curve, start, end, rectPath);
    
            stage.setScene(new Scene( root, 400, 400, Color.ALICEBLUE));
            stage.show();
        }
    
        private Point2D bezier(double t, Point2D... points) {
            if (points.length == 2) {
                return points[0].multiply(1-t).add(points[1].multiply(t));
            }
            Point2D[] leftArray = new Point2D[points.length - 1];
            System.arraycopy(points, 0, leftArray, 0, points.length - 1);
            Point2D[] rightArray = new Point2D[points.length - 1];
            System.arraycopy(points, 1, rightArray, 0, points.length - 1);
            return bezier(t, leftArray).multiply(1-t).add(bezier(t, rightArray).multiply(t));
        }
    
        private Point2D bezierDeriv(double t, Point2D... points) {
            if (points.length == 2) {
                return points[1].subtract(points[0]);
            }
            Point2D[] leftArray = new Point2D[points.length - 1];
            System.arraycopy(points, 0, leftArray, 0, points.length - 1);
            Point2D[] rightArray = new Point2D[points.length - 1];
            System.arraycopy(points, 1, rightArray, 0, points.length - 1);
            return bezier(t, leftArray).multiply(-1).add(bezierDeriv(t, leftArray).multiply(1-t))
                    .add(bezier(t, rightArray)).add(bezierDeriv(t, rightArray).multiply(t));
        }
    
    
    
        /**
         * Create draggable anchor points
         */
        class Anchor extends Circle {
            Anchor(Color color, DoubleProperty x, DoubleProperty y) {
                super(x.get(), y.get(), 10);
                setFill(color.deriveColor(1, 1, 1, 0.5));
                setStroke(color);
                setStrokeWidth(2);
                setStrokeType(StrokeType.OUTSIDE);
    
                x.bind(centerXProperty());
                y.bind(centerYProperty());
                enableDrag();
            }
    
            // make a node movable by dragging it around with the mouse.
            private void enableDrag() {
                final Delta dragDelta = new Delta();
    
                setOnMousePressed(mouseEvent -> {
                    // record a delta distance for the drag and drop operation.
                    dragDelta.x = getCenterX() - mouseEvent.getX();
                    dragDelta.y = getCenterY() - mouseEvent.getY();
                    getScene().setCursor(Cursor.MOVE);
                });
    
                setOnMouseReleased(mouseEvent -> getScene().setCursor(Cursor.HAND));
    
                setOnMouseDragged(mouseEvent -> {
                    double newX = mouseEvent.getX() + dragDelta.x;
                    if (newX > 0 && newX < getScene().getWidth()) {
                        setCenterX(newX);
                    }
                    double newY = mouseEvent.getY() + dragDelta.y;
                    if (newY > 0 && newY < getScene().getHeight()) {
                        setCenterY(newY);
                    }
                });
    
                setOnMouseEntered(mouseEvent -> {
                    if (!mouseEvent.isPrimaryButtonDown()) {
                        getScene().setCursor(Cursor.HAND);
                    }
                });
    
                setOnMouseExited(mouseEvent -> {
                    if (!mouseEvent.isPrimaryButtonDown()) {
                        getScene().setCursor(Cursor.DEFAULT);
                    }
                });
            }
    
            // records relative x and y co-ordinates.
            private class Delta { double x, y; }
        }
    }
    

    我不知道是代码、数学还是只是动画,但这在某种程度上是非常令人满意的......

    【讨论】:

    • 这绝对是美丽的!非常感谢,这非常有帮助。我同意它非常迷人...... :) 我用它在地图上创建轨迹(路径)并模拟在它上面行驶的车辆;我现在可以在车辆保持在其轨迹上时平移/缩放地图。一个小问题:如果您更改“transition.setCycleCount(Animation.INDEFINITE);”到一个有限的数,节点停止。如果您将锚点拖动到节点停止的位置,则节点不再“粘”在锚点上,而是保持在动画结束时的位置。
    猜你喜欢
    • 2020-10-08
    • 1970-01-01
    • 2021-05-26
    • 2017-07-24
    • 2019-04-19
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 2019-11-09
    相关资源
    最近更新 更多