ContinuousArc.java

  1. package org.opentrafficsim.core.geometry;

  2. import org.djunits.value.vdouble.scalar.Angle;
  3. import org.djutils.draw.line.PolyLine2d;
  4. import org.djutils.draw.point.OrientedPoint2d;
  5. import org.djutils.draw.point.Point2d;
  6. import org.djutils.exceptions.Throw;

  7. /**
  8.  * Continuous definition of an arc.
  9.  * <p>
  10.  * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  11.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  12.  * </p>
  13.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  14.  */
  15. public class ContinuousArc implements ContinuousLine
  16. {

  17.     /** Starting point. */
  18.     private final OrientedPoint2d startPoint;

  19.     /** Curve radius. */
  20.     private final double radius;

  21.     /** Angle of the curve. */
  22.     private final Angle angle;

  23.     /** Sign to use for offsets and angles, which depends on the left/right direction. */
  24.     private double sign;

  25.     /** Center point of circle, as calculated in constructor. */
  26.     private final Point2d center;

  27.     /**
  28.      * Define arc by starting point, radius, curve direction, and length.
  29.      * @param startPoint starting point.
  30.      * @param radius radius (must be positive).
  31.      * @param left left curve, or right.
  32.      * @param length arc length.
  33.      */
  34.     public ContinuousArc(final OrientedPoint2d startPoint, final double radius, final boolean left, final double length)
  35.     {
  36.         this(startPoint, radius, left, Angle.instantiateSI(
  37.                 Throw.when(length, length <= 0.0, IllegalArgumentException.class, "Length must be above 0.") / radius));
  38.     }

  39.     /**
  40.      * Define arc by starting point, radius, curve direction, and angle.
  41.      * @param startPoint starting point.
  42.      * @param radius radius (must be positive).
  43.      * @param left left curve, or right.
  44.      * @param angle angle of arc (must be positive).
  45.      */
  46.     public ContinuousArc(final OrientedPoint2d startPoint, final double radius, final boolean left, final Angle angle)
  47.     {
  48.         Throw.whenNull(startPoint, "Start point may not be null.");
  49.         Throw.when(radius < 0.0, IllegalArgumentException.class, "Radius must be positive.");
  50.         Throw.when(angle.si < 0.0, IllegalArgumentException.class, "Angle must be positive.");
  51.         this.startPoint = startPoint;
  52.         this.radius = radius;
  53.         this.sign = left ? 1.0 : -1.0;
  54.         this.angle = angle;
  55.         double dx = Math.cos(startPoint.dirZ) * this.sign * radius;
  56.         double dy = Math.sin(startPoint.dirZ) * this.sign * radius;

  57.         this.center = new Point2d(startPoint.x - dy, startPoint.y + dx);
  58.     }

  59.     @Override
  60.     public OrientedPoint2d getStartPoint()
  61.     {
  62.         return this.startPoint;
  63.     }

  64.     @Override
  65.     public OrientedPoint2d getEndPoint()
  66.     {
  67.         Point2d point = getPoint(1.0, 0.0);
  68.         double dirZ = this.startPoint.dirZ + this.sign * this.angle.si;
  69.         dirZ = dirZ > Math.PI ? dirZ - 2.0 * Math.PI : (dirZ < -Math.PI ? dirZ + 2.0 * Math.PI : 0.0);
  70.         return new OrientedPoint2d(point.x, point.y, dirZ);
  71.     }

  72.     @Override
  73.     public double getStartCurvature()
  74.     {
  75.         return 1.0 / this.radius;
  76.     }

  77.     @Override
  78.     public double getEndCurvature()
  79.     {
  80.         return getStartCurvature();
  81.     }

  82.     @Override
  83.     public double getStartRadius()
  84.     {
  85.         return this.radius;
  86.     }

  87.     @Override
  88.     public double getEndRadius()
  89.     {
  90.         return this.radius;
  91.     }

  92.     /**
  93.      * Returns a point on the arc at a fraction along the arc.
  94.      * @param fraction fraction along the arc.
  95.      * @param offset offset relative to radius.
  96.      * @return point on the arc at a fraction along the arc.
  97.      */
  98.     private Point2d getPoint(final double fraction, final double offset)
  99.     {
  100.         double len = this.radius - this.sign * offset;
  101.         double ang = this.startPoint.dirZ + this.sign * (this.angle.si * fraction);
  102.         double dx = this.sign * Math.cos(ang) * len;
  103.         double dy = this.sign * Math.sin(ang) * len;
  104.         return new Point2d(this.center.x + dy, this.center.y - dx);
  105.     }

  106.     /**
  107.      * Returns the direction at given fraction along the arc.
  108.      * @param fraction fraction along the arc.
  109.      * @return direction at given fraction along the arc.
  110.      */
  111.     private double getDirection(final double fraction)
  112.     {
  113.         double d = this.startPoint.dirZ + this.sign * (this.angle.si) * fraction;
  114.         while (d > Math.PI)
  115.         {
  116.             d -= (2 * Math.PI);
  117.         }
  118.         while (d < -Math.PI)
  119.         {
  120.             d += (2 * Math.PI);
  121.         }
  122.         return d;
  123.     }

  124.     @Override
  125.     public PolyLine2d flatten(final Flattener flattener)
  126.     {
  127.         Throw.whenNull(flattener, "Flattener may not be null.");
  128.         return flattener.flatten(new FlattableLine()
  129.         {
  130.             @Override
  131.             public Point2d get(final double fraction)
  132.             {
  133.                 return getPoint(fraction, 0.0);
  134.             }

  135.             @Override
  136.             public double getDirection(final double fraction)
  137.             {
  138.                 return ContinuousArc.this.getDirection(fraction);
  139.             }
  140.         });
  141.     }

  142.     @Override
  143.     public PolyLine2d flattenOffset(final ContinuousDoubleFunction offset, final Flattener flattener)
  144.     {
  145.         Throw.whenNull(offset, "Offset may not be null.");
  146.         Throw.whenNull(flattener, "Flattener may not be null.");
  147.         return flattener.flatten(new FlattableLine()
  148.         {
  149.             @Override
  150.             public Point2d get(final double fraction)
  151.             {
  152.                 return getPoint(fraction, offset.apply(fraction));
  153.             }

  154.             @Override
  155.             public double getDirection(final double fraction)
  156.             {
  157.                 /*-
  158.                  * x = cos(phi) * (r - s(phi))
  159.                  * y = sin(phi) * (r - s(phi))
  160.                  *
  161.                  * with,
  162.                  *   phi    = angle of circle arc point at fraction, relative to circle center
  163.                  *   r      = radius
  164.                  *   s(phi) = offset at phi (or at fraction)
  165.                  *
  166.                  * then using the product rule:
  167.                  *
  168.                  * x' = -sin(phi) * (r - s(phi)) - cos(phi) * s'(phi)
  169.                  * y' = cos(phi) * (r - s(phi)) - sin(phi) * s'(phi)
  170.                  */
  171.                 double phi = (ContinuousArc.this.startPoint.dirZ
  172.                         + ContinuousArc.this.sign * (ContinuousArc.this.angle.si * fraction - Math.PI / 2));
  173.                 double sinPhi = Math.sin(phi);
  174.                 double cosPhi = Math.cos(phi);
  175.                 double sPhi = ContinuousArc.this.sign * offset.apply(fraction);
  176.                 double sPhiD = offset.getDerivative(fraction) / ContinuousArc.this.angle.si;
  177.                 double dx = -sinPhi * (ContinuousArc.this.radius - sPhi) - cosPhi * sPhiD;
  178.                 double dy = cosPhi * (ContinuousArc.this.radius - sPhi) - sinPhi * sPhiD;
  179.                 return Math.atan2(ContinuousArc.this.sign * dy, ContinuousArc.this.sign * dx);
  180.             }
  181.         });
  182.     }

  183.     @Override
  184.     public double getLength()
  185.     {
  186.         return this.angle.si * this.radius;
  187.     }

  188.     @Override
  189.     public String toString()
  190.     {
  191.         return "ContinuousArc [startPoint=" + this.startPoint + ", radius=" + this.radius + ", angle=" + this.angle + ", left="
  192.                 + (this.sign > 0.0) + "]";
  193.     }

  194. }