Point2D.Double Kurven - Beschriftung und richtiges Anklicken

CptK

Bekanntes Mitglied
Hallo zusammen,

ich habe immer noch ein Problem mit Linien vom Typ Path2D.Double. Dabei zeichne ich jede Linie durch 3 Punkte: Anfangspunkt, Endpunkt und Anker (Mittelpunkt):
Java:
line.moveTo(from.x(), from.y());
line.curveTo(from.x(), from.y(), anchor.x, anchor.y, to.x(), to.y());
line.moveTo(to.x(), to.y());
line.closePath();
g2.drawString(String.valueOf(Arrays.toString(letters)), anchor.x + 2, anchor.y + 2);
g2.draw(line);

In der Skizze weiter unten ist der Anker als schwarzer Punkt dargestellt. Wie man sieht, wird die Linie nicht direkt durch den Anker gezeichnet (ich weiß nicht wieso, es stört mich aber eigentlich auch nicht so wirklich), allerdings nutze ich den Anker auch um Text zu positionieren. Kann ich während dem Zeichnen der Linie den Punkt bekommen, durch den die Linie wirklich geht, sodass der Text am Ende an der Linie und nicht so weit weg steht?

Das zweite Problem ist das Erkennen, ob ein Mausklick auf der Linie ist oder nicht, was ich bisher wie folgt bestimme:
Java:
final int HIT_BOX_SIZE = 10;
Rectangle2D rectangle = new Rectangle(x - HIT_BOX_SIZE / 2, y - HIT_BOX_SIZE / 2, HIT_BOX_SIZE, HIT_BOX_SIZE);

return line.intersects(rectangle);
Wobei x und y für die Koordinaten des Klicks stehen. Bei einer geraden Linie funktioniert das auch einwandfrei, bei einer Kurve hingegen, wird auch bei einem Klick auf die Fläche unter der Linie (In der Abbildung unten grün) true zurückgegeben. Wie kann ich das so umschreiben, dass wirklich nur bei einem Klick auf die Linie true zurück geliefert wird?

1616927168903.png
 
Das du als Anker bezeichnest, ist kein Anker oder Mittelpunkt, sondern Teil einer Bezier Kurve:

Da sieht man auch schön, warum der Punkt nicht drauf liegt im Bild dort.

Anhand der Formeln lässt sich mit Sicherheit auch die Fläche darunter berechnen, bzw. berechnen ob ein Punkt innerhalb oder außerhalb liegt.
 
Das du als Anker bezeichnest, ist kein Anker oder Mittelpunkt, sondern Teil einer Bezier Kurve:

Da sieht man auch schön, warum der Punkt nicht drauf liegt im Bild dort.

Anhand der Formeln lässt sich mit Sicherheit auch die Fläche darunter berechnen, bzw. berechnen ob ein Punkt innerhalb oder außerhalb liegt.
das sieht vorsichtig ausgedrückt interessant aus...
Und sowas gibts nicht zufällig schon fertig bzw. in einer einfacheren Form, die für meine Zwecke reicht?
 
EH, ich will dich nicht irritieren, aber du zeichnest mit deinem Code bereits genau eine Bézierkurve...
Ja ja, das habe ich verstanden aber da gebe ich dem drei Punkte und der macht den Rest für mich ^^
Wenn ich da jetzt aber selbst berechnen muss, wo ich meinen Text hinschreiben soll oder ob ich jetzt auf die Linie geklickt habe oder daneben, bin ich raus
 
Also, ich habe jetzt eine Bezierkurve 2. Grades implementiert, wobei ich nach folgendem Schema vorgegangen bin:
1617033950655.png
Im folgenden Code werden die Schritte 1 - 4 durch diese Methoden umgesetzt:
Java:
1 -> calculateGraphBetweennPoints()
2 -> straightBetweenAnchorsX()
3 -> getFunction()
4 -> calculate()

Java:
public class BezierCurve {

