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