ContinuousBezier.java

package org.opentrafficsim.core.geometry;

import java.util.Arrays;

import org.djutils.draw.point.Point2d;
import org.djutils.exceptions.Throw;

/**
 * Continuous definition of a Bezier. Note that this class does not implement {@code ContinuousLine}. This class is simply a
 * helper class for (and a super of) {@code ContinuousBezierCubic}, which uses this class to determine curvature, offset lines,
 * etc.
 * <p>
 * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 * @see <a href="https://pomax.github.io/bezierinfo/">Bezier info</a>
 */
public class ContinuousBezier
{

    /** T values of numerical approach of Legendre-Gauss to determine Bezier length. */
    private static final double[] T =
            new double[] {-0.0640568928626056260850430826247450385909, 0.0640568928626056260850430826247450385909,
                    -0.1911188674736163091586398207570696318404, 0.1911188674736163091586398207570696318404,
                    -0.3150426796961633743867932913198102407864, 0.3150426796961633743867932913198102407864,
                    -0.4337935076260451384870842319133497124524, 0.4337935076260451384870842319133497124524,
                    -0.5454214713888395356583756172183723700107, 0.5454214713888395356583756172183723700107,
                    -0.6480936519369755692524957869107476266696, 0.6480936519369755692524957869107476266696,
                    -0.7401241915785543642438281030999784255232, 0.7401241915785543642438281030999784255232,
                    -0.8200019859739029219539498726697452080761, 0.8200019859739029219539498726697452080761,
                    -0.8864155270044010342131543419821967550873, 0.8864155270044010342131543419821967550873,
                    -0.9382745520027327585236490017087214496548, 0.9382745520027327585236490017087214496548,
                    -0.9747285559713094981983919930081690617411, 0.9747285559713094981983919930081690617411,
                    -0.9951872199970213601799974097007368118745, 0.9951872199970213601799974097007368118745};

    /** C values of numerical approach of Legendre-Gauss to determine Bezier length. */
    private static final double[] C =
            new double[] {0.1279381953467521569740561652246953718517, 0.1279381953467521569740561652246953718517,
                    0.1258374563468282961213753825111836887264, 0.1258374563468282961213753825111836887264,
                    0.121670472927803391204463153476262425607, 0.121670472927803391204463153476262425607,
                    0.1155056680537256013533444839067835598622, 0.1155056680537256013533444839067835598622,
                    0.1074442701159656347825773424466062227946, 0.1074442701159656347825773424466062227946,
                    0.0976186521041138882698806644642471544279, 0.0976186521041138882698806644642471544279,
                    0.086190161531953275917185202983742667185, 0.086190161531953275917185202983742667185,
                    0.0733464814110803057340336152531165181193, 0.0733464814110803057340336152531165181193,
                    0.0592985849154367807463677585001085845412, 0.0592985849154367807463677585001085845412,
                    0.0442774388174198061686027482113382288593, 0.0442774388174198061686027482113382288593,
                    0.0285313886289336631813078159518782864491, 0.0285313886289336631813078159518782864491,
                    0.0123412297999871995468056670700372915759, 0.0123412297999871995468056670700372915759};

    /** The shape points. */
    protected Point2d[] points;

    /**
     * Create a Bezier of any order.
     * @param points Point2d... shape points.
     */
    public ContinuousBezier(final Point2d... points)
    {
        Throw.whenNull(points, "Points may not be null.");
        Throw.when(points.length < 2, IllegalArgumentException.class, "Minimum number of points is 2.");
        for (Point2d point : points)
        {
            Throw.whenNull(point, "One of the points is null.");
        }
        this.points = points;
    }

    /**
     * Returns the derivative for a Bezier, which is a Bezier of 1 order lower.
     * @return ContinuousBezier; derivative Bezier.
     */
    public ContinuousBezier derivative()
    {
        Throw.when(this.points.length < 2, IllegalStateException.class,
                "Requesting derivative on Bezier with less than 2 points");
        int n = this.points.length - 1;
        Point2d[] derivativePoints = new Point2d[n];
        for (int i = 0; i < n; i++)
        {
            derivativePoints[i] =
                    new Point2d(n * (this.points[i + 1].x - this.points[i].x), n * (this.points[i + 1].y - this.points[i].y));
        }
        return new ContinuousBezier(derivativePoints);
    }

    /**
     * Returns the estimated length using the method of numerical approach of Legendre-Gauss, which is quite accurate.
     * @return double; estimated length.
     */
    public double length()
    {
        double len = 0.0;
        for (int i = 0; i < T.length; i++)
        {
            double t = 0.5 * T[i] + 0.5;
            Point2d p = derivative().at(t);
            len += C[i] * Math.hypot(p.x, p.y);
        }
        len *= 0.5;
        return len;
    }

    /**
     * Return the point for the given t value.
     * @param t double; t value, moving from 0 to 1 along the Bezier.
     * @return Point2d; point of the Bezier at t.
     */
    public Point2d at(final double t)
    {
        double[] x = new double[this.points.length];
        double[] y = new double[this.points.length];
        for (int j = 0; j < this.points.length; j++)
        {
            x[j] = this.points[j].x;
            y[j] = this.points[j].y;
        }
        return new Point2d(Bezier.Bn(t, x), Bezier.Bn(t, y));
    }

    /**
     * Returns the curvature at the given t value.
     * @param t double; t value, moving from 0 to 1 along the Bezier.
     * @return double curvature at the given t value.
     */
    public double curvature(final double t)
    {
        ContinuousBezier der = derivative();
        Point2d d = der.at(t);
        double denom = Math.pow(d.x * d.x + d.y * d.y, 3.0 / 2.0);
        if (denom == 0.0)
        {
            return Double.POSITIVE_INFINITY;
        }
        Point2d dd = der.derivative().at(t);
        double numer = d.x * dd.y - dd.x * d.y;
        return numer / denom;
    }

    /** {@inheritDoc} */
    @Override
    public String toString()
    {
        return "ContinuousBezier [points=" + Arrays.toString(this.points) + "]";
    }

}