    private Point2D anchor1;
    private Point2D anchor2;
    private Point2D controlPoint;

    public BezierCurve(Point2D anchor1, Point2D anchor2, Point2D controlPoint) {
        this.anchor1 = anchor1;
        this.anchor2 = anchor2;
        this.controlPoint = controlPoint;
    }

    public Point2D calculate(double t) {
        // (x, y) -> (x(t), p(t))
        int x = (int) FunctionEvaluator.evaluate(straightBetweenAnchorsX(), "t", t);
        int y = (int) FunctionEvaluator.evaluate(getFunction(), "t", t);
       
        return new Point2D(x, y);
    }

    private String getFunction() {
        return calculateGraphBetweennPoints().replaceAll("x", "("+straightBetweenAnchorsX() + ")");
    }

    /**
     * @return the graph of the BezierCurve in form of g(x) = ax^2 + bx + c as
     *         String
     */
    private String calculateGraphBetweennPoints() {
        // p(x) = a * x^2 + b * x + c
        // p'(x) = 2 * a * x + b

        // I  p'(anchor1.x) =
        double mA1C = (double) (controlPoint.y - anchor1.y) / (double) (controlPoint.x - anchor1.x);
        // = 2ax + b = 2 * anchor1.x * a + b

        // II p'(anchor2.x) =
        double mA2C = (double) (anchor2.y - controlPoint.y) / (double) (anchor2.x - controlPoint.x);
        // = 2ax + b = 2 * anchor2.x * a + b
       
        // I - II -> p'(anchor1.x) - p'(anchor2.x) = (2 * anchor1.x * a + b) - (2 *
        // anchor2.x * a + b)
        double a = (mA1C - mA2C) / (double) ((2 * anchor1.x) - (2 * anchor2.x));

        // a in I -> b
        double b = mA1C - (2 * a * (double) anchor1.x);

        // III p(anchor1.x) = anchor1.y
        double pA1x = (double) anchor1.y;
        // = ax^2 + b*x + c = anchor1.x^2 * a + anchor.x * b + c
       
        double c = pA1x - (a * Math.pow(anchor1.x, 2)) - (b * anchor1.x);

        return "(" + FunctionEvaluator.doubleToString(a) + ") * (x^2) + (" + FunctionEvaluator.doubleToString(b)
                + ") * x + (" + FunctionEvaluator.doubleToString(c) + ")";
    }

    public void setControlPoint(Point2D controlPoint) {
        this.controlPoint = controlPoint;
    }

    /**
     * @return a function for scaling the BezierCurve dependent on a parameter t =
     *         0, ... , 1 as String
     */
    private String straightBetweenAnchorsX() {
        return FunctionEvaluator.doubleToString((Math.abs(anchor1.x) + Math.abs(anchor2.x))) + " * t + ("
                + FunctionEvaluator.doubleToString(Math.min(anchor1.x, anchor2.x)) + ")";
    }
}

Während ich jetzt meine drei Punkte nutze, um eine Linie vom Typ Path2D.Double zu zeichnen, erstelle ich mit den gleichen Punkten ein Objekt vom Typ BezierCurve. Allerdings komm ich nicht so recht weiter, wie ich nun mit gegebener Koordinate eines Mausklicks bestimmen kann, ob dieser auf der Linie ist oder nicht.
 
Also, ich habe jetzt eine Bezierkurve 2. Grades implementiert, wobei ich nach folgendem Schema vorgegangen bin:
Anhang anzeigen 15127
Im folgenden Code werden die Schritte 1 - 4 durch diese Methoden umgesetzt:
Java:
1 -> calculateGraphBetweennPoints()
2 -> straightBetweenAnchorsX()
3 -> getFunction()
4 -> calculate()

Java:
public class BezierCurve {

    private Point2D anchor1;
    private Point2D anchor2;
    private Point2D controlPoint;

    public BezierCurve(Point2D anchor1, Point2D anchor2, Point2D controlPoint) {
        this.anchor1 = anchor1;
        this.anchor2 = anchor2;
        this.controlPoint = controlPoint;
    }

