View Javadoc
1   package org.djutils.draw.point;
2   
3   import java.awt.geom.Point2D;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.Locale;
7   
8   import org.djutils.base.AngleUtil;
9   import org.djutils.draw.DrawRuntimeException;
10  import org.djutils.draw.Oriented2d;
11  import org.djutils.exceptions.Throw;
12  
13  /**
14   * The OrientedPoint2d is a point in a 2-dimensional space with an orientation vector, which is specified in terms of its
15   * counter-clockwise rotation around the point in radians.
16   * <p>
17   * Copyright (c) 2020-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19   * </p>
20   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   */
23  public class OrientedPoint2d extends Point2d implements Oriented2d<OrientedPoint2d>
24  {
25      /** */
26      private static final long serialVersionUID = 20200828L;
27  
28      /** The counter-clockwise rotation around the point in radians. */
29      @SuppressWarnings("checkstyle:visibilitymodifier")
30      public final double dirZ;
31  
32      /**
33       * Construct an oriented point with an x and y coordinate and a direction equal to 0.0.
34       * @param x double; the x coordinate
35       * @param y double; the y coordinate
36       * @throws IllegalArgumentException when any coordinate is NaN
37       */
38      public OrientedPoint2d(final double x, final double y) throws IllegalArgumentException
39      {
40          super(x, y);
41          this.dirZ = 0;
42      }
43  
44      /**
45       * Construct an oriented point with an x and y coordinate and a direction.
46       * @param x double; the x coordinate
47       * @param y double; the y coordinate
48       * @param dirZ double; the counter-clockwise rotation around the point in radians
49       * @throws IllegalArgumentException when any coordinate or dirZ is NaN
50       */
51      public OrientedPoint2d(final double x, final double y, final double dirZ) throws IllegalArgumentException
52      {
53          super(x, y);
54          Throw.when(Double.isNaN(dirZ), IllegalArgumentException.class, "rotZ must be a number (not NaN)");
55          this.dirZ = dirZ;
56      }
57  
58      /**
59       * Construct an oriented point with an x and y coordinate and a direction.
60       * @param xy double[]; the x and y coordinate
61       * @param dirZ double; the counter-clockwise rotation around the point in radians
62       * @throws NullPointerException when xy is null
63       * @throws IllegalArgumentException when the dimension of xy is not 2 or any value in xy is NaN or rotZ is NaN
64       */
65      public OrientedPoint2d(final double[] xy, final double dirZ) throws IllegalArgumentException
66      {
67          super(xy);
68          Throw.when(Double.isNaN(dirZ), IllegalArgumentException.class, "rotZ must be a number (not NaN)");
69          this.dirZ = dirZ;
70      }
71  
72      /**
73       * Construct an oriented point from an AWT Point2D and a direction.
74       * @param point Point2D; an AWT Point2D
75       * @param dirZ double; the counter-clockwise rotation around the point in radians
76       * @throws IllegalArgumentException when any coordinate in point is NaN, or rotZ is NaN
77       */
78      public OrientedPoint2d(final Point2D point, final double dirZ) throws IllegalArgumentException
79      {
80          super(point);
81          Throw.when(Double.isNaN(dirZ), IllegalArgumentException.class, "rotZ must be a number (not NaN)");
82          this.dirZ = dirZ;
83      }
84  
85      /**
86       * Construct an oriented point from a Point2d and a direction.
87       * @param point Point2d; a point (with or without orientation)
88       * @param dirZ double; the counter-clockwise rotation around the point in radians
89       * @throws IllegalArgumentException when rotZ is NaN
90       */
91      public OrientedPoint2d(final Point2d point, final double dirZ) throws IllegalArgumentException
92      {
93          super(point.x, point.y);
94          Throw.when(Double.isNaN(dirZ), IllegalArgumentException.class, "rotZ must be a number (not NaN)");
95          this.dirZ = dirZ;
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     public OrientedPoint2d translate(final double dx, final double dy) throws IllegalArgumentException
101     {
102         Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class, "translation may not be NaN");
103         return new OrientedPoint2d(getX() + dx, getY() + dy, this.dirZ);
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public OrientedPoint3d translate(final double dx, final double dy, final double z) throws IllegalArgumentException
109     {
110         return new OrientedPoint3d(getX() + dx, getY() + dy, z, 0, 0, this.dirZ);
111     }
112 
113     /** {@inheritDoc} */
114     @Override
115     public OrientedPoint2d scale(final double factor) throws IllegalArgumentException
116     {
117         Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
118         return new OrientedPoint2d(getX() * factor, getY() * factor, this.dirZ);
119     }
120 
121     /** {@inheritDoc} */
122     @Override
123     public OrientedPoint2d neg()
124     {
125         return new OrientedPoint2d(-getX(), -getY(), this.dirZ + Math.PI);
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     public OrientedPoint2d abs()
131     {
132         return new OrientedPoint2d(Math.abs(getX()), Math.abs(getY()), this.dirZ);
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public OrientedPoint2d normalize()
138     {
139         double length = Math.sqrt(getX() * getX() + getY() * getY());
140         Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
141         return this.scale(1.0 / length);
142     }
143 
144     /**
145      * Interpolate towards another Point with a fraction. It is allowed for fraction to be less than zero or larger than 1. In
146      * that case the interpolation turns into an extrapolation. DirZ is interpolated using the AngleUtil.interpolateShortest
147      * method.
148      * @param otherPoint OrientedPoint2d; the other point
149      * @param fraction double; the factor for interpolation towards the other point. When &lt;code&gt;fraction&lt;/code&gt; is
150      *            between 0 and 1, it is an interpolation, otherwise an extrapolation. If <code>fraction</code> is 0;
151      *            <code>this</code> Point is returned; if <code>fraction</code> is 1, the other <code>point</code> is returned
152      * @return OrientedPoint2d; a new OrientedPoint2d at the requested fraction
153      * @throws NullPointerException when otherPoint is null
154      * @throws IllegalArgumentException when fraction is NaN
155      */
156     public OrientedPoint2d interpolate(final OrientedPoint2d otherPoint, final double fraction)
157             throws NullPointerException, IllegalArgumentException
158     {
159         Throw.whenNull(otherPoint, "point cannot be null");
160         Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
161         return new OrientedPoint2d((1.0 - fraction) * getX() + fraction * otherPoint.x,
162                 (1.0 - fraction) * getY() + fraction * otherPoint.y,
163                 AngleUtil.interpolateShortest(getDirZ(), otherPoint.getDirZ(), fraction));
164     }
165 
166     /**
167      * Return a new OrientedPoint2d with an in-place rotation around the z-axis by the provided delta. The resulting rotation is
168      * normalized between -&pi; and &pi;.
169      * @param rotateZ double; the rotation around the z-axis
170      * @return OrientedPoint; a new point with the same coordinates and applied rotation
171      * @throws IllegalArgumentException when deltaRotZ is NaN
172      */
173     public OrientedPoint2d rotate(final double rotateZ) throws IllegalArgumentException
174     {
175         Throw.when(Double.isNaN(rotateZ), IllegalArgumentException.class, "deltaDirZ must be a number (not NaN)");
176         return new OrientedPoint2d(getX(), getY(), AngleUtil.normalizeAroundZero(getDirZ() + rotateZ));
177     }
178 
179     /** {@inheritDoc} */
180     @Override
181     public double getDirZ()
182     {
183         return this.dirZ;
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public Iterator<? extends OrientedPoint2d> getPoints()
189     {
190         return Arrays.stream(new OrientedPoint2d[] { this }).iterator();
191     }
192 
193     /** {@inheritDoc} */
194     @Override
195     public String toString()
196     {
197         return toString("%f", false);
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
203     {
204         String format =
205                 String.format("%1$s[x=%2$s, y=%2$s, rot=%2$s]", doNotIncludeClassName ? "" : "OrientedPoint2d ", doubleFormat);
206         return String.format(Locale.US, format, this.x, this.y, this.dirZ);
207     }
208 
209     /** {@inheritDoc} */
210     @Override
211     public boolean epsilonEquals(final OrientedPoint2d other, final double epsilonCoordinate, final double epsilonRotation)
212             throws NullPointerException, IllegalArgumentException
213     {
214         Throw.whenNull(other, "other point cannot be null");
215         if (Math.abs(this.x - other.x) > epsilonCoordinate)
216         {
217             return false;
218         }
219         if (Math.abs(this.y - other.y) > epsilonCoordinate)
220         {
221             return false;
222         }
223         if (Math.abs(AngleUtil.normalizeAroundZero(this.dirZ - other.dirZ)) > epsilonRotation)
224         {
225             return false;
226         }
227         return true;
228     }
229 
230     /** {@inheritDoc} */
231     @Override
232     public int hashCode()
233     {
234         final int prime = 31;
235         int result = super.hashCode();
236         long temp;
237         temp = Double.doubleToLongBits(this.dirZ);
238         result = prime * result + (int) (temp ^ (temp >>> 32));
239         return result;
240     }
241 
242     /** {@inheritDoc} */
243     @Override
244     @SuppressWarnings("checkstyle:needbraces")
245     public boolean equals(final Object obj)
246     {
247         if (this == obj)
248             return true;
249         if (!super.equals(obj))
250             return false;
251         OrientedPoint2d other = (OrientedPoint2d) obj;
252         if (Double.doubleToLongBits(this.dirZ) != Double.doubleToLongBits(other.dirZ))
253             return false;
254         return true;
255     }
256 
257 }