1

Is there any way in JavaFX to get the x,y coordinates of the path i draw on screen on a Canvas?

I have an app that draw some Circles on a Canvas and then it connects the Circles with Lines.

After that i can change the positions of the Circles by dragging the nodes and the program is always redrawing the Lines between the Circles on the Canvas

But now i want to draw a a QuadCurve between those Circles.

public void drawQuadCurveOnCanvas(double startX, double startY, double endX, double endY, double controlX, double controlY) {
        // Set line width
        lineDraw.setLineWidth(Constants.LINE_THICKNESS);
        // Set the Color
        lineDraw.setStroke(Constants.DRAG_COLOR);
        // Start the Path
        lineDraw.beginPath();
        lineDraw.moveTo(startX, startY);
        lineDraw.quadraticCurveTo(controlX, controlY, endX, endY);
        // Draw the Path
        lineDraw.stroke();
    }

I draw the QuadCurve with one of the Circles as the control point and other two Circles as the start and end coordinates.

So i wanted to get the x,y coordinates along the QuadCurve i just draw so i can create some Circles on those coordinates and after that when i connect those Circles with Lines i get the same QuadCurve.

I don't know if i explained myself well, anyone with any idea how to accomplish what i want?

  • 4
    The x-coordinate of any point on the curve is given by `(1-t)*(1-t)*startX + 2*t*(1-t)*controlX + t*t*endX` for some `t` between zero and 1. (And a similar formula for the y-coordinate.) So just pick a series of values of t between zero and one, compute the x and y coordinates using those formulae, and place the circles at those points. – James_D Sep 07 '22 at 10:38
  • [mcve] please, demonstrating what you want to achieve and how it doesn't work as expected – kleopatra Sep 07 '22 at 10:45
  • Thank you @James_D, i think is that what i needed, i'm gona try to implement that. – Alexandre Carvalho Sep 07 '22 at 11:01
  • 3
    A similar, but not the same, question regarding placing stuff along an [arbitrary path](https://stackoverflow.com/questions/17300457/how-to-write-text-along-a-bezier-curve/17374726#17374726). – jewelsea Sep 07 '22 at 11:03

1 Answers1

5

For a Quadratic Bézier Curve, any point on the curve can be written as

x = (1-t)*(1-t)*startX + 2*t*(1-t)*controlX + t*t*endX
y = (1-t)*(1-t)*startY + 2*t*(1-t)*controlY + t*t*endY

where 0t1.

To plot a number of circles along a Quadratic Bézier Curve, just pick some values between 0 and 1, and create a bunch of circles whose centers are given by the coordinates in the formula above. You can use bindings if the control points might change. Here's a quick example that creates circles bound to the coordinates along the curve, with line segments whose end points are in turn bound to the circles.

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class BezierCurve extends Application {

    private Circle start;
    private Circle end;
    private Circle control;
    private Pane drawingPane;
    private List<Circle> points;
    private List<Line> segments;

    private Circle createPoint(double t) {
        Circle c = new Circle(4);
        c.setFill(Color.BLACK);
        c.centerXProperty().bind(Bindings.createDoubleBinding(
                () -> (1-t)*(1-t)*start.getCenterX() 
                     + 2*t*(1-t)*control.getCenterX() 
                     + t*t*end.getCenterX(),
                start.centerXProperty(),
                end.centerXProperty(),
                control.centerXProperty())
        );
        c.centerYProperty().bind(Bindings.createDoubleBinding(
                () -> (1-t)*(1-t)*start.getCenterY() 
                    + 2*t*(1-t)*control.getCenterY() 
                    + t*t*end.getCenterY(),
                start.centerYProperty(),
                end.centerYProperty(),
                control.centerYProperty())
        );
        return c;
    }

    private Line createSegment(Circle start, Circle end) {
        Line segment = new Line();
        segment.startXProperty().bind(start.centerXProperty());
        segment.startYProperty().bind(start.centerYProperty());
        segment.endXProperty().bind(end.centerXProperty());
        segment.endYProperty().bind(end.centerYProperty());
        return segment ;
    }

    @Override
    public void start(Stage stage) throws IOException {
        drawingPane = new Pane();
        points = new ArrayList<>();
        segments = new ArrayList<>();

        start = new Circle(100,100, 10, Color.GREEN);
        end = new Circle(700, 100, 10, Color.GREEN);
        control = new Circle(400, 500, 10, Color.GREEN);

        for (Circle c : List.of(start, end, control)) setUpDragging(c);

        Spinner<Integer> numSegmentsSpinner = new Spinner(5, Integer.MAX_VALUE, 25, 5);
        numSegmentsSpinner.setEditable(true);
        numSegmentsSpinner.valueProperty().addListener(
            (obs, oldValue, newValue) -> populatePointsAndSegments(newValue)
        );
        HBox ctrls = new HBox(5, new Label("Number of segments:"), numSegmentsSpinner);

        populatePointsAndSegments(numSegmentsSpinner.getValue()); ;
        drawingPane.getChildren().addAll(start, end, control);

        BorderPane root = new BorderPane();
        root.setCenter(drawingPane);
        root.setTop(ctrls);
        Scene scene = new Scene(root, 800, 800);
        stage.setScene(scene);
        stage.show();
    }

    private void populatePointsAndSegments(int numSegments) {

        drawingPane.getChildren().removeAll(points);
        drawingPane.getChildren().removeAll(segments);
        points.clear();
        segments.clear();

        Circle previousCircle = start ;

        for (int i = 1 ; i < numSegments; i++) {
            double t = 1.0 * i / numSegments ;
            Circle c = createPoint(t);
            points.add(c);
            segments.add(createSegment(previousCircle, c));
            previousCircle = c ;
        }
        segments.add(createSegment(previousCircle, end));
        drawingPane.getChildren().addAll(points);
        drawingPane.getChildren().addAll(segments);
    }

    private void setUpDragging(Circle c) {
        c.setOnMouseDragged(e -> {
            c.setCenterX(e.getX());
            c.setCenterY(e.getY());
        });
    }

    public static void main(String[] args) {
        launch();
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322