    public Point2D calculate(double t) {
        // (x, y) -> (x(t), p(t))
        int x = (int) FunctionEvaluator.evaluate(straightBetweenAnchorsX(), "t", t);
        int y = (int) FunctionEvaluator.evaluate(getFunction(), "t", t);
      
        return new Point2D(x, y);
    }

    private String getFunction() {
        return calculateGraphBetweennPoints().replaceAll("x", "("+straightBetweenAnchorsX() + ")");
    }

    /**
     * @return the graph of the BezierCurve in form of g(x) = ax^2 + bx + c as
     *         String
     */
    private String calculateGraphBetweennPoints() {
        // p(x) = a * x^2 + b * x + c
        // p'(x) = 2 * a * x + b

        // I  p'(anchor1.x) =
        double mA1C = (double) (controlPoint.y - anchor1.y) / (double) (controlPoint.x - anchor1.x);
        // = 2ax + b = 2 * anchor1.x * a + b

        // II p'(anchor2.x) =
        double mA2C = (double) (anchor2.y - controlPoint.y) / (double) (anchor2.x - controlPoint.x);
        // = 2ax + b = 2 * anchor2.x * a + b
      
        // I - II -> p'(anchor1.x) - p'(anchor2.x) = (2 * anchor1.x * a + b) - (2 *
        // anchor2.x * a + b)
        double a = (mA1C - mA2C) / (double) ((2 * anchor1.x) - (2 * anchor2.x));

        // a in I -> b
        double b = mA1C - (2 * a * (double) anchor1.x);

        // III p(anchor1.x) = anchor1.y
        double pA1x = (double) anchor1.y;
        // = ax^2 + b*x + c = anchor1.x^2 * a + anchor.x * b + c
      
        double c = pA1x - (a * Math.pow(anchor1.x, 2)) - (b * anchor1.x);

        return "(" + FunctionEvaluator.doubleToString(a) + ") * (x^2) + (" + FunctionEvaluator.doubleToString(b)
                + ") * x + (" + FunctionEvaluator.doubleToString(c) + ")";
    }

    public void setControlPoint(Point2D controlPoint) {
        this.controlPoint = controlPoint;
    }

    /**
     * @return a function for scaling the BezierCurve dependent on a parameter t =
     *         0, ... , 1 as String
     */
    private String straightBetweenAnchorsX() {
        return FunctionEvaluator.doubleToString((Math.abs(anchor1.x) + Math.abs(anchor2.x))) + " * t + ("
                + FunctionEvaluator.doubleToString(Math.min(anchor1.x, anchor2.x)) + ")";
    }
}

Während ich jetzt meine drei Punkte nutze, um eine Linie vom Typ Path2D.Double zu zeichnen, erstelle ich mit den gleichen Punkten ein Objekt vom Typ BezierCurve. Allerdings komm ich nicht so recht weiter, wie ich nun mit gegebener Koordinate eines Mausklicks bestimmen kann, ob dieser auf der Linie ist oder nicht.
Also kleines Update: der Code von oben ist Müll, funktioniert nicht und ist viel zu kompliziert, hier was besseres:
Java:
public static VectorPoint quadraticBezier(Point2D p0, Point2D p1, Point2D c, double t, style s, Color color) {
        double x = Math.pow(1 - t, 2) * p0.x + (1 - t) * 2 * t * p1.x + t * t * c.x;
        double y = Math.pow(1 - t, 2) * p0.y + (1 - t) * 2 * t * p1.y + t * t * c.y;
        return new VectorPoint(s, color, x, y);
    }
 
Falls jemand anderes ähnlich Funktionalitäten sucht wie ich und das nicht selbst schreiben möchte hier die ganze QuadraticBezier-Klasse:
Java:
package math.linear_algebra;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Path2D;

import math.Point2D;
import math.Point2D.style;

