0

I'm a beginner in JavaFX, and I'm working on a project where I'm using four Bezier curves to draw a circle. My goal is to eventually achieve a "wobbly" effect by allowing the curves to be deformed when the user drags them. To accomplish this, I want to add a feature that restores the original state of the circle when the user releases the mouse click (EDIT) performing an animation that makes the curve move to its initial path, which I stored in the "initialPath" list.

While I have chosen to use JavaFX for this project, I am aware that it may not be the best tool for the job... I'm just trying to explore JavaFX's capabilities and would appreciate any guidance or tips on how to implement this functionality. Thank you for your help.

I tried working with Timeline and Keyframes' interpolation, I can't seem to find any other question on this exact topic and I'm finding it hard to grasp. This is the state of my code at the moment:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.CubicCurveTo;
    import javafx.scene.shape.MoveTo;
    import javafx.scene.shape.Path;
    import javafx.scene.shape.PathElement;
    import javafx.stage.Stage;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;

    public class BezierCurves extends Application {

    private double dragStartX;
    private double dragStartY;
    private List<Path> initialPaths = new ArrayList<>();
    private Pane pane;


    public Path drawCurve(double[] moveTo, double[] coordinates, int color) {
        Path path = new Path();
        switch (color) {
            case 0 -> path.setStroke(Color.RED);
            case 1 -> path.setStroke(Color.BLUE);
            case 2 -> path.setStroke(Color.GREEN);
            case 3 -> path.setStroke(Color.ORANGE);
            default -> path.setStroke(Color.BLACK);
        }
        path.setStrokeWidth(2);
        path.setFill(null);
        MoveTo moveTo1 = new MoveTo(moveTo[0], moveTo[1]);
        CubicCurveTo curveTo1 = new CubicCurveTo(coordinates[0],
                coordinates[1],
                coordinates[2],
                coordinates[3],
                coordinates[4],
                coordinates[5]);
        path.getElements().addAll(moveTo1, curveTo1);
        return path;
    }

    @Override
    public void start(Stage primaryStage) {
        pane = new Pane();
        Scene scene = new Scene(pane, 400, 400);
        double centerX = 200, centerY = 200, radius = 100, kappa = 0.5522848;

        Path path1 = drawCurve(new double[]{centerX, centerY - radius},
                new double[]{centerX + radius * kappa,
                        centerY - radius,
                        centerX + radius,
                        centerY - radius * kappa,
                        centerX + radius, centerY}, 0);
        path1.setOnMousePressed(this::handleMousePressed);
        path1.setOnMouseDragged(this::handleMouseDragged);
        path1.setOnMouseReleased(this::handleMouseReleased);

        Path path2 = drawCurve(new double[]{centerX + radius, centerY},
                new double[]{centerX + radius,
                        centerY + radius * kappa,
                        centerX + radius * kappa,
                        centerY + radius, centerX,
                        centerY + radius}, 1);
        path2.setOnMousePressed(this::handleMousePressed);
        path2.setOnMouseDragged(this::handleMouseDragged);
        path2.setOnMouseReleased(this::handleMouseReleased);

        Path path3 = drawCurve(new double[]{centerX, centerY + radius},
                new double[]{centerX - radius * kappa,
                        centerY + radius,
                        centerX - radius,
                        centerY + radius * kappa,
                        centerX - radius, centerY}, 2);
        path3.setOnMousePressed(this::handleMousePressed);
        path3.setOnMouseDragged(this::handleMouseDragged);
        path3.setOnMouseReleased(this::handleMouseReleased);

        Path path4 = drawCurve(new double[]{centerX - radius, centerY},
                new double[]{centerX - radius,
                        centerY - radius * kappa,
                        centerX - radius * kappa,
                        centerY - radius,
                        centerX,
                        centerY - radius}, 3);
        path4.setOnMousePressed(this::handleMousePressed);
        path4.setOnMouseDragged(this::handleMouseDragged);
        path4.setOnMouseReleased(this::handleMouseReleased);

        initialPaths.addAll(Arrays.asList(path1, path2, path3, path4));
        pane.getChildren().addAll(path1, path2, path3, path4);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void handleMousePressed(MouseEvent event) {
        dragStartX = event.getX();
        dragStartY = event.getY();
    }

    private void handleMouseDragged(MouseEvent event) {
        double offsetX = event.getX() - dragStartX;
        double offsetY = event.getY() - dragStartY;
       
        Path path = (Path) event.getSource();

        for (PathElement element : path.getElements()) {
            if (element instanceof CubicCurveTo curve) {
                curve.setControlX1(curve.getControlX1() + offsetX * 0.02);
                curve.setControlY1(curve.getControlY1() + offsetY * 0.02);
                curve.setControlX2(curve.getControlX2() + offsetX * 0.02);
                curve.setControlY2(curve.getControlY2() + offsetY * 0.02);
            }
        }
    }

    private void handleMouseReleased(MouseEvent event) {
            // TODO
        }

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

    }
  • 1
    What _specific_ thing do you need help with relating to this code though? – Mike 'Pomax' Kamermans May 02 '23 at 20:53
  • I'm sorry this is my first question here, I am trying to write a handleMouseReleased() method that restores the original state of the path (after it has been dragged and deformed by handleMouseDragged(). I tried searching online and I believe I should be using the Timeline class, but I need some guidance. I saved the original state of the paths in the List initialPaths. – Dario Peluso May 02 '23 at 21:21
  • 1
    If you're just responding to mouse events, then there's no real reason to use a timeline, simply cache the original state, and on mouse release, replace the current state with the cached state. – Mike 'Pomax' Kamermans May 02 '23 at 22:02
  • You are right my comment was too vague. My intention is to make a simple animation transitioning from the dragged state to the original state. But I will start applying your suggestion, first things first :) thank you – Dario Peluso May 02 '23 at 23:24
  • Just for your info, saving the paths (in initialPaths) makes no difference. Because the moment you dragged, the path properties are already updated and there is no way to get the initial values. For that you need to take backup of each property. Check my answer for a possible solution. – Sai Dandem May 04 '23 at 23:39

1 Answers1

1

For this requirement, all you need to do is to store the initial values of controlX1,Y1,X2,Y2 and reset the curves control properties to the initial values using a Timeline.

Below is the output, if you add the below changes to your code:

enter image description here

private double x1, y1, x2, y2;

private void handleMousePressed(MouseEvent event) {
    dragStartX = event.getX();
    dragStartY = event.getY();
    Path path = (Path) event.getSource();
    for (PathElement element : path.getElements()) {
        if (element instanceof CubicCurveTo curve) {
            x1 = curve.getControlX1();
            y1 = curve.getControlY1();
            x2 = curve.getControlX2();
            y2 = curve.getControlY2();
        }
    }
}

private void handleMouseReleased(MouseEvent event) {
    Path path = (Path) event.getSource();
    for (PathElement element : path.getElements()) {
        if (element instanceof CubicCurveTo curve) {
            // Create key values to reset the properties to the initial state
            KeyValue kx1 = new KeyValue(curve.controlX1Property(), x1);
            KeyValue ky1 = new KeyValue(curve.controlY1Property(), y1);
            KeyValue kx2 = new KeyValue(curve.controlX2Property(), x2);
            KeyValue ky2 = new KeyValue(curve.controlY2Property(), y2);
            // Change the speed as per your needs
            Duration speed = Duration.millis(300);
            Timeline tl = new Timeline(new KeyFrame(speed, kx1, ky1, kx2, ky2));
            tl.play();
        }
    }
}
Sai Dandem
  • 8,229
  • 11
  • 26