View Javadoc
1   /*
2    * Copyright (C) 2017 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.irurueta.geometry;
18  
19  import java.io.Serializable;
20  import java.util.Collection;
21  
22  /**
23   * Abstract class defining the base interface that all 3D points should have.
24   * 3D points describe points in a 3D space such as the Euclidean space. They can
25   * be  implemented either as homogeneous or inhomogeneous points.
26   */
27  public abstract class Point3D implements Serializable, Point<Point3D> {
28  
29      /**
30       * Defines the threshold used when comparing two values.
31       */
32      public static final double DEFAULT_COMPARISON_THRESHOLD = 1e-10;
33  
34      /**
35       * Constant defining minimum threshold.
36       */
37      public static final double MIN_THRESHOLD = 0.0;
38  
39      /**
40       * Length of homogeneous coordinates array.
41       */
42      public static final int POINT3D_HOMOGENEOUS_COORDINATES_LENGTH = 4;
43  
44      /**
45       * Length of inhomogeneous coordinates array.
46       */
47      public static final int POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH = 3;
48  
49      /**
50       * Default type of coordinates.
51       */
52      public static final CoordinatesType DEFAULT_COORDINATES_TYPE = CoordinatesType.HOMOGENEOUS_COORDINATES;
53  
54      /**
55       * Constructor of this class.
56       */
57      protected Point3D() {
58      }
59  
60      /**
61       * Creates and returns an instance of any existing subclass of Point3D
62       * specified in coordinatesType. The right size of the provided array is
63       * also checked depending on the type of coordinates used.
64       *
65       * @param coordinatesType Type of coordinates used.
66       * @param v               Array containing the coordinates of the 3D homogeneous or
67       *                        inhomogeneous point.
68       * @return Created Point3D.
69       * @throws IllegalArgumentException Raised if the size of provided array is
70       *                                  not valid.
71       */
72      public static Point3D create(final CoordinatesType coordinatesType, final double[] v) {
73          if (coordinatesType == CoordinatesType.INHOMOGENEOUS_COORDINATES) {
74              return new InhomogeneousPoint3D(v);
75          } else {
76              return new HomogeneousPoint3D(v);
77          }
78      }
79  
80      /**
81       * Creates and returns an instance of any existing subclass of Point3D
82       * depending on provided vector length. Size of provided vector is also
83       * checked to ensure it has appropriate size to represent 3D points either
84       * using inhomogeneous or homogeneous coordinates.
85       *
86       * @param v Array containing the coordinates of the 3D homogeneous or
87       *          inhomogeneous 3D point.
88       * @return Created Point3D.
89       * @throws IllegalArgumentException Raised if the size of provided array
90       *                                  is not valid.
91       */
92      public static Point3D create(final double[] v) {
93          return create(DEFAULT_COORDINATES_TYPE, v);
94      }
95  
96      /**
97       * Creates and returns an instance of any existing subclass of Point3D
98       * specified in coordinatesType.
99       *
100      * @param coordinatesType Type of coordinates used.
101      * @return Created Point3D.
102      */
103     public static Point3D create(final CoordinatesType coordinatesType) {
104         if (coordinatesType == CoordinatesType.INHOMOGENEOUS_COORDINATES) {
105             return new InhomogeneousPoint3D();
106         } else {
107             return new HomogeneousPoint3D();
108         }
109     }
110 
111     /**
112      * Creates and returns an instance of an existing subclass of Point3D
113      * using DEFAULT_COORDINATES_TYPE.
114      *
115      * @return Create Point3D.
116      */
117     public static Point3D create() {
118         return create(DEFAULT_COORDINATES_TYPE);
119     }
120 
121     /**
122      * Returns an array containing the coordinates of this Point3D.
123      *
124      * @return Array containing coordinates of this Point3D.
125      */
126     public abstract double[] asArray();
127 
128     /**
129      * Uses provided array to store the coordinates of this Point3D
130      *
131      * @param array Array where coordinates will be stored.
132      * @throws IllegalArgumentException Raised if length of array is not valid.
133      */
134     public abstract void asArray(final double[] array);
135 
136     /**
137      * Sets the coordinates of a 3D point using an array containing its
138      * coordinates.
139      *
140      * @param v Array containing the coordinates of the point.
141      * @throws IllegalArgumentException Raised if provided array does not have
142      *                                  a valid size.
143      */
144     public abstract void setCoordinates(final double[] v);
145 
146     /**
147      * Sets coordinates of this instance using the coordinates of provided 3D
148      * point.
149      *
150      * @param point Input point.
151      */
152     public abstract void setCoordinates(final Point3D point);
153 
154     /**
155      * Returns X homogeneous coordinate of this 3D point.
156      *
157      * @return X homogeneous coordinate.
158      */
159     public abstract double getHomX();
160 
161     /**
162      * Returns Y homogeneous coordinate of this 3D point.
163      *
164      * @return Y homogeneous coordinate.
165      */
166     public abstract double getHomY();
167 
168     /**
169      * Returns Z homogeneous coordinate of this 3D point.
170      *
171      * @return Z homogeneous coordinate.
172      */
173     public abstract double getHomZ();
174 
175     /**
176      * Returns W homogeneous coordinate of this 3D point.
177      *
178      * @return W homogeneous coordinate.
179      */
180     public abstract double getHomW();
181 
182     /**
183      * Sets coordinates of this 3D point instance using provided homogeneous
184      * coordinates.
185      *
186      * @param homX x homogeneous coordinate.
187      * @param homY y homogeneous coordinate.
188      * @param homZ z homogeneous coordinate.
189      * @param homW w homogeneous coordinate.
190      */
191     public abstract void setHomogeneousCoordinates(
192             final double homX, final double homY, final double homZ, final double homW);
193 
194     /**
195      * Returns X inhomogeneous coordinate of this 3D point.
196      *
197      * @return X inhomogeneous coordinate.
198      */
199     public abstract double getInhomX();
200 
201     /**
202      * Sets X inhomogeneous coordinate of this 3D point.
203      *
204      * @param inhomX X inhomogeneous coordinate.
205      */
206     public abstract void setInhomX(final double inhomX);
207 
208     /**
209      * Returns Y inhomogeneous coordinate of this 3D point.
210      *
211      * @return Y inhomogeneous coordinate.
212      */
213     public abstract double getInhomY();
214 
215     /**
216      * Sets Y inhomogeneous coordinate of this 3D point.
217      *
218      * @param inhomY Y inhomogeneous coordinate.
219      */
220     public abstract void setInhomY(final double inhomY);
221 
222     /**
223      * Returns Z inhomogeneous coordinate of this 3D point.
224      *
225      * @return Z inhomogeneous coordinate.
226      */
227     public abstract double getInhomZ();
228 
229     /**
230      * Sets Z inhomogeneous coordinate of this 3D point.
231      *
232      * @param inhomZ Z inhomogeneous coordinate.
233      */
234     public abstract void setInhomZ(final double inhomZ);
235 
236     /**
237      * Sets coordinates of this 3D point instance using provided inhomogeneous
238      * coordinates.
239      *
240      * @param inhomX x inhomogeneous coordinate.
241      * @param inhomY y inhomogeneous coordinate.
242      * @param inhomZ z inhomogeneous coordinate.
243      */
244     public abstract void setInhomogeneousCoordinates(
245             final double inhomX, final double inhomY, final double inhomZ);
246 
247     /**
248      * Checks if the 3D point described by this class equals the input Point3D
249      * (using a comparison threshold).
250      *
251      * @param point     Point that will be compared to.
252      * @param threshold threshold used to check that the difference of the
253      *                  values is close to zero with an absolute error defined by threshold.
254      * @return True if current point and input point are the same, false
255      * otherwise.
256      * @throws IllegalArgumentException Raised if threshold is negative.
257      */
258     public abstract boolean equals(final Point3D point, final double threshold);
259 
260     /**
261      * Checks if the 3D point described by this class equals the input Point3D
262      * (using DEFAULT_COMPARISON_THRESHOLD).
263      *
264      * @param point Point that will be compared to.
265      * @return True if current point and input point are the same, false
266      * otherwise.
267      */
268     public boolean equals(final Point3D point) {
269         return equals(point, DEFAULT_COMPARISON_THRESHOLD);
270     }
271 
272     /**
273      * Checks if provided object equals current 3D point.
274      *
275      * @param obj Object to compare.
276      * @return True if both objects are considered to be equal, false otherwise.
277      */
278     @Override
279     public boolean equals(final Object obj) {
280         if (!(obj instanceof Point3D point)) {
281             return false;
282         }
283         if (obj == this) {
284             return true;
285         }
286 
287         return equals(point);
288     }
289 
290     /**
291      * Returns hash code value. This is only defined to keep the compiler happy.
292      * This method must be overridden in subclasses of this class.
293      *
294      * @return Hash code.
295      */
296     @Override
297     public abstract int hashCode();
298 
299     /**
300      * Checks whether this Point3D is at infinity or not.
301      *
302      * @return True if the point is at infinity. False otherwise.
303      */
304     public abstract boolean isAtInfinity();
305 
306     /**
307      * Returns the type of coordinates used to represent a Point3D.
308      *
309      * @return Type of coordinates of this 2d point.
310      */
311     public abstract CoordinatesType getType();
312 
313     /**
314      * Method to normalize a 3D point. This only applies to homogeneous
315      * 2d points, otherwise it has no effect.
316      * This method is meant to be overridden.
317      */
318     public void normalize() {
319     }
320 
321     /**
322      * Returns boolean indicating whether this point has already been
323      * normalized.
324      * This method is meant to be overridden. By default, it will always return
325      * true, to indicate that no further normalization is possible.
326      *
327      * @return True if normalized, false otherwise.
328      */
329     public boolean isNormalized() {
330         return true;
331     }
332 
333     /**
334      * Returns number of dimensions of this point implementation.
335      *
336      * @return number of dimensions.
337      */
338     @Override
339     public int getDimensions() {
340         return POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH;
341     }
342 
343     /**
344      * Gets value of inhomogeneous coordinate for provided dimension.
345      *
346      * @param dim dimension to retrieve coordinate for (i.e. 0 means x, 1 means y, 2 means z, etc).
347      * @return value of inhomogeneous coordinate.
348      * @throws IllegalArgumentException if provided dimension value is negative or exceeds number of dimensions.
349      */
350     @Override
351     public double getInhomogeneousCoordinate(final int dim) {
352         if (dim < 0 || dim >= getDimensions()) {
353             throw new IllegalArgumentException();
354         }
355 
356         return switch (dim) {
357             case 0 -> getInhomX();
358             case 1 -> getInhomY();
359             default -> getInhomZ();
360         };
361     }
362 
363     /**
364      * Sets value of inhomogeneous coordinate for provided dimension.
365      *
366      * @param dim   dimension to set coordinate for (i.e. 0 means x, 1 means y, 2 means z, etc.).
367      * @param value value to be set.
368      * @throws IllegalArgumentException if provided dimension value is negative or exceeds number of dimensions.
369      */
370     @Override
371     public void setInhomogeneousCoordinate(final int dim, final double value) {
372         switch (dim) {
373             case 0:
374                 setInhomX(value);
375                 break;
376             case 1:
377                 setInhomY(value);
378                 break;
379             case 2:
380                 setInhomZ(value);
381                 break;
382             default:
383                 throw new IllegalArgumentException();
384         }
385     }
386 
387     /**
388      * Returns Euclidean distance between this point and provided point.
389      *
390      * @param point Point to compare.
391      * @return Euclidean distance between this point and provided point.
392      */
393     @Override
394     public double distanceTo(final Point3D point) {
395         return Math.sqrt(sqrDistanceTo(point));
396     }
397 
398     /**
399      * Returns squared Euclidean distance between this point and provided point.
400      *
401      * @param point point to compare.
402      * @return Euclidean distance between this point and provided point.
403      */
404     @Override
405     public double sqrDistanceTo(final Point3D point) {
406         final var diffX = getInhomX() - point.getInhomX();
407         final var diffY = getInhomY() - point.getInhomY();
408         final var diffZ = getInhomZ() - point.getInhomZ();
409 
410         return diffX * diffX + diffY * diffY + diffZ * diffZ;
411     }
412 
413     /**
414      * Computes the dot product between the homogeneous coordinates x, y, z, w
415      * of this point and the ones of provided point.
416      *
417      * @param point point to compute dot product with.
418      * @return dot product value.
419      */
420     public double dotProduct(final Point3D point) {
421         final var thisHomX = getHomX();
422         final var thisHomY = getHomY();
423         final var thisHomZ = getHomZ();
424         final var thisHomW = getHomW();
425         final var otherHomX = point.getHomX();
426         final var otherHomY = point.getHomY();
427         final var otherHomZ = point.getHomZ();
428         final var otherHomW = point.getHomW();
429 
430         final var thisNormSqr = thisHomX * thisHomX + thisHomY * thisHomY + thisHomZ * thisHomZ + thisHomW * thisHomW;
431         final var otherNormSqr = otherHomX * otherHomX + otherHomY * otherHomY + otherHomZ * otherHomZ
432                 + otherHomW * otherHomW;
433         final var denom = Math.sqrt(thisNormSqr * otherNormSqr);
434         final var num = thisHomX * otherHomX + thisHomY * otherHomY + thisHomZ * otherHomZ + thisHomW * otherHomW;
435 
436         return num / denom;
437     }
438 
439     /**
440      * Returns true if this point is between points point1 and point2, in other
441      * words, is inside the segment formed by those 2 points.
442      *
443      * @param point1 Point 1.
444      * @param point2 Point 2.
445      * @return True if point is between point1 and point2, false otherwise.
446      */
447     public boolean isBetween(final Point3D point1, final Point3D point2) {
448         return isBetween(point1, point2, DEFAULT_COMPARISON_THRESHOLD);
449     }
450 
451     /**
452      * Returns true if this point is between points point1 and point2, in other
453      * words, is inside the segment formed by those 2 points.
454      *
455      * @param point1    Point 1.
456      * @param point2    Point 2.
457      * @param threshold Threshold to determine if point is between.
458      * @return True if point is between point1 and point2, false otherwise.
459      */
460     public boolean isBetween(final Point3D point1, final Point3D point2, final double threshold) {
461         if (threshold < MIN_THRESHOLD) {
462             throw new IllegalArgumentException();
463         }
464         // If this point is between point1 and point2 then,
465         // dist(point1,this) + dist(point2, this) == dist(point1,point2) except
466         // for some small difference due to machine precision
467         return Math.abs(distanceTo(point1) + distanceTo(point2) - point1.distanceTo(point2)) <= threshold;
468     }
469 
470     /**
471      * Computes the centroid of provided collection of points by computing the
472      * mean of their inhomogeneous coordinates.
473      *
474      * @param points collection of points to compute centroid from.
475      * @param result instance where computed centroid will be stored.
476      */
477     public static void centroid(final Collection<Point3D> points, final Point3D result) {
478         var x = 0.0;
479         var y = 0.0;
480         var z = 0.0;
481         if (points != null) {
482             final var n = points.size();
483             for (final var point : points) {
484                 x += point.getInhomX();
485                 y += point.getInhomY();
486                 z += point.getInhomZ();
487             }
488 
489             x /= n;
490             y /= n;
491             z /= n;
492         }
493         result.setInhomogeneousCoordinates(x, y, z);
494     }
495 
496     /**
497      * Computes the centroid of provided collection of points by computing the
498      * mean of their inhomogeneous coordinates.
499      *
500      * @param points collection of points to compute centroid from.
501      * @return computed centroid.
502      */
503     public static Point3D centroid(final Collection<Point3D> points) {
504         final var result = Point3D.create();
505         centroid(points, result);
506         return result;
507     }
508 }