001/*
002 *   GeomPoint  -- Holds the floating point coordinates of a point in nD space.
003 *
004 *   Copyright (C) 2002-2018, Joseph A. Huwaldt
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom;
023
024import jahuwaldt.js.param.Parameter;
025import jahuwaldt.js.param.ParameterVector;
026import jahuwaldt.js.param.Vector3D;
027import jahuwaldt.util.XYPoint;
028import java.text.MessageFormat;
029import static java.util.Objects.isNull;
030import static java.util.Objects.nonNull;
031import static java.util.Objects.requireNonNull;
032import javax.measure.quantity.Area;
033import javax.measure.quantity.Dimensionless;
034import javax.measure.quantity.Length;
035import javax.measure.unit.Unit;
036import javolution.context.StackContext;
037import javolution.lang.MathLib;
038import javolution.text.Text;
039import javolution.text.TextBuilder;
040import javolution.util.FastTable;
041import org.jscience.mathematics.number.Float64;
042import org.jscience.mathematics.vector.Float64Vector;
043
044/**
045 * A container that holds the coordinates of a point in n-dimensional space.
046 * 
047 * <p> * Modified by: Joseph A. Huwaldt </p>
048 * 
049 * @author Joseph A. Huwaldt, Date: December 11, 1999
050 * @version February 5, 2018
051 */
052@SuppressWarnings({"serial", "CloneableImplementsClone"})
053public abstract class GeomPoint extends AbstractGeomElement<GeomPoint>
054        implements PointGeometry<GeomPoint>, Transformable<GeomPoint>, XYPoint {
055
056    /**
057     * Constant used to identify the X or 1st coordinate.
058     */
059    public static final int X = 0;
060
061    /**
062     * Constant used to identify the Y or 2nd coordinate.
063     */
064    public static final int Y = 1;
065
066    /**
067     * Constant used to identify the Z or 3rd coordinate.
068     */
069    public static final int Z = 2;
070
071    /**
072     * Constant used to identify the W or 4th coordinate.
073     */
074    public static final int W = 2;
075
076    /**
077     * Return an immutable version of this point.
078     *
079     * @return an immutable version of this point.
080     */
081    public abstract Point immutable();
082
083    /**
084     * Returns the number of child-elements that make up this geometry element. This
085     * implementation always returns 0 as a GeomPoint is not made up of any other
086     * elements.
087     */
088    @Override
089    public int size() {
090        return 0;
091    }
092
093    /**
094     * Returns the number of parametric dimensions of the geometry element. This
095     * implementation always returns 0 as a GeomPoint is not parametric.
096     */
097    @Override
098    public int getParDimension() {
099        return 0;
100    }
101
102    /**
103     * Returns the value of a Parameter from this point.
104     *
105     * @param i the dimension index.
106     * @return the value of the parameter at <code>i</code>.
107     * @throws IndexOutOfBoundsException
108     * <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
109     */
110    public Parameter<Length> get(int i) {
111        return Parameter.valueOf(getValue(i), getUnit());
112    }
113
114    /**
115     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
116     * this point's {@link #getUnit unit}.
117     *
118     * @param i the dimension index.
119     * @return the value of the Parameter at <code>i</code>.
120     * @throws IndexOutOfBoundsException
121     * <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
122     */
123    public abstract double getValue(int i);
124
125    /**
126     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
127     * the specified unit.
128     *
129     * @param i    the dimension index.
130     * @param unit the unit to return the value in. May not be null.
131     * @return the value of the Parameter at <code>i</code> in the specified unit.
132     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; dimension())</code>
133     */
134    public abstract double getValue(int i, Unit<Length> unit);
135
136    /**
137     * Return the X-coordinate of this point as a <code>double</code>, stated in
138     * this point's {@link #getUnit unit}.
139     * 
140     * @return the value of the X-coordinate in the current units.
141     */
142    @Override
143    public double getX() {
144        return this.getValue(X);
145    }
146    
147    /**
148     * Return the Y-coordinate of this point as a <code>double</code>, stated in
149     * this point's {@link #getUnit unit}.
150     * 
151     * @return the value of the Y-coordinate in the current units.
152     */
153    @Override
154    public double getY() {
155        return this.getValue(Y);
156    }
157    
158    /**
159     * Return the Z-coordinate of this point as a <code>double</code>, stated in
160     * this point's {@link #getUnit unit}.
161     * 
162     * @return the value of the Z-coordinate in the current units.
163     */
164    public double getZ() {
165        return this.getValue(Z);
166    }
167    
168    /**
169     * Returns the square of the Euclidean norm, magnitude, or length value of the vector
170     * from the origin to this point (the dot product of the origin-to-this-point vector
171     * and itself). This is slightly faster than calling <code>normValue</code> if the
172     * squared value is all that is needed.
173     *
174     * @return <code>this.normSq().getValue()</code>.
175     * @see #normValue() 
176     */
177    public abstract double normSqValue();
178
179    /**
180     * Returns the square of the Euclidean norm, magnitude, or length of the vector from
181     * the origin to this point (the dot product of the origin-to-this-point vector and
182     * itself). This is slightly faster than calling <code>normValue</code> if the
183     * squared value is all that is needed.
184     *
185     * @return <code>this · this</code>.
186     * @see #norm() 
187     */
188    public Parameter<Area> normSq() {
189        return Parameter.valueOf(normSqValue(), getUnit().pow(2)).asType(Area.class);
190    }
191
192    /**
193     * Returns the {@link #norm}, magnitude, or length value of the vector from the origin
194     * to this point (square root of the dot product of the origin-to-this-point vector
195     * and itself).
196     *
197     * @return <code>this.norm().doubleValue()</code>.
198     * @see #normSqValue() 
199     */
200    public double normValue() {
201        return MathLib.sqrt(normSqValue());
202    }
203
204    /**
205     * Returns the Euclidian norm, magnitude, or length of the vector from the origin to
206     * this point (square root of the dot product of the origin-to-this-point vector and
207     * itself).
208     *
209     * @return <code>sqrt(this · this)</code>.
210     * @see #normValue() 
211     */
212    public Parameter<Length> norm() {
213        return Parameter.valueOf(normValue(), getUnit());
214    }
215
216    /**
217     * Returns the sum of this point with the one specified. The unit of the output point
218     * will be the units of this point.
219     *
220     * @param that the point to be added. May not be null.
221     * @return <code>this + that</code>.
222     * @throws DimensionException if point dimensions are different.
223     */
224    public abstract Point plus(GeomPoint that);
225
226    /**
227     * Adds the specified parameter to each component of this point. The unit of the
228     * output point will be the units of this point.
229     *
230     * @param that the parameter to be added to each component of this point. May not be
231     *             null.
232     * @return <code>this + that</code>.
233     */
234    public abstract Point plus(Parameter<Length> that);
235
236    /**
237     * Returns the difference between this point and the one specified. The unit of the
238     * output point will be the units of this point.
239     *
240     * @param that the point to be subtracted from this point. May not be null.
241     * @return <code>this - that</code>.
242     * @throws DimensionException if point dimensions are different.
243     */
244    public abstract Point minus(GeomPoint that);
245
246    /**
247     * Subtracts the specified parameter from each component of this point. The unit of
248     * the output point will be the units of this point.
249     *
250     * @param that the parameter to be subtracted from each component of this point. May
251     *             not be null.
252     * @return <code>this - that</code>.
253     */
254    public abstract Point minus(Parameter<Length> that);
255
256    /**
257     * Returns the negation of this point (all the values of each dimension negated).
258     *
259     * @return <code>-this</code>
260     */
261    public abstract Point opposite();
262
263    /**
264     * Returns the product of this point with the specified coefficient.
265     *
266     * @param k the coefficient multiplier.
267     * @return <code>this · k</code>
268     */
269    public abstract Point times(double k);
270
271    /**
272     * Returns the product of this point with the specified dimensionless Parameter.
273     *
274     * @param k the dimensionless Parameter multiplier. May not be null.
275     * @return <code>this · k</code>
276     */
277    public Point times(Parameter<Dimensionless> k) {
278        return times(k.getValue(Dimensionless.UNIT));
279    }
280
281    /**
282     * Returns this point with each element divided by the specified divisor.
283     *
284     * @param divisor the divisor.
285     * @return <code>this / divisor</code>.
286     */
287    public Point divide(double divisor) {
288        return times(1.0 / divisor);
289    }
290
291    /**
292     * Returns this point with each element divided by the specified dimensionless
293     * Parameter.
294     *
295     * @param divisor the dimensionless Parameter divisor. May not be null.
296     * @return <code>this / divisor</code>.
297     */
298    public Point divide(Parameter<Dimensionless> divisor) {
299        return divide(divisor.getValue(Dimensionless.UNIT));
300    }
301
302    /**
303     * Return the square of the Euclidian distance between this point and the one
304     * specified. This is slightly faster than calling <code>distance</code> if the
305     * squared value is all that you need.
306     *
307     * @param that The point to determine the distance squared from this point to. May not
308     *             be null.
309     * @return the distance squared from this point to the one specified.
310     * @see #distance(geomss.geom.GeomPoint) 
311     */
312    public Parameter<Area> distanceSq(GeomPoint that) {
313        return Parameter.valueOf(distanceSqValue(that), getUnit().pow(2)).asType(Area.class);
314    }
315
316    /**
317     * Return the Euclidian distance squared between this point and the one specified as a
318     * <code>double</code>. This is slightly faster than calling <code>distance</code> if
319     * the squared value is all that you need.
320     *
321     * @param that The point to determine the distance squared from this point to. May not
322     *             be null.
323     * @return the distance squared from this point to the one specified.
324     * @see #distanceValue(geomss.geom.GeomPoint) 
325     */
326    public double distanceSqValue(GeomPoint that) {
327        StackContext.enter();
328        try {
329            GeomPoint dp = this.minus(requireNonNull(that));
330            return dp.normSqValue();
331        } finally {
332            StackContext.exit();
333        }
334    }
335
336    /**
337     * Return the Euclidian distance between this point and the one specified.
338     *
339     * @param that The point to determine the distance from this point to. May not be null.
340     * @return the distance from this point to the one specified.
341     * @see #distanceSq(geomss.geom.GeomPoint) 
342     */
343    public Parameter<Length> distance(GeomPoint that) {
344        return Parameter.valueOf(distanceValue(that), getUnit());
345    }
346
347    /**
348     * Return the Euclidian distance between this point and the one specified as a
349     * <code>double</code>.
350     *
351     * @param that The point to determine the distance from this point to. May not be null.
352     * @return the distance from this point to the one specified.
353     * @see #distanceSqValue(geomss.geom.GeomPoint) 
354     */
355    public double distanceValue(GeomPoint that) {
356        return MathLib.sqrt(distanceSqValue(that));
357    }
358
359    /**
360     * Returns a point consisting of the minimum value in each dimension between this
361     * point and the input point. This is used to find points that bound a geometry.
362     *
363     * @param that The point being compared with this one for minimum values in each
364     *             dimension. May not be null.
365     * @return A point consisting of the minimum value in each dimension between this
366     *         point and the input point.
367     */
368    public Point min(GeomPoint that) {
369        requireNonNull(that);
370        StackContext.enter();
371        try {
372            Unit<Length> unit = getUnit();
373
374            FastTable<Float64> valueList = FastTable.newInstance();
375            int numDims = getPhyDimension();
376            for (int i = 0; i < numDims; ++i) {
377                double value = MathLib.min(this.getValue(i), that.getValue(i, unit));
378                valueList.add(Float64.valueOf(value));
379            }
380            Float64Vector V = Float64Vector.valueOf(valueList);
381
382            Point P = Point.valueOf(V, unit);
383            return StackContext.outerCopy(P);
384
385        } finally {
386            StackContext.exit();
387        }
388    }
389
390    /**
391     * Returns a point consisting of the maximum value in each dimension between this
392     * point and the input point. This is used to find points that bound a geometry.
393     *
394     * @param that The point being compared with this one for maximum values in each
395     *             dimension. May not be null.
396     * @return A point consisting of the maximum value in each dimension between this
397     *         point and the input point.
398     */
399    public Point max(GeomPoint that) {
400        requireNonNull(that);
401        StackContext.enter();
402        try {
403            Unit<Length> unit = getUnit();
404
405            FastTable<Float64> valueList = FastTable.newInstance();
406            int numDims = getPhyDimension();
407            for (int i = 0; i < numDims; ++i) {
408                double value = MathLib.max(this.getValue(i), that.getValue(i, unit));
409                valueList.add(Float64.valueOf(value));
410            }
411            Float64Vector V = Float64Vector.valueOf(valueList);
412
413            Point P = Point.valueOf(V, getUnit());
414            return StackContext.outerCopy(P);
415
416        } finally {
417            StackContext.exit();
418        }
419    }
420
421    /**
422     * Return the coordinate point representing the minimum bounding box corner (e.g.: min
423     * X, min Y, min Z).
424     *
425     * @return The minimum bounding box coordinate for this geometry element.
426     */
427    @Override
428    public Point getBoundsMin() {
429        return this.immutable();
430    }
431
432    /**
433     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
434     * X, max Y, max Z).
435     *
436     * @return The maximum bounding box coordinate for this geometry element.
437     */
438    @Override
439    public Point getBoundsMax() {
440        return this.immutable();
441    }
442
443    /**
444     * Returns the most extreme point, either minimum or maximum, in the specified
445     * coordinate direction on this geometry element. This implementation always returns
446     * this point's coordinate.
447     *
448     * @param dim An index indicating the dimension to find the min/max point for (0=X,
449     *            1=Y, 2=Z, etc).
450     * @param max Set to <code>true</code> to return the maximum value, <code>false</code>
451     *            to return the minimum.
452     * @param tol Fractional tolerance to refine the min/max point position to if
453     *            necessary.
454     * @return The point found on this element that is the min or max in the specified
455     *         coordinate direction.
456     * @see #getBoundsMin
457     * @see #getBoundsMax
458     */
459    @Override
460    public GeomPoint getLimitPoint(int dim, boolean max, double tol) {
461        return this;
462    }
463
464    /**
465     * Return the total number of points in this geometry element. This implementation
466     * always returns 1.
467     */
468    @Override
469    public int getNumberOfPoints() {
470        return 1;
471    }
472
473    /**
474     * Return <code>true</code> if this point contains valid and finite numerical
475     * components. A value of <code>false</code> will be returned if any of the coordinate
476     * values are NaN or Inf.
477     */
478    @Override
479    public boolean isValid() {
480        int numDims = getPhyDimension();
481        for (int i = 0; i < numDims; ++i) {
482            double value = getValue(i);
483            if (Double.isInfinite(value) || Double.isNaN(value))
484                return false;
485        }
486        return true;
487    }
488
489    /**
490     * Return a copy of this object with any transformations or subranges removed
491     * (applied).
492     */
493    @Override
494    public abstract Point copyToReal();
495
496    /**
497     * Returns a copy of this {@link GeomPoint} instance
498     * {@link javolution.context.AllocatorContext allocated} by the calling thread
499     * (possibly on the stack).
500     *
501     * @return an identical and independent copy of this point.
502     */
503    @Override
504    public abstract GeomPoint copy();
505
506    /**
507     * Returns a Vector3D representation of this point if possible.
508     *
509     * @return A Vector3D that is equivalent to this point
510     * @throws DimensionException if this point has any number of dimensions other than 3.
511     */
512    public Vector3D<Length> toVector3D() {
513        if (getPhyDimension() != 3)
514            throw new DimensionException(
515                    MessageFormat.format(RESOURCES.getString("dimensionNot3"), "point", getPhyDimension()));
516        return Vector3D.valueOf(this.toFloat64Vector(), this.getUnit());
517    }
518
519    /**
520     * Returns a ParameterVector representation of this point.
521     *
522     * @return A ParameterVector that is equivalent to this point
523     */
524    public ParameterVector<Length> toParameterVector() {
525        return ParameterVector.valueOf(this.toFloat64Vector(), this.getUnit());
526    }
527
528    /**
529     * Returns a <code>GeomVector</code> representation of this point.
530     *
531     * @return A GeomVector that is equivalent to this point
532     */
533    public Vector<Length> toGeomVector() {
534        return Vector.valueOf(this);
535    }
536
537    /**
538     * Returns the values stored in this point, stated in the current
539     * {@link #getUnit units}, as a Float64Vector.
540     *
541     * @return A Float64Vector containing the values stored in this point in the current
542     *         units.
543     */
544    public abstract Float64Vector toFloat64Vector();
545
546    /**
547     * Returns the values stored in this point as a Java array, stated in the
548     * current {@link #getUnit units}.
549     *
550     * @return A new array with the point values copied into it.
551     */
552    public double[] toArray() {
553        return toArray(new double[this.getPhyDimension()]);
554    }
555
556    /**
557     * Returns the values stored in this point, stated in the current
558     * {@link #getUnit units} stored in the input Java array.
559     *
560     * @param array An existing array that has at least as many elements as the physical
561     *              dimension of this point.
562     * @return A reference to the input array after the point values have been copied over
563     *         to it.
564     * @see #getPhyDimension()
565     */
566    public double[] toArray(double[] array) {
567        int numDims = this.getPhyDimension();
568        for (int i = 0; i < numDims; ++i)
569            array[i] = this.getValue(i);
570        return array;
571    }
572
573    /**
574     * Returns transformed version of this element. The returned object implements
575     * {@link GeomTransform} and contains this element as a child.
576     *
577     * @param transform The transformation to apply to this geometry. May not be null.
578     * @return A new triangle that is identical to this one with the specified
579     *         transformation applied.
580     * @throws DimensionException if this point is not 3D.
581     */
582    @Override
583    public GeomPointTrans getTransformed(GTransform transform) {
584        return GeomPointTrans.newInstance(this, requireNonNull(transform));
585    }
586
587    /**
588     * Returns the text representation of this geometry element that consists of the name
589     * followed by the coordinate values. For example:
590     * <pre>
591     *   {aPoint = {10 ft, -3 ft, 4.56 ft}}
592     * </pre>
593     * If there is no name, then the output looks like this:
594     * <pre>
595     *   {10 ft, -3 ft, 4.56 ft}
596     * </pre>
597     *
598     * @return the text representation of this geometry element.
599     */
600    @Override
601    public Text toText() {
602        final int dimension = this.getPhyDimension();
603        TextBuilder tmp = TextBuilder.newInstance();
604        tmp.append('{');
605        String nameStr = getName();
606        boolean hasName = nonNull(nameStr);
607        if (hasName) {
608            tmp.append(nameStr);
609            tmp.append(" = {");
610        }
611        for (int i = 0; i < dimension; i++) {
612            tmp.append(get(i));
613            if (i != dimension - 1) {
614                tmp.append(", ");
615            }
616        }
617        if (hasName)
618            tmp.append('}');
619        tmp.append('}');
620        Text txt = tmp.toText();
621        TextBuilder.recycle(tmp);
622        return txt;
623    }
624
625    /**
626     * Compares this point against the specified point for approximate equality
627     * (coordinate values approximately equal to this one to within the numerical roundoff
628     * tolerance).
629     *
630     * @param obj The GeomPoint object to compare with.
631     * @return <code>true</code> if this point is approximately identical to that point;
632     *         <code>false</code> otherwise.
633     */
634    public boolean isApproxEqual(GeomPoint obj) {
635        if (this == obj)
636            return true;
637        if (obj == null)
638            return false;
639
640        int numDims = getPhyDimension();
641        if (obj.getPhyDimension() != numDims)
642            return false;
643
644        for (int i = 0; i < numDims; ++i) {
645            Parameter<Length> thisi = get(i);
646            Parameter<Length> thati = obj.get(i);
647            if (!thisi.isApproxEqual(thati))
648                return false;
649        }
650
651        return true;
652    }
653
654    /**
655     * Compares this point against the specified point for approximate equality
656     * (coordinate values approximately equal to this one to within the numerical roundoff
657     * tolerance).
658     *
659     * @param obj The GeomPoint object to compare with.
660     * @param tol The amount use to define approximate equality. If <code>null</code> is
661     *            passed, exact numerical equality will be required.
662     * @return <code>true</code> if this point is approximately identical to that point;
663     *         <code>false</code> otherwise.
664     */
665    public boolean isApproxEqual(GeomPoint obj, Parameter<Length> tol) {
666        if (this == obj)
667            return true;
668        if (isNull(obj))
669            return false;
670
671        int numDims = getPhyDimension();
672        if (obj.getPhyDimension() != numDims)
673            return false;
674
675        for (int i = 0; i < numDims; ++i) {
676            Parameter<Length> thisi = get(i);
677            Parameter<Length> thati = obj.get(i);
678            if (!thisi.isApproxEqual(thati, tol))
679                return false;
680        }
681
682        return true;
683    }
684
685}