/**
 *
 * @author Marcus Kornmann
 * @version 1.0.0
 *
 */
public class QuadraticBezier {

    public final Point2D anchor1;
    public final Point2D anchor2;
    protected Point2D c1;

    public QuadraticBezier(Point2D anchor1, Point2D anchor2, Point2D controlPoint1) {
        this.anchor1 = anchor1;
        this.anchor2 = anchor2;
        this.c1 = controlPoint1;
    }

    /**
     *
     * @param t     Parameter depending on which the point is calculated
     * @param s     Style of calculated VectorPoint
     * @param color Color of calculated VectoPoint
     * @return a VectorPoint calculated depending on t
     */
    public VectorPoint calculate(double t, style s, Color color) {
        return quadraticBezier(anchor1, c1, anchor2, t, s, color);
    }

    /**
     * Computes a line of type Path2D.Double through the two anchor-points and the
     * control-point and draws it
     *
     * @param g2 Graphics
     */
    public void drawAsPath2DDouble(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Path2D.Double line = new Path2D.Double();
        line.moveTo(anchor1.x, anchor1.y);
        line.curveTo(anchor1.x, anchor1.y, c1.x, c1.y, anchor2.x, anchor2.y);
        line.moveTo(anchor2.x, anchor2.y);
        line.closePath();
        g2.draw(line);
    }

    /**
     * Computes points in the interval step and draws a line between the current and
     * the next point
     *
     * @param g2    Graphics
     * @param step  distance between the calculated points
     * @param color Color of the line and the calculated VectorPoints
     */
    public void drawThroughPoints(Graphics2D g2, double step, Color color) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(color);
        VectorPoint p = calculate(0, style.dot, color);
        for (double i = 0.0; i <= 1; i = i + step) {
            VectorPoint q = calculate(i, style.dot, color);
            g2.drawLine((int) p.getData()[0], (int) p.getData()[1], (int) q.getData()[0], (int) q.getData()[1]);
            p = q;
        }
    }

    /**
     * Checks whether the Bézier-Line is clicked
     *
     * @param x        coordinate of the mouse click
     * @param y        coordinate of the mouse click
     * @param accuracy maximum allowed distance between line and click-position
     * @return return true, if there is a point on the line whose distance to the
     *         click-position is <= the give accuracy, false else
     */
    public boolean isClicked(int x, int y, double accuracy) {
        for (double i = 0.0; i <= 1; i = i + 0.001) {
            VectorPoint p = calculate(i, null, null);
            if (Math.sqrt(Math.pow(p.getData()[0] - x, 2) + Math.pow(p.getData()[1] - y, 2)) <= accuracy)
                return true;
        }
        return false;
    }

    /**
     * @param p0    First anchor-point
     * @param c     Control-point
     * @param p1    Second anchor-point
     * @param t     Parameter the point is calculated for
     * @param s     Style of calculated point
     * @param color Color of calculated point
     * @return the point on the line between p0, c, p1 dependent on t
     */
    public static VectorPoint quadraticBezier(Point2D p0, Point2D c, Point2D p1, double t, style s, Color color) {
        double x = Math.pow(1 - t, 2) * p0.x + (1 - t) * 2 * t * c.x + t * t * p1.x;
        double y = Math.pow(1 - t, 2) * p0.y + (1 - t) * 2 * t * c.y + t * t * p1.y;
        return new VectorPoint(s, color, x, y);
    }

}

Die isClicked-Methode ist vermutlich nicht optimal (was Effizienz angeht) umgesetzt aber sie funktioniert einwandfrei.
Die drawThroughPoints-Methode zeichnet eine Kurve die allerdings etwas verpixelt ist, falls jemand ne Methode hat, wie das besser geht, nur her damit.
Die drawAsPath2DDouble-Methode zwichnet zwar eine smoothe Kurve, dafür unterscheidet sich die Kurve leicht von der aus der eigenen Berechnung, keine Ahnung wieso.
 

Zurück
Oben