001/*
002 *   GeomUtil  -- Static utility methods used by the geometry package.
003 *
004 *   Copyright (C) 2000-2025, by 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 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 *   Library 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.tools.math.MathTools;
026import java.text.MessageFormat;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.List;
030import static java.util.Objects.isNull;
031import static java.util.Objects.nonNull;
032import static java.util.Objects.requireNonNull;
033import java.util.ResourceBundle;
034import javax.measure.converter.UnitConverter;
035import javax.measure.quantity.Angle;
036import javax.measure.quantity.Dimensionless;
037import javax.measure.quantity.Length;
038import javax.measure.unit.NonSI;
039import javax.measure.unit.SI;
040import javax.measure.unit.Unit;
041import javolution.context.ConcurrentContext;
042import javolution.context.ImmortalContext;
043import javolution.context.StackContext;
044import static javolution.lang.MathLib.PI;
045import static javolution.lang.MathLib.TWO_PI;
046import static javolution.lang.MathLib.abs;
047import static javolution.lang.MathLib.sqrt;
048import javolution.util.FastTable;
049import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
050import org.apache.commons.math3.geometry.euclidean.twod.hull.AklToussaintHeuristic;
051import org.apache.commons.math3.geometry.euclidean.twod.hull.MonotoneChain;
052import org.jscience.mathematics.vector.Float64Matrix;
053import org.jscience.mathematics.vector.Float64Vector;
054
055/**
056 * A collection of static methods used by classes in the geometry package.
057 *
058 * <p> Modified by: Joseph A. Huwaldt </p>
059 *
060 * @author Joseph A. Huwaldt, Date: March 31, 2009
061 * @version February 17, 2025
062 */
063public final class GeomUtil {
064
065    /**
066     * The resource bundle for this package.
067     */
068    private static final ResourceBundle RESOURCES = AbstractGeomElement.RESOURCES;
069
070    /**
071     * The machine epsilon or unit roundoff for <code>double</code> in the Java
072     * environment. Machine epsilon gives an upper bound on the relative error due to
073     * rounding in floating point arithmetic.
074     */
075    public static final double EPS = MathTools.EPS;
076
077    /**
078     * The square-root of EPS.
079     *
080     * @see #EPS
081     */
082    public static final double SQRT_EPS = MathTools.SQRT_EPS;
083
084    //  A default geometry tolerance.
085    private static final Parameter<Length> GTOL;
086
087    static {
088        ImmortalContext.enter();
089        try {
090            GTOL = Parameter.valueOf(1e-8, SI.METER);
091        } finally {
092            ImmortalContext.exit();
093        }
094    }
095
096    /**
097     * Compute the average of an array of points (returning the mid-point between them all).
098     *
099     * @param pnts The array of points to average. There must be at least one point.
100     * @return The average of the input points.
101     */
102    public static Point averagePoints(GeomPoint... pnts) {
103        StackContext.enter();
104        try {
105            
106            Point pavg = pnts[0].immutable();
107            int numPnts = pnts.length;
108            for (int i=1; i < numPnts; ++i) {
109                pavg = pavg.plus(pnts[i]);
110            }
111            pavg = pavg.divide(numPnts);
112            return StackContext.outerCopy(pavg);
113            
114        } finally {
115            StackContext.exit();
116        }
117    }
118    
119    /**
120     * Compute the average of a list of points (returning the mid-point between them all).
121     *
122     * @param pnts The list of points to average. There must be at least one point.
123     * @return The average of the input points.
124     */
125    public static Point averagePoints(List<? extends GeomPoint> pnts) {
126        StackContext.enter();
127        try {
128            
129            Point avg = pnts.get(0).immutable();
130            int numPnts = pnts.size();
131            for (int i=1; i < numPnts; ++i) {
132                avg = avg.plus(pnts.get(i));
133            }
134            avg = avg.divide(numPnts);
135            return StackContext.outerCopy(avg);
136            
137        } finally {
138            StackContext.exit();
139        }
140    }
141    
142    /**
143     * Returns the total transformation represented by an entire chain of GeomTransform
144     * objects below the one provided.
145     *
146     * @param tElem The transformed geometry element to have the total TM returned for.
147     *              May not be null.
148     * @return The transformation matrix (TM) that represents the entire chain of
149     *         transformations contained in a nested set of transformed geometry elements.
150     */
151    public static GTransform getTotalTransform(GeomTransform tElem) {
152        StackContext.enter();
153        try {
154
155            GTransform T = tElem.getTransform();
156            while (true) {
157                Transformable elem = tElem.getChild();
158                if (!(elem instanceof GeomTransform))
159                    break;
160                tElem = (GeomTransform)elem;
161                T = T.times(tElem.getTransform());
162            }
163            return StackContext.outerCopy(T);
164
165        } finally {
166            StackContext.exit();
167        }
168    }
169
170    /**
171     * Returns the input angle (in radians) bounded to the range 0 - 2*PI.
172     *
173     * @param angle The angle (in radians) to be bounded.
174     * @return The input angle bounded to the range 0 to 2*PI (radians).
175     */
176    public static double bound2Pi(double angle) {
177        while (angle > TWO_PI)
178            angle -= TWO_PI;
179        while (angle < 0)
180            angle += TWO_PI;
181        return angle;
182    }
183
184    /**
185     * Returns the input angle bounded to the range 0 - 2*PI.
186     *
187     * @param angle The angle to be bounded. May not be null.
188     * @return The input angle bounded to the range 0 to 2*PI.
189     */
190    public static Parameter<Angle> bound2Pi(Parameter<Angle> angle) {
191        StackContext.enter();
192        try {
193
194            while (angle.isGreaterThan(Parameter.TWOPI_ANGLE)) {
195                angle = angle.minus(Parameter.TWOPI_ANGLE);
196            }
197            while (angle.isLessThan(Parameter.ZERO_ANGLE)) {
198                angle = angle.plus(Parameter.TWOPI_ANGLE);
199            }
200            return StackContext.outerCopy(angle);
201
202        } finally {
203            StackContext.exit();
204        }
205    }
206
207    /**
208     * Returns the input angle (in radians) bounded to the range +/-PI.
209     *
210     * @param angle The angle (in radians) to be bounded.
211     * @return The input angle bounded to the range -PI to +PI (radians).
212     */
213    public static double boundPi(double angle) {
214        while (angle > PI)
215            angle -= TWO_PI;
216        while (angle < -PI)
217            angle += TWO_PI;
218        return angle;
219    }
220
221    /**
222     * Returns the input angle bounded to the range +/-PI (+/- 180 deg).
223     *
224     * @param angle The angle to be bounded. May not be null.
225     * @return The input angle bounded to the range -PI to +PI.
226     */
227    public static Parameter<Angle> boundPi(Parameter<Angle> angle) {
228        StackContext.enter();
229        try {
230
231            while (angle.isGreaterThan(Parameter.PI_ANGLE)) {
232                angle = angle.minus(Parameter.TWOPI_ANGLE);
233            }
234            Parameter NEG_PI_ANGLE = Parameter.PI_ANGLE.opposite();
235            while (angle.isLessThan(NEG_PI_ANGLE)) {
236                angle = angle.plus(Parameter.TWOPI_ANGLE);
237            }
238            return StackContext.outerCopy(angle);
239
240        } finally {
241            StackContext.exit();
242        }
243    }
244
245    /**
246     * Return the maximum physical dimension among a collection of geometry elements.
247     *
248     * @param elements The array of geometry elements to query. May not be null.
249     * @return The maximum physical dimension among the input list of geometry elements.
250     */
251    public static int maxPhyDimension(GeomElement... elements) {
252        int numDims = 0;
253        int size = elements.length;
254        for (int i = size - 1; i >= 0; --i)
255            numDims = Math.max(numDims, elements[i].getPhyDimension());
256        return numDims;
257    }
258
259    /**
260     * Return the maximum physical dimension among a collection of geometry elements.
261     *
262     * @param elements The collection of geometry elements to query. May not be null.
263     * @return The maximum physical dimension among the input list of geometry elements.
264     */
265    public static int maxPhyDimension(Collection<GeomElement> elements) {
266        int numDims = 0;
267        for (GeomElement item : elements)
268            numDims = Math.max(numDims, item.getPhyDimension());
269        return numDims;
270    }
271
272    /**
273     * Return a unit vector, of <code>nDims</code> dimensions, in the X (dimension = 0)
274     * direction.
275     *
276     * @param numDims The number of dimensions for the unit vector.
277     * @return A unit vector, with the given dimensions, in the "X" direction.
278     */
279    public static Vector<Dimensionless> makeXVector(int numDims) {
280        FastTable<Parameter<Dimensionless>> tmpList = FastTable.newInstance();
281
282        tmpList.add(Parameter.ONE);
283        for (int i = 1; i < numDims; ++i)
284            tmpList.add(Parameter.ZERO);
285        Vector<Dimensionless> V = Vector.valueOf(tmpList);
286
287        FastTable.recycle(tmpList);
288        return V;
289    }
290
291    /**
292     * Return a unit vector, of <code>nDims</code> dimensions, in the Y (dimension = 1)
293     * direction. If the number of dimensions is less than two, this will return null.
294     *
295     * @param numDims The number of dimensions for the unit vector.
296     * @return A unit vector, with the given dimensions, in the "Y" direction.
297     */
298    public static Vector<Dimensionless> makeYVector(int numDims) {
299        if (numDims < 2)
300            return null;
301
302        FastTable<Parameter<Dimensionless>> tmpList = FastTable.newInstance();
303
304        for (int i = 0; i < numDims; ++i)
305            tmpList.add(Parameter.ZERO);
306        tmpList.set(1, Parameter.ONE);
307        Vector<Dimensionless> V = Vector.valueOf(tmpList);
308
309        FastTable.recycle(tmpList);
310        return V;
311    }
312
313    /**
314     * Return a unit vector, of <code>nDims</code> dimensions, in the Z (dimension = 2)
315     * direction. If the number of dimensions is less than three, then this will return
316     * null.
317     *
318     * @param numDims The number of dimensions for the unit vector.
319     * @return A unit vector, with the given dimensions, in the "Z" direction.
320     */
321    public static Vector<Dimensionless> makeZVector(int numDims) {
322        if (numDims < 3)
323            return null;
324
325        FastTable<Parameter<Dimensionless>> tmpList = FastTable.newInstance();
326
327        for (int i = 0; i < numDims; ++i)
328            tmpList.add(Parameter.ZERO);
329        tmpList.set(2, Parameter.ONE);
330        Vector<Dimensionless> V = Vector.valueOf(tmpList);
331
332        FastTable.recycle(tmpList);
333        return V;
334    }
335
336    /**
337     * Return the yhat vector (a Y axis orthogonal to the specified plane normal). The
338     * returned vector will have the origin set to zero.
339     *
340     * @param n A unit plane normal vector for the plane containing the new axis. Pass
341     *          <code>null</code> for 2D geometry.
342     * @return A unit vector orthogonal to the specified plane normal vector.
343     */
344    public static Vector<Dimensionless> calcYHat(GeomVector<Dimensionless> n) {
345        StackContext.enter();
346        try {
347            Vector<Dimensionless> yhat;
348
349            if (isNull(n)) {
350                //  A 2D curve must be in the 2D plane.
351                yhat = Vector.valueOf(0, 1);
352
353            } else {
354                int numDims = n.getPhyDimension();
355
356                //  Create a yhat axis that is orthogonal to the normal vector.
357                if (MathTools.isApproxEqual(n.getValue(0), 1))              //  n.getValue(0) == 1.0
358                    //  Normal vector is along the X axis (parallel to X).
359                    yhat = GeomUtil.makeYVector(numDims);
360
361                else if (MathTools.isApproxEqual(n.getValue(0), -1))        //  n.getValue(0) == -1.0
362                    yhat = GeomUtil.makeYVector(numDims).opposite();
363
364                else {
365                    Vector<Dimensionless> xV = GeomUtil.makeXVector(numDims);   //  Create a vector along the X axis.
366                    if (n.getValue(2) < 0)
367                        yhat = xV.cross(n).toUnitVector();
368                    else {
369                        yhat = n.cross(xV).toUnitVector();
370                        yhat.setOrigin(Point.newInstance(numDims));
371                    }
372                }
373            }
374
375            return StackContext.outerCopy(yhat);
376
377        } finally {
378            StackContext.exit();
379        }
380    }
381
382    /**
383     * Return the xhat vector (an X axis orthogonal to the specified normal and Y-axis
384     * vectors).
385     *
386     * @param n    A unit plane normal vector for the plane containing the new axis. Pass
387     *             null for 2D geometry.
388     * @param yhat The unit direction of the Y axis in the plane containing the new axis.
389     * @return A unit vector orthogonal to the specified normal and Y-axis vectors.
390     */
391    public static Vector<Dimensionless> calcXHat(GeomVector<Dimensionless> n,
392            GeomVector<Dimensionless> yhat) {
393        Vector<Dimensionless> xhat;
394
395        if (isNull(n))
396            //  A 2D axis must be in the 2D plane.
397            xhat = Vector.valueOf(1, 0);
398        else
399            //  Create an xhat axis that is orthogonal to yhat and the normal vector.
400            xhat = (Vector<Dimensionless>)yhat.cross(n);
401
402        return xhat;
403    }
404
405    /**
406     * Return a vector that is the in-plane normal to the input 2D vector.
407     * 
408     * @param u The 2D vector that the normal vector is to be determined for.
409     * @return The 2D vector normal of the input 2D vector.
410     */
411    public static Vector normal2D(GeomVector u) {
412        if (u.getPhyDimension() != 2)
413            throw new IllegalArgumentException("Input vector must be 2D.");
414        
415        Vector output = Vector.valueOf(u.get(Point.Y).opposite(), u.get(Point.X));
416        
417        return output;
418    }
419    
420    /**
421     * Return the vector or cross product area value for two vectors. The magnitude of the
422     * vector or cross product results in a scalar that represents the the area of a
423     * parallelogram with the vectors for sides. This is also the magnitude of the
424     * determinant of the two vectors. For 2D vectors, a signed area will be returned with
425     * positive area indicating a normal vector that is pointing "out of the page" and a
426     * negative area indicating a normal vector pointing "into the page".
427     *
428     * @param v1 The 1st vector to be multiplied. May not be null.
429     * @param v2 The 2nd vector multiplier. May not be null.
430     * @return The vector or cross product between the input vectors (v1 X v2) as a scalar
431     *         area.
432     */
433    public static Parameter crossArea(GeomVector v1, GeomVector v2) {
434        //  Convert to highest dimension between input vectors.
435        int numDims = Math.max(v1.getPhyDimension(), v2.getPhyDimension());
436        v1 = v1.toDimension(numDims);
437        v2 = v2.toDimension(numDims);
438
439        if (numDims < 3) {
440            //  Do a 2D cross product: area = (v1.X*v2.Y) - (v1.Y*v2.X);
441            Parameter area = v1.get(Vector.X).times(v2.get(Vector.Y)).minus(v1.get(Vector.Y).times(v2.get(Vector.X)));
442            return area;
443        }
444
445        //  Do a regular cross product and extract the area.
446        GeomVector n = v1.cross(v2);
447        return n.norm();
448    }
449
450    /**
451     * Finds the shortest distance from a point to an infinite line.
452     *
453     * @param p The point being compared to the line to find the shortest distance. May
454     *          not be null.
455     * @param a A point on the line. May not be null.
456     * @param u The unit direction vector for the line. May not be null.
457     * @return The distance from the point to the line.
458     */
459    public static Parameter<Length> pointLineDistance(GeomPoint p, GeomPoint a, GeomVector<Dimensionless> u) {
460        //  Reference:
461        //  http://softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm#Distance%20to%202-Point%20Line
462
463        int numDims = maxPhyDimension(p, a, u);
464        if (numDims < 3)
465            numDims = 3;
466
467        StackContext.enter();
468        try {
469            //  Convert all the points to the highest dimension between them.
470            p = p.toDimension(numDims);
471            a = a.toDimension(numDims);
472            u = u.toDimension(numDims);
473
474            //  Form a vector from the point on the line to the target point.
475            Vector<Length> w = p.minus(a).toGeomVector();
476
477            //  Cross-product of unit vector with point-line vector gives vector with magnitude that is distance.
478            GeomVector<Length> uxw = (GeomVector<Length>)u.cross(w);
479            Parameter<Length> mag = uxw.mag();
480
481            return StackContext.outerCopy(mag);
482
483        } finally {
484            StackContext.exit();
485        }
486    }
487
488    /**
489     * Finds the shortest distance from a point to an finite line segment.
490     *
491     * @param p  The point being compared to the segment to find the shortest distance.
492     *           May not be null.
493     * @param p0 The start of the line segment. May not be null.
494     * @param p1 The end of the line segment. May not be null.
495     * @return The shortest distance from the point to the line segment.
496     */
497    public static Parameter<Length> pointLineSegDistance(GeomPoint p, GeomPoint p0, GeomPoint p1) {
498        //  Reference:
499        //  http://www.softsurfer.com/Archive/algorithm_0102/algorithm_0102.htm#Distance%20to%20Ray%20or%20Segment
500
501        int numDims = maxPhyDimension(p, p0, p1);
502
503        StackContext.enter();
504        try {
505            //  Convert all the points to the highest dimension between them.
506            p = p.toDimension(numDims);
507            p0 = p0.toDimension(numDims);
508            p1 = p1.toDimension(numDims);
509
510            //  Form a vector along the line.
511            Vector<Length> v = p1.minus(p0).toGeomVector();
512
513            //  Form a vector from the start of the line to the target point.
514            Vector<Length> w = p.minus(p0).toGeomVector();
515
516            //  Projection of w onto v.
517            Parameter c1 = w.dot(v);
518
519            if (c1.getValue() <= 0)
520                //  Closest to start.
521                return StackContext.outerCopy(p0.distance(p));
522
523            //  Magnitude of v squared.
524            Parameter c2 = v.dot(v);
525            if (c2.compareTo(c1) <= 0)
526                //  Closest to the end.
527                return StackContext.outerCopy(p1.distance(p));
528
529            Parameter<Dimensionless> b = c1.divide(c2);
530            Point pb = p0.plus(Point.valueOf(v.times(b)));
531
532            return StackContext.outerCopy(p.distance(pb));
533
534        } finally {
535            StackContext.exit();
536        }
537    }
538
539    /**
540     * Finds the point on an infinite line that is closest to the specified point.
541     *
542     * @param p The point being compared to the line to find the closest point on the
543     *          line. May not be null.
544     * @param a A point on the line. May not be null.
545     * @param u The unit direction vector for the line. May not be null.
546     * @return The point on the line closest to the point "p".
547     */
548    public static Point pointLineClosest(GeomPoint p, GeomPoint a, GeomVector<Dimensionless> u) {
549
550        int numDims = maxPhyDimension(p, a, u);
551        if (numDims < 2)
552            numDims = 2;
553
554        StackContext.enter();
555        try {
556            //  Convert all the points to the highest dimension between them.
557            p = p.toDimension(numDims);
558            a = a.toDimension(numDims);
559            u = u.toDimension(numDims);
560
561            //  Form a vector from the point on the line to the target point.
562            Vector<Length> w = p.minus(a).toGeomVector();
563
564            //  Calculate vector from the end of the line to the closest point.
565            //  Project w onto u.
566            GeomVector<Length> cpV = (GeomVector<Length>)u.times(u.dot(w));
567
568            //  Calculate the end point of the cpV vector.
569            Point pOut = Point.valueOf(cpV).plus(a);
570
571            return StackContext.outerCopy(pOut);
572
573        } finally {
574            StackContext.exit();
575        }
576    }
577
578    /**
579     * Finds the shortest signed distance from a point to an infinite plane.
580     *
581     * @param p The point being compared to the plane to find the closest point on the
582     *          plane. May not be null.
583     * @param a Any point in the plane. May not be null.
584     * @param n The unit normal vector for the plane. May not be null.
585     * @return The signed shortest distance from the input point "p" to the plane. The
586     *         distance is positive for points on the side of the plane pointed to by the
587     *         normal vector "n", and negative for points on the other side of the plane.
588     * @throws DimensionException if input normal vector physical dimension is not at
589     * least 3 dimensional.
590     */
591    public static Parameter<Length> pointPlaneDistance(GeomPoint p, GeomPoint a, GeomVector<Dimensionless> n) throws DimensionException {
592        requireNonNull(p);
593        requireNonNull(a);
594        
595        //  Convert all the points to the highest dimension between them.
596        int numDims = n.getPhyDimension();
597        if (numDims < 3)
598            throw new DimensionException(
599                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast3"),
600                            "input normal vector", numDims));
601
602        numDims = Math.max(numDims, Math.max(a.getPhyDimension(), p.getPhyDimension()));
603
604        StackContext.enter();
605        try {
606            p = p.toDimension(numDims);
607            a = a.toDimension(numDims);
608            n = n.toDimension(numDims);
609
610            //  Vector from point in plane to input point.
611            Vector<Length> v = p.minus(a).toGeomVector();
612
613            //  The signed distance is projection of v onto the normal vector.
614            Parameter<Length> dist = (Parameter)v.dot(n);
615
616            return StackContext.outerCopy(dist);
617
618        } finally {
619            StackContext.exit();
620        }
621    }
622
623    /**
624     * Finds the point on an infinite plane that is closest to the specified point. The
625     * distance to the plane is simply:
626     * <code>p.minus(pointPlaneClosest(p,...)).norm();</code>.
627     *
628     * @param p The point being compared to the plane to find the closest point on the
629     *          plane. May not be null.
630     * @param a Any point in the plane. May not be null.
631     * @param n The unit normal vector for the plane. May not be null.
632     * @return The point on the plane closest to the point "p". May return the input point
633     *         "p" if it is in the plane.
634     * @throws DimensionException if input normal vector physical dimension is not at
635     * least 3 dimensional.
636     */
637    public static Point pointPlaneClosest(GeomPoint p, GeomPoint a, GeomVector<Dimensionless> n) throws DimensionException {
638        requireNonNull(p);
639        requireNonNull(a);
640        
641        //  Convert all the points to the highest dimension between them.
642        int numDims = n.getPhyDimension();
643        if (numDims < 3)
644            throw new DimensionException(
645                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast3"),
646                            "input normal vector", numDims));
647
648        numDims = Math.max(numDims, Math.max(a.getPhyDimension(), p.getPhyDimension()));
649
650        StackContext.enter();
651        try {
652            p = p.toDimension(numDims);
653            a = a.toDimension(numDims);
654            n = n.toDimension(numDims);
655
656            //  Find the signed distance to the plane from the point.
657            Parameter dist = pointPlaneDistance(p, a, n);
658
659            //  If distance is zero, then the input point is already in the plane.
660            if (abs(dist.getValue()) <= MathTools.SQRT_EPS)
661                return StackContext.outerCopy(p.immutable());
662
663            //  dv is parallel to normal vector and "dist" long.
664            GeomVector dv = n.times(dist);
665
666            //  Subtract dv from the input point to get point projected onto the plane.
667            Point projPoint = p.minus(Point.valueOf(dv));
668
669            return StackContext.outerCopy(projPoint);
670
671        } finally {
672            StackContext.exit();
673        }
674    }
675
676    /**
677     * Find a circle that passes through the supplied (not co-linear) points.
678     *
679     * @param p1 The 1st of the points that define the circle (must not be colinear with
680     *           the other two). May not be null.
681     * @param p2 The 2nd of the points that define the circle (must not be colinear with
682     *           the other two). May not be null.
683     * @param p3 The 3rd of the points that define the circle (must not be colinear with
684     *           the other two). May not be null.
685     * @return A CircleInfo record containing the geometry of the circle. The "xhat"
686     *         parameter points to the first point supplied. The "yhat" parameter
687     *         indicates the direction to the next point. The "angle" parameter gives the
688     *         angle swept out from the 1st point to the 3rd point.
689     * @throws DimensionException if one of the 3 points does not have at least 2 physical
690     * dimensions.
691     * @throws IllegalArgumentException if the input points are co-linear.
692     */
693    public static CircleInfo threePointCircle(GeomPoint p1, GeomPoint p2, GeomPoint p3) throws DimensionException {
694        //  Reference: http://en.wikipedia.org/wiki/Circumscribed_circle#Barycentric_coordinates_as_a_function_of_the_side_lengths
695        //      Also: http://stackoverflow.com/a/13992781
696
697        //  Convert all the points to the highest dimension between them.
698        int numDims = maxPhyDimension(p1, p2, p3);
699        if (numDims < 2)
700            throw new DimensionException(
701                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "input points", numDims));
702
703        //  Up-cast the geometry to 3D if necessary to make some of the math easier.
704        int saveDims = numDims;
705        if (numDims < 3)
706            numDims = 3;
707
708        Parameter<Length> radius;
709        Point Pc;
710        Vector<Dimensionless> xhat, yhat;
711        Parameter<Angle> angle;
712        StackContext.enter();
713        try {
714            p1 = p1.toDimension(numDims);
715            p2 = p2.toDimension(numDims);
716            p3 = p3.toDimension(numDims);
717            Unit<Length> unit = p1.getUnit();
718
719            //  Calculate edges of the triangle being circumscribed.
720            Vector<Length> t = p2.minus(p1).toGeomVector();
721            Vector<Length> u = p3.minus(p1).toGeomVector();
722            Vector<Length> v = p3.minus(p2).toGeomVector();
723
724            //  Are the points colinear?
725            double tDOTv = t.dot(v).getValue() / (t.normValue() * v.normValue());
726            if (abs(tDOTv) > 1.0 - 1.e-6)
727                throw new IllegalArgumentException(RESOURCES.getString("pointsColinearErr"));
728
729            //  Compute the triangle normal.
730            Vector w = t.cross(u);
731            Parameter wmag = w.mag();
732            Vector<Dimensionless> nhat = w.toUnitVector();
733
734            //  Compute some dot products we will need.
735            Parameter tt = t.dot(t);
736            Parameter uu = u.dot(u);
737
738            //  Compute the circle radius:  r = sqrt(tt*uu*(v*v))/(2*wmag)
739            radius = tt.times(uu).times(v.dot(v)).sqrt().divide(2).divide(wmag).to(unit);
740
741            //  Compute the circle center:  Pc = p1 + (u*tt*(u*v) - t*uu*(t*v))/(2*wmag^2)
742            Parameter wmag2 = wmag.pow(2);
743            Parameter uv = u.dot(v);
744            Parameter tv = t.dot(v);
745            Vector<Length> Pcv = u.times(tt).times(uv).minus((Vector)t.times(uu).times(tv)).divide(2).divide(wmag2).to(unit);
746            Pc = p1.plus(Point.valueOf(Pcv));
747
748            //  The xhat vector is the vector from the center to the 1st point.
749            xhat = p1.minus(Pc).toGeomVector().toUnitVector();
750            xhat.setOrigin(Point.newInstance(numDims));
751
752            //  The yhat vector is perpendicular to the xhat and normal vectors.
753            yhat = nhat.cross(xhat).toUnitVector();
754            yhat.setOrigin(Point.newInstance(numDims));
755
756            //  Calculate the angle between the 1st and last points.
757            Vector<Dimensionless> p2V = p2.minus(Pc).toGeomVector().toUnitVector();
758            Parameter dot = xhat.dot(p2V);
759            angle = Parameter.acos(dot);
760            Vector<Dimensionless> p3V = p3.minus(Pc).toGeomVector().toUnitVector();
761            dot = p2V.dot(p3V);
762            angle = angle.plus(Parameter.acos(dot));
763
764            if (saveDims == 2) {
765                //  Convert back to 2D geometry on output.
766                Pc = Pc.toDimension(2);
767                xhat = xhat.toDimension(2);
768                yhat = yhat.toDimension(2);
769            }
770
771            //  Copy the things we need out of the stack context.
772            radius = StackContext.outerCopy(radius);
773            Pc = StackContext.outerCopy(Pc);
774            xhat = StackContext.outerCopy(xhat);
775            yhat = StackContext.outerCopy(yhat);
776            angle = StackContext.outerCopy(angle);
777
778        } finally {
779            StackContext.exit();
780        }
781
782        //  Create and return a circle record.
783        CircleInfo circle = new CircleInfo();
784        circle.radius = radius;
785        circle.center = Pc;
786        circle.xhat = xhat;
787        circle.yhat = yhat;
788        circle.angle = angle;
789
790        return circle;
791    }
792
793    /**
794     * Finds a circle that is approximately centered on the specified origin point and
795     * passes through the two input points. The origin point is used to determine the
796     * plane that the circle lies in and is used to approximate the center of the circle.
797     * The true origin/center of the circle is calculated to ensure that the circle passes
798     * through the supplied edge points.
799     *
800     * @param o  Approximate origin or center to create the full-circle around. May not be
801     *           null.
802     * @param p1 The 1st of the points that define the circle (must not be colinear with
803     *           the other two). May not be null.
804     * @param p2 The 2nd of the points that define the circle (must not be colinear with
805     *           the other two). May not be null.
806     * @return A CircleInfo record containing the geometry of the circle. THe "xhat"
807     *         parameter points to the first point supplied. The "yhat" parameter
808     *         indicates the direction to the 2nd point. The "angle" parameter gives the
809     *         angle swept out from the 1st point to the 2nd point.
810     * @throws DimensionException if the origin or 2 points do not have at least 2
811     * physical dimensions.
812     */
813    public static CircleInfo centerTwoPointCircle(GeomPoint o, GeomPoint p1, GeomPoint p2) throws DimensionException {
814
815        //  Convert all the points to the highest dimension between them.
816        int numDims = maxPhyDimension(o, p1, p2);
817        if (numDims < 2)
818            throw new DimensionException(
819                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "input points", numDims));
820
821        //  Up-cast the geometry to 3D if necessary to make some of the math easier.
822        int saveDims = numDims;
823        if (numDims < 3)
824            numDims = 3;
825
826        o = o.toDimension(numDims);
827        p1 = p1.toDimension(numDims);
828        p2 = p2.toDimension(numDims);
829
830        //  Find the true center of the circle.
831        Vector<Length> a = p2.minus(p1).toGeomVector();
832
833        //  Find the bisection point of the line segment "a".
834        Point aB = p1.plus(p2).divide(2);    //  aB = (p1 + p2)/2
835
836        //  Find normal vector and direction perpendicular to vector "a".
837        //  Get vectors from the guess at the circle center to the end points.
838        GeomVector<Length> p1v = p1.minus(o).toGeomVector();
839        GeomVector<Length> p2v = p2.minus(o).toGeomVector();
840
841        //  Determine the normal vector for the plane containing the circle.
842        Vector<Dimensionless> n = p1v.cross(p2v).toUnitVector();
843
844        //  Determine the perpendicular vector.
845        Vector<Dimensionless> taB = a.cross(n).toUnitVector();
846
847        //  The center is the nearest point to the user supplied center on the line that
848        //  bisects the line between p1 and p2.
849        Point c = pointLineClosest(o, aB, taB);
850
851        //  Construct a circle information record from the center, two end points, and normal vector.
852        CircleInfo circInfo = makeCircleInfo(c, p1, p2, n);
853        if (saveDims == 2) {
854            //  Convert back to 2D geometry on output.
855            circInfo.center = circInfo.center.toDimension(2);
856            circInfo.xhat = circInfo.xhat.toDimension(2);
857            circInfo.yhat = circInfo.yhat.toDimension(2);
858        }
859
860        return circInfo;
861    }
862
863    /**
864     * Finds a circle that passes through the two specified points with the specified
865     * tangent vector at the start.
866     *
867     * @param p1 The 1st of the points that define the circle. May not be null.
868     * @param t1 The tangent vector at p1 (must not point directly at p2). May not be null.
869     * @param p2 The 2nd of the points that define the circle. May not be null.
870     * @return A CircleInfo record containing the geometry of the circle. THe "xhat"
871     *         parameter points to the first point supplied. The "yhat" parameter
872     *         indicates the direction to the 2nd point. The "angle" parameter gives the
873     *         angle swept out from the 1st point to the 2nd point.
874     * @throws DimensionException if the 2 points do not have at least 2 physical
875     * dimensions.
876     */
877    public static CircleInfo twoPointTangentCircle(GeomPoint p1, GeomVector t1, GeomPoint p2) throws DimensionException {
878
879        //  Convert all the points to the highest dimension between them.
880        int numDims = maxPhyDimension(p1, t1, p2);
881        if (numDims < 2)
882            throw new DimensionException(
883                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), "input points", numDims));
884
885        //  Up-cast the geometry to 3D if necessary to make some of the math easier.
886        int saveDims = numDims;
887        if (numDims < 3)
888            numDims = 3;
889        p1 = p1.toDimension(numDims);
890        p2 = p2.toDimension(numDims);
891        t1 = t1.toDimension(numDims);
892
893        //  "a" is the vector between the two end points.
894        Vector<Length> a = p2.minus(p1).toGeomVector();
895
896        //  Make sure tangent vector is valid.  If parallel to p2-p1 then dot product will equal 1.
897        if (t1.dot(a).divide(t1.mag()).divide(a.mag()).minus(Parameter.ONE).abs().isApproxZero())
898            throw new IllegalArgumentException(RESOURCES.getString("t1ParallelTop2p1Err"));
899
900        //  Find the bisection point of the line segment "a".
901        Point aB = p1.plus(p2).divide(2);   //  aB = (p1 + p2)/2
902
903        //  Find normal vector and direction perpendicular to vector "a".
904        //  Determine the normal vector for the plane containing the circle.
905        Vector<Dimensionless> n = t1.cross(a).toUnitVector();
906
907        //  Determine the perpendicular vector.
908        Vector<Dimensionless> taB = a.cross(n).toUnitVector();
909
910        //  Form vector perpendicular to t1 and the normal vector
911        //  (points toward the center of the circle).
912        Vector<Dimensionless> t1hat = n.cross(t1).toUnitVector();
913
914        //  The center/origin is where the p1/t1hat and perpendicular bisector
915        //  lines intersect (or come closest to intersecting).
916        MutablePoint c = MutablePoint.newInstance(numDims, p1.getUnit());
917        lineLineIntersect(p1, t1hat, aB, taB, GTOL, null, null, c);
918
919        //  Construct a circle information record from the center, two end points, and normal vector.
920        CircleInfo circInfo = makeCircleInfo(c, p1, p2, n);
921        if (saveDims == 2) {
922            //  Convert back to 2D geometry on output.
923            circInfo.center = circInfo.center.toDimension(2);
924            circInfo.xhat = circInfo.xhat.toDimension(2);
925            circInfo.yhat = circInfo.yhat.toDimension(2);
926        }
927
928        return circInfo;
929    }
930
931    /**
932     * Return a CircleInfo record from the circle center, end points and normal vector.
933     * All input geometry must be at least 3D. No error checking is performed. All points
934     * & vectors must be the same dimension and compatible with a circle.
935     *
936     * @param c  The center of the circle.
937     * @param p1 The 1st end point of the circle or circular arc.
938     * @param p2 The 2nd end point of the circle or circular arc.
939     * @param n  The normal vector for the circle.
940     * @return A CircleInfo record containing the geometry of the circle. The "xhat"
941     *         parameter points to the first point supplied. The "yhat" parameter
942     *         indicates the direction to the next point. The "angle" parameter gives the
943     *         angle swept out from the 1st point to the 2nd point.
944     */
945    private static CircleInfo makeCircleInfo(GeomPoint c, GeomPoint p1, GeomPoint p2, GeomVector n) {
946        int numDims = c.getPhyDimension();
947
948        //  The radius is the distance from the center point to any one of the edge points.
949        Parameter<Length> r = c.distance(p1);
950
951        //  The xhat vector is the vector from the center to the 1st point.
952        Vector<Dimensionless> xhat = p1.minus(c).toGeomVector().toUnitVector();
953        xhat.setOrigin(Point.newInstance(numDims));
954
955        //  The yhat vector is perpendicular to the xhat and normal vectors.
956        Vector<Dimensionless> yhat = n.cross(xhat).toUnitVector();
957        yhat.setOrigin(Point.newInstance(numDims));
958
959        //  Calculate the angle between the 1st and last points.
960        Vector<Dimensionless> p2V = p2.minus(c).toGeomVector().toUnitVector();
961        Parameter dot = xhat.dot(p2V);
962        Parameter<Angle> angle = Parameter.acos(dot);
963
964        //  Create and return a circle record.
965        CircleInfo circle = new CircleInfo();
966        circle.radius = r.to(p1.getUnit());
967        circle.center = c.to(p1.getUnit());
968        circle.xhat = xhat;
969        circle.yhat = yhat;
970        circle.angle = angle;
971
972        return circle;
973    }
974
975    /**
976     * Returns the default units for this system. The default unit is based on the user's
977     * locale. If the user's locale is the United States, then a value of NonSI.INCH is
978     * returned, otherwise a value of SI.CENTIMETER is returned.
979     *
980     * @return The default units for this system.
981     */
982    public static Unit<Length> getDefaultUnit() {
983        Unit<Length> unit = SI.CENTIMETER;
984        if (java.util.Locale.getDefault().equals(java.util.Locale.US))
985            unit = NonSI.INCH;
986        return unit;
987    }
988
989    /**
990     * Finds the intersection point between two lines, or the point of closest approach if
991     * the lines do not intersect, and returns the point on each line. The output data is
992     * returned by modifying the input <code>sOut</code>, <code>out1</code> and 
993     * <code>out2</code> points.
994     *
995     * @param p1   A point on the 1st line. May not be null.
996     * @param t1   The direction vector for the 1st line (does not have to be a unit
997     *             vector). May not be null.
998     * @param p2   A point on the 2nd line. May not be null.
999     * @param t2   The direction vector for the 2nd line (MUST be similar to t1; unit
1000     *             vector if t1 is unit vector and dimension vector if t1 is a dimensional
1001     *             vector). Do NOT mix dimensional and unit vectors in the same call. May
1002     *             not be null.
1003     * @param tol  Tolerance on how close the lines must be to be considered intersecting.
1004     *             A value of null indicates that an exact intersection is required.
1005     * @param sOut The pre-defined 2D output parametric positions on the input lines that
1006     *             will be filled in to represent the points where the lines intersect. If
1007     *             the input direction vectors are dimensionless (unit vectors), then the
1008     *             output point will be scaled to the units of p1 (indicating the distance
1009     *             that the intersection point is away from the origin in p1 units). If
1010     *             the input direction vectors are dimensional, then the point will be
1011     *             scaled such that 1.0 m represents the direction vector's length along
1012     *             the line. If there is no intersection, the point is not modified. If
1013     *             <code>null</code> is passed, then the parametric point is not
1014     *             calculated.
1015     * @param out1 The pre-defined output point on the 1st line representing the
1016     *             intersection or closest approach of the two lines (may be null if not
1017     *             required).
1018     * @param out2 The pre-defined output point on the 2nd line representing the
1019     *             intersection or closest approach of the two lines (may be null if not
1020     *             required).
1021     * @return DISJOINT if the lines do not intersect or INTERSECT if they do.
1022     * @throws DimensionException if the output points do not have at least as many
1023     * dimensions as the input points.
1024     */
1025    public static IntersectType lineLineIntersect(GeomPoint p1, GeomVector t1,
1026            GeomPoint p2, GeomVector t2, Parameter<Length> tol,
1027            MutablePoint sOut, MutablePoint out1, MutablePoint out2) throws DimensionException {
1028        //  For algorithm see:  http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
1029        
1030        int numDims = maxPhyDimension(p1, t1, p2, t2);
1031        if (numDims < 2)
1032            numDims = 2;
1033
1034        boolean parallel = false;
1035        Parameter mu1, mu2;
1036        Vector<Length> v1, v2;
1037        StackContext.enter();
1038        try {
1039            //  Convert all the points to the highest dimension between them.
1040            p1 = p1.toDimension(numDims);
1041            t1 = t1.toDimension(numDims);
1042            p2 = p2.toDimension(numDims);
1043            t2 = t2.toDimension(numDims);
1044
1045            Vector<Length> v12 = p1.minus(p2).toGeomVector();
1046
1047            Parameter a = t1.dot(t1);
1048            Parameter b = t1.dot(t2);
1049            Parameter c = t2.dot(t2);
1050            Parameter d = t1.dot(v12);
1051            Parameter e = t2.dot(v12);
1052            Parameter denom = a.times(c).minus(b.times(b));     //  denom = a*c - b^2
1053
1054            if (denom.isApproxZero()) {
1055                //  The lines are parallel.
1056                parallel = true;
1057                mu1 = Parameter.valueOf(0, d.getUnit().divide(b.getUnit()));    //  mu1 = 0;
1058                mu2 = b.isGreaterThan(c) ? d.divide(b) : e.divide(c);           //  b > c ? d / b : e / c;
1059
1060            } else {
1061                mu1 = b.times(e).minus(c.times(d)).divide(denom);               //  mu1 = (b * e - c * d) / denom;
1062                mu2 = a.times(e).minus(b.times(d)).divide(denom);               //  mu2 = (a * e - b * d) / denom;
1063            }
1064
1065            //  Compute the closest points on each line.
1066            v1 = t1.times(mu1);
1067            v1 = v1.plus(p1.toGeomVector());
1068            v2 = t2.times(mu2);
1069            v2 = v2.plus(p2.toGeomVector());
1070
1071            //  Copy the things we need to the outer context.
1072            if (nonNull(sOut)) {
1073                mu1 = StackContext.outerCopy(mu1);
1074                mu2 = StackContext.outerCopy(mu2);
1075            }
1076            v1 = StackContext.outerCopy(v1);
1077            v2 = StackContext.outerCopy(v2);
1078
1079        } finally {
1080            StackContext.exit();
1081        }
1082
1083        if (nonNull(sOut)) {
1084            if (mu1.getUnit().isCompatible(Dimensionless.UNIT)) {
1085                UnitConverter cvtr = SI.METER.getConverterTo(sOut.getUnit());
1086                sOut.setValue(0, cvtr.convert(mu1.getValue(Dimensionless.UNIT)));
1087                sOut.setValue(1, cvtr.convert(mu2.getValue(Dimensionless.UNIT)));
1088            } else {
1089                sOut.set(0, mu1);
1090                sOut.set(1, mu2);
1091            }
1092        }
1093
1094        if (nonNull(out1)) {
1095            out1.set(v1);
1096        }
1097
1098        if (nonNull(out2)) {
1099            out2.set(v2);
1100        }
1101
1102        if (v2.minus(v1).norm().isLessThan(tol)) {
1103            if (parallel)
1104                return IntersectType.COINCIDENT;
1105            return IntersectType.INTERSECT;
1106        }
1107        return IntersectType.DISJOINT;
1108    }
1109
1110    /**
1111     * Finds the intersection point between a line and a plane (if there is an
1112     * intersection). If the line and the plane are parallel, then IntersectType.DISJOINT
1113     * is output, if there is an intersection, then 1 is output and the intersection point
1114     * is returned in the input <code>out</code> point. Finally, if the line is coincident
1115     * with the plane, then 2 is output and out is set to the L0 point.
1116     *
1117     * @param L0    A point on the line (origin of the line). May not be null.
1118     * @param Ldir  The direction vector for the line (does not have to be a unit vector).
1119     *              May not be null.
1120     * @param plane The plane being tested for intersection. May not be null.
1121     * @param out   The pre-defined output point on the line that will be filled in to
1122     *              represent the intersection of the line and the plane (not modified if
1123     *              there is no intersection). If <code>null</code> is passed, then the
1124     *              point is not calculated, but the return codes of this method are still
1125     *              valid.
1126     * @return DISJOINT if the line and plane are disjoint (parallel), INTERSECT if there
1127     *         is a unique intersection and COINCIDENT if the line is coincident with the
1128     *         plane.
1129     * @throws DimensionException if the output point does not have at least as many
1130     * dimensions as the input line and plane.
1131     */
1132    public static IntersectType linePlaneIntersect(GeomPoint L0, GeomVector Ldir,
1133            GeomPlane plane, MutablePoint out) throws DimensionException {
1134        //  For algorithm see:  http://www.softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm
1135        
1136        //  Convert all the input data to the highest dimension between them.
1137        int numDims = maxPhyDimension(L0, Ldir, plane);
1138        L0 = L0.toDimension(numDims);
1139        Vector<Dimensionless> Lu = Ldir.toDimension(numDims).toUnitVector();
1140        plane = plane.toDimension(numDims);
1141
1142        //  Get data we need.
1143        GeomPoint V0 = plane.getRefPoint();
1144        Vector nw = V0.minus(L0).toGeomVector();
1145        GeomVector<Dimensionless> n = plane.getNormal();
1146
1147        //  Check for the line parallel to the plane.
1148        Parameter ndotu = n.dot(Lu);
1149        if (ndotu.isApproxZero()) {
1150            Parameter N = n.dot(nw);
1151            if (N.isApproxZero()) {
1152                if (out != null)
1153                    out.set(L0);
1154                return IntersectType.COINCIDENT;
1155            }
1156            return IntersectType.DISJOINT;
1157        }
1158
1159        if (nonNull(out)) {
1160            //  Find the intersection point.
1161            Parameter N = n.dot(nw);
1162            Parameter si = N.divide(ndotu);
1163            Vector v = Lu.times(si);
1164            v = v.plus(L0.toGeomVector());
1165
1166            out.set(v);
1167        }
1168
1169        return IntersectType.INTERSECT;                     //  A unique intersection.
1170    }
1171
1172    /**
1173     * Finds the intersection point between a line and a plane (if there is an
1174     * intersection). If the line and the plane are parallel, then IntersectType.DISJOINT
1175     * is output, if there is an intersection, then 1 is output and the intersection point
1176     * is returned in the input <code>out</code> point. Finally, if the line is coincident
1177     * with the plane, then 2 is output and out is set to the L0 point.
1178     *
1179     * @param L0    A point on the line (origin of the line). May not be null.
1180     * @param Ldir  The direction vector for the line (does not have to be a unit vector).
1181     *              May not be null.
1182     * @param plane The plane being tested for intersection. May not be null.
1183     * @param out   The pre-defined output point on the line that will be filled in to
1184     *              represent the intersection of the line and the plane (not modified if
1185     *              there is no intersection). If <code>null</code> is passed, then the
1186     *              point is not calculated, but the return codes of this method are still
1187     *              valid.
1188     * @param tol   The fractional tolerance on determining if the line and plane are
1189     *              coincident or disjoint.
1190     * @return DISJOINT if the line and plane are disjoint (parallel), INTERSECT if there
1191     *         is a unique intersection and COINCIDENT if the line is coincident with the
1192     *         plane.
1193     * @throws DimensionException if the output point does not have at least as many
1194     * dimensions as the input line and plane.
1195     */
1196    public static IntersectType linePlaneIntersect(GeomPoint L0, GeomVector Ldir,
1197            GeomPlane plane, MutablePoint out, double tol) throws DimensionException {
1198        //  For algorithm see:  http://www.softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm
1199        
1200        //  Convert all the input data to the highest dimension between them.
1201        int numDims = maxPhyDimension(L0, Ldir, plane);
1202        L0 = L0.toDimension(numDims);
1203        Vector<Dimensionless> Lu = Ldir.toDimension(numDims).toUnitVector();
1204        plane = plane.toDimension(numDims);
1205
1206        //  Get data we need.
1207        GeomPoint V0 = plane.getRefPoint();
1208        Vector nw = V0.minus(L0).toGeomVector();
1209        GeomVector<Dimensionless> n = plane.getNormal();
1210
1211        //  Check for the line parallel to the plane.
1212        Parameter ptol = Parameter.valueOf(tol, Dimensionless.UNIT);
1213        Parameter ndotu = n.dot(Lu);
1214        if (!ndotu.isLargerThan(ptol)) {
1215            Parameter N = n.dot(nw.toUnitVector());
1216            if (!N.isLargerThan(ptol)) {
1217                if (out != null)
1218                    out.set(L0);
1219                return IntersectType.COINCIDENT;
1220            }
1221            return IntersectType.DISJOINT;
1222        }
1223
1224        if (nonNull(out)) {
1225            //  Find the intersection point.
1226            Parameter N = n.dot(nw);
1227            Parameter si = N.divide(ndotu);
1228            Vector v = Lu.times(si);
1229            v = v.plus(L0.toGeomVector());
1230
1231            out.set(v);
1232        }
1233
1234        return IntersectType.INTERSECT;                     //  A unique intersection.
1235    }
1236
1237    /**
1238     * Finds the intersection line between two planes (if there is an intersection). If
1239     * the two planes are parallel, then IntersectType.DISJOINT is output, if there is an
1240     * intersection, then IntersectType.INTERSECT is output and a point on the
1241     * intersection line is returned in the input <code>out</code> point and the direction
1242     * vector of the intersection line is returned in the input <code>dir</code> vector.
1243     * Finally, if the two planes are coincident, then IntersectType.COINCIDENT is output.
1244     *
1245     * @param plane1 The 1st plane being tested for intersection. May not be null.
1246     * @param plane2 The 2nd plane being tested for intersection. May not be null.
1247     * @param out    The pre-defined output point on the intersection line that will be
1248     *               filled in to represent the intersection of the two planes (not
1249     *               modified if there is no intersection or if the planes are
1250     *               coincident). If <code>null</code> is passed, then the intersection
1251     *               line point is not calculated, but the return codes of this method are
1252     *               still valid.
1253     * @param dir    The pre-defined output unit direction vector of the intersection line
1254     *               that will be filled in to represent the intersection of the two
1255     *               planes (not modified if there is no intersection or if the planes are
1256     *               coincident). If <code>null</code> is passed, then the intersection
1257     *               line direction is not calculated, but the return codes of this method
1258     *               are still valid.
1259     * @return DISJOINT if the line and plane are disjoint (parallel), INTERSECT if there
1260     *         is a unique intersection and COINCIDENT if the line is coincident with the
1261     *         plane.
1262     * @throws DimensionException if the output point and plane do not have at least as
1263     * many dimensions as the input planes.
1264     */
1265    public static IntersectType planePlaneIntersect(GeomPlane plane1,
1266            GeomPlane plane2, MutablePoint out, MutableVector<Dimensionless> dir) throws DimensionException {
1267        //  Reference:  http://mathworld.wolfram.com/Plane-PlaneIntersection.html
1268        
1269        //  Convert all the input data to the highest dimension between them.
1270        int numDims = Math.max(plane1.getPhyDimension(), plane2.getPhyDimension());
1271
1272        StackContext.enter();
1273        try {
1274            //  Convert to the highest dimension input object.
1275            Unit<Length> unit = plane1.getUnit();
1276            plane1 = plane1.toDimension(numDims);
1277            plane2 = plane2.toDimension(numDims).to(unit);
1278
1279            //  Get data we need.
1280            GeomPoint P1 = plane1.getRefPoint();
1281            GeomVector<Dimensionless> n1 = plane1.getNormal();
1282            GeomPoint P2 = plane2.getRefPoint();
1283            GeomVector<Dimensionless> n2 = plane2.getNormal();
1284            Vector nw = P2.minus(P1).toGeomVector();
1285
1286            //  Check for the two planes being parallel.
1287            double n1dotn2 = n1.dot(n2).getValue();
1288            if (MathTools.isApproxEqual(n1dotn2, 1)) {         //  n1dotn2 == 1
1289                //  Are the two planes also coincident?
1290                Parameter N = n1.dot(nw);
1291                if (N.isApproxZero()) {
1292                    return IntersectType.COINCIDENT;
1293                }
1294                return IntersectType.DISJOINT;
1295            }
1296
1297            if (nonNull(dir)) {
1298                //  Find the intersection line direction vector.
1299                Vector<Dimensionless> a = n1.cross(n2).toUnitVector();
1300                //  The following works because MutableVector.set() is context safe when provided
1301                //  a "Vector" instance, but ONLY in that case.
1302                dir.set(StackContext.outerCopy(a));
1303            }
1304
1305            if (nonNull(out)) {
1306                //  Find the intersection point.
1307
1308                //  Create elements of the "b" matrix.
1309                double np1 = plane1.getConstant().getValue();   //  -p1
1310                double np2 = plane2.getConstant().getValue();   //  -p2
1311                Float64Vector b = Float64Vector.valueOf(np1, np2);
1312
1313                //  Create the "M" matrix.
1314                Float64Matrix M = Float64Matrix.valueOf(n1.toFloat64Vector(), n2.toFloat64Vector());
1315                M = M.transpose();
1316
1317                //  Solve for the "x0" vector:  x0 = M^-1 * b.
1318                Float64Vector x0 = Float64Vector.valueOf(M.pseudoInverse().transpose().times(b));
1319
1320                //  Store the newly calculated point in x0.
1321                Point x0p = Point.valueOf(x0, unit);
1322                //  The following works because MutablePoint.set() is context safe when provided
1323                //  a "Point" instance, but ONLY in that case.
1324                out.set(StackContext.outerCopy(x0p));
1325            }
1326
1327        } finally {
1328            StackContext.exit();
1329        }
1330
1331        //  We have an intersection if we have gotten here.
1332        if (nonNull(out) && nonNull(dir)) {
1333            //  If the line direction is requested, store the intersection point in it.
1334            dir.setOrigin(out.immutable());
1335        }
1336
1337        return IntersectType.INTERSECT;             //  A unique intersection.
1338    }
1339
1340    /**
1341     * Returns true if the bounding boxes of two geometry objects intersect or overlap.
1342     * This is an aligned axis bounding box (AABB) test.
1343     *
1344     * @param b1 The first geometric object to test for overlap. May not be null.
1345     * @param b2 The 2nd geometric object to test for overlap. May not be null.
1346     * @return <code>true</code> if the bounding boxes of the two input geometry objects
1347     *         intersect or overlap (including one box contained completely inside of the
1348     *         other).
1349     */
1350    public static boolean boundsIntersect(GeomElement b1, GeomElement b2) {
1351        //  Reference:
1352        //  http://www.gamasutra.com/view/feature/3383/simple_intersection_tests_for_games.php?page=3
1353
1354        StackContext.enter();
1355        try {
1356            //  Make sure both geometry elements have the same units.
1357            Unit<Length> unit = b1.getUnit();
1358
1359            //  Get physical dimension of each element.
1360            int dim1 = b1.getPhyDimension();
1361            int dim2 = b2.getPhyDimension();
1362
1363            //  Get the bounding boxes of each geometry element.
1364            Point b1min = b1.getBoundsMin().to(unit);
1365            Point b1max = b1.getBoundsMax().to(unit);
1366            Point b2min = b2.getBoundsMin().to(unit);
1367            Point b2max = b2.getBoundsMax().to(unit);
1368
1369            //  Make sure points are all the same dimension
1370            //  by making both have the dimension of the largest.
1371            if (dim1 > dim2) {
1372                b2min = b2min.toDimension(dim1);
1373                b2max = b2max.toDimension(dim1);
1374
1375            } else if (dim1 != dim2) {  //  dim2 > dim1
1376                b1min = b1min.toDimension(dim2);
1377                b1max = b1max.toDimension(dim2);
1378            }
1379
1380            //  Find center of each bounding box.
1381            Point p1 = b1min.plus(b1max).divide(2);
1382            Point p2 = b2min.plus(b2max).divide(2);
1383
1384            //  Find extents of each bounding box.
1385            Point E1 = p1.minus(b1min);
1386            Point E2 = p2.minus(b2min);
1387
1388            //  Find the distance between the centers of each box.
1389            Point T = p2.minus(p1);
1390
1391            //  Do the overlap test.
1392            for (int i = 0; i < dim1; ++i) {            //  Loop over each axis.
1393                double Tv = abs(T.getValue(i));     //  Distance between centers along this axis.
1394                double E1v = abs(E1.getValue(i));   //  Bounding box extents along this axis.
1395                double E2v = abs(E2.getValue(i));
1396                if (Tv > E1v + E2v) {
1397                    //  Found a separating axis where
1398                    //  distance along this axis is > than box sizes along this axis.
1399                    //  So, the boxes must not overlap.
1400                    return false;
1401                }
1402            }
1403
1404            return true;
1405
1406        } finally {
1407            StackContext.exit();
1408        }
1409    }
1410
1411    /**
1412     * Returns true if the specified infinite line and bounding box of the specified
1413     * geometry element intersect. This is an infinite line - aligned axis bounding box
1414     * (AABB) test.
1415     *
1416     * @param L0   A point on the line (origin of the line). May not be null.
1417     * @param Ldir The direction vector for the line (does not have to be a unit vector).
1418     *             May not be null.
1419     * @param box  The geometry element to extract the bounding box from. May not be null.
1420     * @param sOut The pre-defined 2D output parametric position on the line (relative to
1421     *             the origin) that will be filled in to represent the points where the
1422     *             line enters and leaves the bounding box. If there is no intersection,
1423     *             the point is not modified. If <code>null</code> is passed, then the
1424     *             parametric point is not calculated.
1425     * @return <code>true</code> if the line and geometry element bounding box intersects.
1426     */
1427    public static boolean lineBoundsIntersect(GeomPoint L0, GeomVector Ldir, GeomElement box, MutablePoint sOut) {
1428        //  Reference:
1429        //  http://ggt.sourceforge.net/gmtlReferenceGuide-0.6.1-html/namespacegmtl.html#a5e30c7bc7cdaec04e04276eea798264f
1430        double tIn = -Double.MAX_VALUE;
1431        double tOut = Double.MAX_VALUE;
1432
1433        int numDims = maxPhyDimension(L0, Ldir, box);
1434        if (numDims < 2)
1435            numDims = 2;
1436
1437        StackContext.enter();
1438        try {
1439            //  Make sure everything uses the same units.
1440            Unit<Length> unit = box.getUnit();
1441
1442            //  Make sure everything has the dimensions of the largest input object.
1443            L0 = L0.toDimension(numDims);
1444            Ldir = Ldir.toDimension(numDims);
1445            if (!Dimensionless.UNIT.equals(Ldir.getUnit()))
1446                Ldir = (GeomVector)Ldir.toDimension(numDims).to(unit);
1447
1448            //  Get the bounding box of the geometry element.
1449            Point bmin = box.getBoundsMin().toDimension(numDims).minus(GTOL);
1450            Point bmax = box.getBoundsMax().toDimension(numDims).plus(GTOL);
1451
1452            //  Do any of the principal axes form a separating axis?
1453            for (int i = 0; i < numDims; ++i) {     //  Loop over each axis.
1454                double ui = Ldir.getValue(i);       //  Component of the line this axis direction.
1455
1456                //  Is the line parallel to this axis?
1457                if (MathTools.isApproxZero(ui)) {
1458                    double L0i = L0.getValue(i, unit);
1459                    if (L0i < bmin.getValue(i, unit) || L0i > bmax.getValue(i, unit))
1460                        return false;
1461                }
1462            }
1463
1464            //  Check to see if there are any intersections with the bounding box.
1465            for (int i = 0; i < numDims; ++i) {
1466                double ui = Ldir.getValue(i);
1467                if (MathTools.isApproxZero(ui))
1468                    continue;
1469                double L0i = L0.getValue(i, unit);
1470
1471                double t0 = (bmin.getValue(i, unit) - L0i) / ui; //  t0 = (bmin[i] - p0[i]) / u[i];
1472                double t1 = (bmax.getValue(i, unit) - L0i) / ui; //  t1 = (bmax[i] - p0[i]) / u[i];
1473                if (t0 > t1) {
1474                    //  Swap
1475                    double tmp = t0;
1476                    t0 = t1;
1477                    t1 = tmp;
1478                }
1479
1480                if (t0 > tIn)
1481                    tIn = t0;
1482                if (t1 < tOut)
1483                    tOut = t1;
1484                if (tIn > tOut || tOut < 0)
1485                    return false;
1486            }
1487
1488        } finally {
1489            StackContext.exit();
1490        }
1491
1492        if (nonNull(sOut)) {
1493            sOut.setValue(0, tIn);
1494            sOut.setValue(1, tOut);
1495        }
1496
1497        return true;
1498    }
1499
1500    /**
1501     * Returns true if the specified line segment and bounding box of the specified
1502     * geometry element intersect or overlap. This is a line segment aligned axis bounding
1503     * box (AABB) test.
1504     *
1505     * @param p0  One end of the line segment. May not be null.
1506     * @param p1  The other end of the line segment. May not be null.
1507     * @param box The geometry element to extract the bounding box from. May not be null.
1508     * @return <code>true</code> if the line segment and geometry element bounding box
1509     *         intersects or overlaps (including the line segment contained completely
1510     *         inside of the box).
1511     */
1512    public static boolean lineSegBoundsIntersect(GeomPoint p0, GeomPoint p1, GeomElement box) {
1513        //  Reference:
1514        //  http://ggt.sourceforge.net/gmtlReferenceGuide-0.6.1-html/namespacegmtl.html#a4c99e51116eb3c68a52091d6a2480522
1515        requireNonNull(p0);
1516        requireNonNull(p1);
1517        requireNonNull(box);
1518
1519        //  Define a point that represents the parametric position on the line where
1520        //  the intersection occurs.
1521        MutablePoint pt = MutablePoint.valueOf(-Double.MAX_VALUE, Double.MAX_VALUE);
1522        try {
1523            boolean result = lineBoundsIntersect(p0, p1.minus(p0).toGeomVector(), box, pt);
1524            if (!result)
1525                return false;
1526
1527            double tIn = pt.getValue(0);
1528            double tOut = pt.getValue(1);
1529            if (tIn > 1.0)
1530                return false;
1531            if (tOut < 0.0)
1532                return false;
1533
1534        } finally {
1535            MutablePoint.recycle(pt);
1536        }
1537
1538        return true;
1539    }
1540
1541    /**
1542     * Returns true if the specified infinite plane and bounding box of the specified
1543     * geometry element intersect. This is a aligned axis bounding box (AABB) - infinite
1544     * plane test.
1545     *
1546     * @param plane The infinite plane being used for the intersection test. May not be null.
1547     * @param box   The geometry element to extract the bounding box from. May not be null.
1548     * @return <code>true</code> if the plane and geometry element bounding box
1549     *         intersects.
1550     */
1551    public static boolean planeBoundsIntersect(GeomPlane plane, GeomElement box) {
1552        //  Reference:
1553        //  http://www.gamasutra.com/view/feature/131790/simple_intersection_tests_for_games.php?page=7
1554        //  http://www8.cs.umu.se/kurser/5DV058/VT09/lectures/Lecture7.pdf
1555
1556        int numDims = Math.max(plane.getPhyDimension(), box.getPhyDimension());
1557        if (numDims < 3)
1558            numDims = 3;
1559
1560        StackContext.enter();
1561        try {
1562            //  Make sure everything uses the same units.
1563            Unit<Length> unit = box.getUnit();
1564
1565            //  Make sure everything has the dimensions of the largest input object.
1566            GeomPoint P0 = plane.getRefPoint().toDimension(numDims).to(unit);
1567            GeomVector<Dimensionless> nhat = plane.getNormal().toDimension(numDims);
1568
1569            //  Get the bounding box of the geometry element.
1570            Point bmin = box.getBoundsMin().toDimension(numDims).to(unit);
1571            Point bmax = box.getBoundsMax().toDimension(numDims).to(unit);
1572
1573            //  Find center of the bounding box.
1574            Point p1 = bmin.plus(bmax).divide(2);
1575
1576            //  Find extents of the bounding box.
1577            Point E1 = p1.minus(bmin);
1578
1579            //  Find the shortest distance between the center of the box and the plane.
1580            double d = pointPlaneClosest(p1, P0, nhat).distanceValue(p1);
1581
1582            //  Do the overlap test.
1583            double r = 0;
1584            for (int i = 0; i < numDims; ++i) {     //  Loop over each axis.
1585                double E1v = abs(E1.getValue(i));   //  Bounding box extents along this axis.
1586                double ni = abs(nhat.getValue(i));  //  Component of the plane normal vector.
1587                r += E1v * ni;
1588            }
1589            return d <= r;
1590
1591        } finally {
1592            StackContext.exit();
1593        }
1594    }
1595
1596    /**
1597     * Return the approximate normal vector for a quadrilateral panel. Quadrilateral
1598     * panels are not necessarily planar, but in practical applications usually are close.
1599     * This method returns the cross-product of the diagonal vectors of the quadrilateral
1600     * panel which approximates the normal vector of the quadrilateral panel.
1601     *
1602     * @param quadA 1st corner point of a quadrilateral panel. May not be null.
1603     * @param quadB 2nd corner point in a counter-clockwise direction. May not be null.
1604     * @param quadC 3rd corner point in a counter-clockwise direction. May not be null.
1605     * @param quadD 4th corner point in a counter-clockwise direction. May not be null.
1606     * @return The normal vector formed by the cross-product of the diagonal vectors of
1607     *         the quadrilateral panel. Will be at least 3-dimensional no matter the
1608     *         inputs.
1609     */
1610    public static Vector<Dimensionless> quadNormal(GeomPoint quadA, GeomPoint quadB, GeomPoint quadC, GeomPoint quadD) {
1611
1612        int numDims = maxPhyDimension(quadA, quadB, quadC, quadD);
1613        if (numDims < 3)
1614            numDims = 3;
1615
1616        StackContext.enter();
1617        try {
1618            //  Make everything the same dimension.
1619            quadA = quadA.toDimension(numDims);
1620            quadB = quadB.toDimension(numDims);
1621            quadC = quadC.toDimension(numDims);
1622            quadD = quadD.toDimension(numDims);
1623
1624            //  Compute diagonal vectors.
1625            Vector<Length> T1 = quadC.minus(quadA).toGeomVector();
1626            Vector<Length> T2 = quadD.minus(quadB).toGeomVector();
1627
1628            // Compute normal vector as cross product:  N = T1 X T2
1629            Vector norm = T1.cross(T2);
1630
1631            //  Compute average point.
1632            Point avg = quadA.plus(quadB).plus(quadC).plus(quadD).divide(4);
1633            norm.setOrigin(avg);
1634
1635            //  Make the normal vector unit length.
1636            Vector<Dimensionless> n = norm.toUnitVector();
1637            return StackContext.outerCopy(n);
1638
1639        } finally {
1640            StackContext.exit();
1641        }
1642    }
1643
1644    /**
1645     * Test to see if a line segment intersects a triangle.
1646     *
1647     * @param L0   The start point on the line segment (origin of the line). May not be
1648     *             null.
1649     * @param L1   The end point on the line segment. May not be null.
1650     * @param V0   1st corner point of a triangle. May not be null.
1651     * @param V1   2nd corner point in a counter-clockwise direction. May not be null.
1652     * @param V2   3rd corner point in a counter-clockwise direction. May not be null.
1653     * @param pOut A point that will be filled in with the intersection point in this
1654     *             triangle. If there is no intersection with this triangle, then pOut is
1655     *             not modified. Pass <code>null</code> if the intersection point is not
1656     *             required.
1657     * @return DISJOINT if the line and triangle are disjoint (do not intersect),
1658     *         INTERSECT if there is a unique intersection and COINCIDENT if the line is
1659     *         coincident with the plane of the triangle.
1660     */
1661    public static IntersectType lineSegTriIntersect(GeomPoint L0, GeomPoint L1,
1662            GeomPoint V0, GeomPoint V1, GeomPoint V2, MutablePoint pOut) {
1663        //  Reference: http://geomalgorithms.com/a06-_intersect-2.html#intersect3D_RayTriangle()
1664
1665        int numDims = maxPhyDimension(L0, L1, V0, V1, V2);
1666        if (numDims < 3)
1667            numDims = 3;
1668
1669        StackContext.enter();
1670        try {
1671            //  Make sure all the geometry is the same dimension and units.
1672            Unit<Length> unit = L0.getUnit();
1673            if (nonNull(pOut) && pOut.getPhyDimension() != numDims)
1674                throw new DimensionException(RESOURCES.getString("pOutDimErr"));
1675            L0 = L0.toDimension(numDims);
1676            L1 = L1.toDimension(numDims).to(unit);
1677            V0 = V0.toDimension(numDims).to(unit);
1678            V1 = V1.toDimension(numDims).to(unit);
1679            V2 = V2.toDimension(numDims).to(unit);
1680
1681            //  Determine the triangle normal vector.
1682            Vector<Length> u = V1.minus(V0).toGeomVector();
1683            Vector<Length> v = V2.minus(V0).toGeomVector();
1684            Vector n = u.cross(v);
1685            if (n.magValue() < MathTools.SQRT_EPS) {
1686                //  Triangle is degenerate (a line or point).
1687                double um = u.magValue();
1688                double vm = v.magValue();
1689                if (um < MathTools.SQRT_EPS && vm < MathTools.SQRT_EPS) {
1690                    //  Triangle is actually a single point.
1691                    //  Does the input line segment cross this single point?
1692                    Parameter D = pointLineSegDistance(V0, L0, L1);
1693                    if (D.isApproxZero()) {
1694                        if (nonNull(pOut)) {
1695                            Point V0p = V0.immutable();
1696                            pOut.set(StackContext.outerCopy(V0p));
1697                        }
1698                        return IntersectType.INTERSECT;
1699                    } else
1700                        return IntersectType.DISJOINT;
1701                }
1702
1703                //  The triangle is a straight line segment.
1704                if (um > vm)
1705                    v = u;
1706                Vector<Length> dir = L1.minus(L0).toGeomVector();
1707                MutablePoint sOut = MutablePoint.newInstance(2);
1708                IntersectType type = lineLineIntersect(L0, dir, V0, v, GTOL, sOut, null, null);
1709                if (type == IntersectType.INTERSECT) {
1710                    double s1 = sOut.getValue(0);
1711                    double s2 = sOut.getValue(1);
1712                    if (s1 > 0 && s1 < 1 && s2 > 0 && s2 < 1) {
1713                        //  The input line segment intersected the collapsed triangle line.
1714                        if (nonNull(pOut)) {
1715                            Point p = L0.plus(Point.valueOf(dir.times(s1)));
1716                            pOut.set(StackContext.outerCopy(p));
1717                        }
1718                        return IntersectType.INTERSECT;
1719                    }
1720                }
1721                return IntersectType.DISJOINT;
1722            }
1723            Vector<Dimensionless> nhat = n.toUnitVector();
1724
1725            Vector<Length> dir = L1.minus(L0).toGeomVector();
1726            Vector<Length> w0 = L0.minus(V0).toGeomVector();
1727            double a = -nhat.dot(w0).getValue();
1728            double b = nhat.dot(dir).getValue();
1729            if (abs(b) < MathTools.SQRT_EPS) {
1730                //  Line is  parallel to triangle plane
1731                if (abs(a) < MathTools.SQRT_EPS)
1732                    //  Line lies in triangle plane
1733                    return IntersectType.COINCIDENT;
1734                else
1735                    //  Line disjoint from plane
1736                    return IntersectType.DISJOINT;
1737            }
1738
1739            //  Get intersect point of line with triangle plane
1740            double rv = a / b;
1741            if (rv < 0.0) {
1742                //  Line goes away from triangle (=> no intersect)
1743                return IntersectType.DISJOINT;
1744            } else if (rv > 1.0) {
1745                //  Line does not reach the triangle (=> no intersect)
1746                return IntersectType.DISJOINT;
1747            }
1748
1749            //  Intersect point of line and triangle
1750            Point I = L0.plus(Point.valueOf(dir.times(rv)));     //  I = L0 + r * dir
1751
1752            //  Is I inside T?
1753            double uu = u.dot(u).getValue();
1754            double uv = u.dot(v).getValue();
1755            double vv = v.dot(v).getValue();
1756            Vector<Length> w = I.minus(V0).toGeomVector();
1757            double wu = w.dot(u).getValue();
1758            double wv = w.dot(v).getValue();
1759            double D = uv * uv - uu * vv;
1760
1761            //  Get and test parametric coords
1762            double s = (uv * wv - vv * wu) / D;
1763            if (s < 0.0 || s > 1.0) {
1764                // I is outside T
1765                return IntersectType.DISJOINT;
1766            }
1767            double t = (uv * wu - uu * wv) / D;
1768            if (t < 0.0 || (s + t) > 1.0) {
1769                // I is outside T
1770                return IntersectType.DISJOINT;
1771            }
1772
1773            //  Change the output point if we get this far.
1774            if (nonNull(pOut)) {
1775                pOut.set(StackContext.outerCopy(I));
1776            }
1777
1778            return IntersectType.INTERSECT;
1779
1780        } finally {
1781            StackContext.exit();
1782        }
1783    }
1784
1785    /**
1786     * Test to see if an infinite line intersects a quadrilateral panel.
1787     *
1788     * @param L0    A point on the line (origin of the line). May not be null.
1789     * @param Ldir  The direction vector for the line (does not have to be a unit vector).
1790     *              May not be null.
1791     * @param quadA 1st corner point of a quadrilateral panel. May not be null.
1792     * @param quadB 2nd corner point in a counter-clockwise direction. May not be null.
1793     * @param quadC 3rd corner point in a counter-clockwise direction. May not be null.
1794     * @param quadD 4th corner point in a counter-clockwise direction. May not be null.
1795     * @param nhat  The normal vector for the quadrilateral panel. May not be null.
1796     * @param sOut  A 2D point that will be filled in with the parametric position of the
1797     *              intersection point in this quad (s, t) where "s" is the fractional
1798     *              distance between corners AB (or DC) and "t" is the fractional distance
1799     *              between corners AD (or BC). If there is no intersection with this
1800     *              panel, then sOut is not modified. Pass <code>null</code> if the
1801     *              parametric position is not required.
1802     * @return <code>true</code> if the line and quadrilateral panel intersect.
1803     */
1804    public static boolean lineQuadIntersect(GeomPoint L0, GeomVector Ldir,
1805            GeomPoint quadA, GeomPoint quadB, GeomPoint quadC, GeomPoint quadD,
1806            GeomVector nhat, MutablePoint sOut) {
1807        //  Reference:
1808        //  Graphic Gems V:  http://tog.acm.org/resources/GraphicsGems/gemsv/ch5-2/quad_gg.c
1809
1810        //  Make sure all the geometry is the same dimension and units.
1811        Unit<Length> unit = L0.getUnit();
1812        int numDims = maxPhyDimension(L0, Ldir, quadA, quadB, quadC, quadD, nhat);
1813        if (numDims < 3)
1814            numDims = 3;
1815        Ldir = Ldir.toDimension(numDims).toUnitVector();
1816        nhat = nhat.toDimension(numDims).toUnitVector();
1817
1818        // If the line is parallel to the facet, there is no intersection.
1819        Parameter D = Ldir.dot(nhat);
1820        if (D.isApproxZero())
1821            return false;
1822
1823        L0 = L0.toDimension(numDims);
1824        quadA = quadA.toDimension(numDims).to(unit);
1825        quadB = quadB.toDimension(numDims).to(unit);
1826        quadC = quadC.toDimension(numDims).to(unit);
1827        quadD = quadD.toDimension(numDims).to(unit);
1828
1829        // Compute line intersection with the plane of the facet
1830        Plane qPln = Plane.valueOf(nhat, averagePoints(quadA,quadB,quadC,quadD));
1831        MutablePoint hit = MutablePoint.newInstance(numDims);
1832        IntersectType intersection = linePlaneIntersect(L0, Ldir, qPln, hit);
1833        Plane.recycle(qPln);
1834        if (intersection != IntersectType.INTERSECT) {
1835            MutablePoint.recycle(hit);
1836            return false;
1837        }
1838
1839        //  Is the intersection point inside the facet
1840        boolean inside = pointInQuad(quadA, quadB, quadC, quadD, nhat, hit, sOut);
1841        MutablePoint.recycle(hit);
1842
1843        return inside;
1844    }
1845
1846    /**
1847     * Tests if the specified point, in the plane of an approximately planar quadrilateral
1848     * panel, is inside the boundaries of the quadrilateral panel or not.
1849     */
1850    private static boolean pointInQuad(GeomPoint quadA, GeomPoint quadB, GeomPoint quadC, GeomPoint quadD,
1851            GeomVector nhat, GeomPoint hit, MutablePoint sOut) {
1852        //  Reference:
1853        //  Graphic Gems V:  http://tog.acm.org/resources/GraphicsGems/gemsv/ch5-2/quad_gg.c
1854        
1855        double u = 0, v = 0;
1856        boolean isInside = false;
1857
1858        StackContext.enter();
1859        try {
1860            //  Projection on the plane that is most parallel to the facet
1861            int largestComp = largestComponent(nhat);
1862
1863            Unit<Length> unit = quadA.getUnit();
1864            MutablePoint A = MutablePoint.newInstance(2, unit);        //  Projected vertices
1865            MutablePoint B = MutablePoint.newInstance(2, unit);
1866            MutablePoint C = MutablePoint.newInstance(2, unit);
1867            MutablePoint D = MutablePoint.newInstance(2, unit);
1868            MutablePoint M = MutablePoint.newInstance(2, unit);        //  Projected intersection point.
1869            if (largestComp == 0) {         //  X
1870                A.set(0, quadA.get(1));     //  A.x = Quad->A.y;
1871                B.set(0, quadB.get(1));     //  B.x = Quad->B.y;
1872                C.set(0, quadC.get(1));     //  C.x = Quad->C.y;
1873                D.set(0, quadD.get(1));     //  D.x = Quad->D.y;
1874                M.set(0, hit.get(1));       //  M.x = hit.y
1875            } else {
1876                A.set(0, quadA.get(0));     //  A.x = Quad->A.x;
1877                B.set(0, quadB.get(0));     //  B.x = Quad->B.x;
1878                C.set(0, quadC.get(0));     //  C.x = Quad->C.x;
1879                D.set(0, quadD.get(0));     //  D.x = Quad->D.x;
1880                M.set(0, hit.get(0));       //  M.x = hit.x;
1881            }
1882
1883            if (largestComp == 2) {         //  Z
1884                A.set(1, quadA.get(1));     //  A.y = Quad->A.y;
1885                B.set(1, quadB.get(1));     //  B.y = Quad->B.y;
1886                C.set(1, quadC.get(1));     //  C.y = Quad->C.y;
1887                D.set(1, quadD.get(1));     //  D.y = Quad->D.y;
1888                M.set(1, hit.get(1));       //  M.y = Hit->Point.y;
1889
1890            } else {
1891                A.set(1, quadA.get(2));     //  A.y = Quad->A.z;
1892                B.set(1, quadB.get(2));     //  B.y = Quad->B.z;
1893                C.set(1, quadC.get(2));     //  C.y = Quad->C.z;
1894                D.set(1, quadD.get(2));     //  D.y = Quad->D.z;
1895                M.set(1, hit.get(2));       //  M.y = Hit->Point.z;
1896            }
1897
1898            Vector AB = B.minus(A).toGeomVector();
1899            Vector BC = C.minus(B).toGeomVector();
1900            Vector CD = D.minus(C).toGeomVector();
1901            Vector AD = D.minus(A).toGeomVector();
1902            Vector AE = CD.plus(AB).opposite();         //  AE = DC - AB = DA - CB
1903            Vector AM = M.minus(A).toGeomVector();
1904
1905            if (abs(determinant2D(AB, CD)) < SQRT_EPS) {
1906                //  case AB // CD
1907                Vector vec = AB.minus(CD);
1908                v = determinant2D(AM, vec) / determinant2D(AD, vec);
1909                if ((v >= -SQRT_EPS) && (v <= 1.0 + SQRT_EPS)) {
1910                    double b = determinant2D(AB, AD) - determinant2D(AM, AE);
1911                    double c = determinant2D(AM, AD);
1912                    u = abs(b) <= EPS ? -1 : c / b;
1913                    isInside = ((u >= -SQRT_EPS) && (u <= 1.0 + SQRT_EPS));
1914                }
1915
1916            } else if (abs(determinant2D(BC, AD)) < SQRT_EPS) {
1917                //  case AD // BC
1918                Vector vec = AD.plus(BC);
1919                u = determinant2D(AM, vec) / determinant2D(AB, vec);
1920                if ((u >= -SQRT_EPS) && (u <= 1.0 + SQRT_EPS)) {
1921                    double b = determinant2D(AD, AB) - determinant2D(AM, AE);
1922                    double c = determinant2D(AM, AB);
1923                    v = abs(b) <= EPS ? -1 : c / b;
1924                    isInside = ((v >= -SQRT_EPS) && (v <= 1.0 + SQRT_EPS));
1925                }
1926
1927            } else {
1928                //  General case
1929                double a = determinant2D(AB, AE);
1930                double c = -determinant2D(AM, AD);
1931                double b = determinant2D(AB, AD) - determinant2D(AM, AE);
1932                a = -0.5 / a;
1933                b *= a;
1934                c *= (a + a);
1935                double SqrtDelta = b * b + c;
1936                if (SqrtDelta >= 0.0) {
1937                    SqrtDelta = sqrt(SqrtDelta);
1938                    u = b - SqrtDelta;
1939                    if ((u < -SQRT_EPS) || (u > 1.0 + SQRT_EPS))     // to choose u between 0 and 1
1940                        u = b + SqrtDelta;
1941                    if ((u >= -SQRT_EPS) && (u <= 1.0 + SQRT_EPS)) {
1942                        v = AD.getValue(0) + u * AE.getValue(0);
1943                        if (abs(v) <= EPS)
1944                            v = (AM.getValue(1) - u * AB.getValue(1)) / (AD.getValue(1) + u * AE.getValue(1));
1945                        else
1946                            v = (AM.getValue(0) - u * AB.getValue(0)) / v;
1947                        isInside = ((v >= -SQRT_EPS) && (v <= 1.0 + SQRT_EPS));
1948                    }
1949                }
1950            }
1951
1952        } finally {
1953            StackContext.exit();
1954        }
1955
1956        if (isInside && nonNull(sOut) && sOut.getPhyDimension() > 1) {
1957            sOut.setValue(0, u);
1958            sOut.setValue(1, v);
1959        }
1960
1961        return isInside;
1962    }
1963
1964    private static int largestComponent(GeomVector A) {
1965        int maxDim = 0;
1966        double maxValue = -1;
1967        int numDims = A.getPhyDimension();
1968        for (int i = 0; i < numDims; ++i) {
1969            double v = abs(A.getValue(i));
1970            if (v > maxValue) {
1971                maxValue = v;
1972                maxDim = i;
1973            }
1974        }
1975        return maxDim;
1976    }
1977
1978    private static double determinant2D(Vector A, Vector B) {
1979        return A.getValue(Vector.X) * B.getValue(Vector.Y) - A.getValue(Vector.Y) * B.getValue(Vector.X);
1980    }
1981
1982    /**
1983     * Test to see if an infinite plane intersects a quadrilateral panel.
1984     *
1985     * @param P0    A reference point in the plane to test for intersection with a
1986     *              quadrilateral panel. May not be null.
1987     * @param nhat  The unit normal vector for the plane. May not be null.
1988     * @param quadA 1st corner point of a quadrilateral panel. May not be null.
1989     * @param quadB 2nd corner point in a counter-clockwise direction. May not be null.
1990     * @param quadC 3rd corner point in a counter-clockwise direction. May not be null.
1991     * @param quadD 4th corner point in a counter-clockwise direction. May not be null.
1992     * @return <code>true</code> if the plane and quadrilateral panel intersect.
1993     */
1994    public static boolean planeQuadIntersect(GeomPoint P0, GeomVector<Dimensionless> nhat, 
1995            GeomPoint quadA, GeomPoint quadB, GeomPoint quadC, GeomPoint quadD) {
1996
1997        int numDims = maxPhyDimension(P0, nhat, quadA, quadB, quadC, quadD);
1998
1999        StackContext.enter();
2000        try {
2001            //  Convert everything to the same physical dimensions and units.
2002            Unit<Length> unit = quadA.getUnit();
2003            P0 = P0.toDimension(numDims).to(unit);
2004            nhat = nhat.toDimension(numDims);
2005            quadA = quadA.toDimension(numDims).to(unit);
2006            quadB = quadB.toDimension(numDims).to(unit);
2007            quadC = quadC.toDimension(numDims).to(unit);
2008            quadD = quadD.toDimension(numDims).to(unit);
2009
2010            boolean intersects = false;
2011
2012            //  Edge 1
2013            Parameter<Length> d0 = pointPlaneDistance(quadA, P0, nhat);
2014            Parameter<Length> d1 = pointPlaneDistance(quadB, P0, nhat);
2015            double d0v = d0.getValue();
2016            if (d0v * d1.getValue() < 0) {
2017                //  An intersection on this patch edge exists.
2018                intersects = true;
2019
2020            } else {
2021                //  Edge 4
2022                Parameter<Length> d2 = pointPlaneDistance(quadD, P0, nhat);
2023                if (d0v * d2.getValue() < 0) {
2024                    //  An intersection on this patch edge exists.
2025                    intersects = true;
2026
2027                } else {
2028                    //  Edge 3
2029                    d0 = GeomUtil.pointPlaneDistance(quadC, P0, nhat);
2030                    d0v = d0.getValue();
2031                    if (d0v * d2.getValue() < 0) {
2032                        intersects = true;
2033                    } else {
2034                        //  Edge 2
2035                        if (d0v * d1.getValue() < 0) {
2036                            intersects = true;
2037                        }
2038                    }
2039                }
2040            }
2041
2042            return intersects;
2043
2044        } finally {
2045            StackContext.exit();
2046        }
2047    }
2048
2049    /**
2050     * <p>
2051     * Method that detects corners in a a list of 2D points which represent a planar
2052     * curve. The input array "sValues" is filled in with the sharpness of a corner
2053     * detected at each point. If no corner is detected at a point, that entry in
2054     * "sValues" is filled in with Math.PI. The opening angle of a triangle fit to each
2055     * corner can be calculated as: <code>alpha[i] = Math.PI - sValues[i];</code>.
2056     * </p>
2057     *
2058     * @param points  A list of at least 2D GeomPoint objects making up a planar curve. If
2059     *                the input points are greater than 2D, then the higher dimensions are
2060     *                simply ignored.
2061     * @param sValues An existing array of doubles of size points.size(). The sharpness
2062     *                (in radians) of a corner applied to each point will be stored here.
2063     * @param dmin    The minimum distance to consider (any points closer together than
2064     *                this are ignored.
2065     * @param dmax    The maximum distance to consider (any points further apart than this
2066     *                are ignored).
2067     * @param amax    The maximum opening angle to consider as a corner.
2068     * @return A count of the number of corners (sharper than <code>amax</code>) detected
2069     * in the curve.
2070     */
2071    public static int detectCorners(List<? extends GeomPoint> points, double[] sValues,
2072            Parameter<Length> dmin, Parameter<Length> dmax, Parameter<Angle> amax) {
2073        /*
2074            Uses the IPAN99 algorithm described in: Chetverinkov, D., Szabo, Z.,
2075            "A Simple and Efficient Algorithm for Detection of High Curvature Points
2076            in Planar Curves", Proc. 23rd Workshop of Austrian Pattern Recognition Group,
2077            Steyr, pp. 175-184, 1999.
2078         */
2079        
2080        //  Convert the input geometry to 2D.
2081        PointString pointsLst;
2082        if (points instanceof PointString)
2083            pointsLst = (PointString)points;
2084        else {
2085            pointsLst = PointString.newInstance();
2086            pointsLst.addAll(points);
2087        }
2088        if (pointsLst.getPhyDimension() < 2) {
2089            throw new IllegalArgumentException(
2090                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"),
2091                            "input points", pointsLst.getPhyDimension()));
2092        }
2093        pointsLst = pointsLst.toDimension(2);
2094        
2095        //  Extract some things we need.
2096        Unit<Length> units = pointsLst.getUnit();
2097        double dminv = dmin.getValue(units);
2098        double dminv2 = dminv*dminv;
2099        double dmaxv = dmax.getValue(units);
2100        double dmaxv2 = dmaxv*dmaxv;
2101        double amaxv = amax.getValue(SI.RADIAN);
2102
2103        //  Loop over all the points in this curve and
2104        //  calculate the opening angle of the sharpest triangle
2105        //  that will fit the curve at that point.
2106        int numPntsm1 = pointsLst.size() - 1;
2107        for (int j = 1; j < numPntsm1; ++j) {
2108            //  Return the sharpness of the sharpest triangle fit
2109            double sharpness = fitTriangle(pointsLst, j, dminv2, dmaxv2, amaxv);
2110            sValues[j] = sharpness;
2111        }
2112        
2113        //  Remove spurious corners.
2114        int cornerCount = removeAdjacentCorners(pointsLst, sValues, dminv2);
2115        
2116        return cornerCount;
2117    }
2118
2119    /**
2120     *  Method that fits a series of triangles to a list of points
2121     *  at the specified index and returns the sharpness of the sharpest
2122     *  triangle.
2123     *
2124     *  @param  points  A list of 2D GeomPoint objects making up a planar curve.
2125     *  @param  ip      Index to the point for the tip of the triangle.  Can not be
2126     *                  outside range 1 to points.length-2.
2127     *  @param  dmin2   The minimum distance to consider squared.
2128     *  @param  dmax2   The maximum distance to consider squared.
2129     *  @param  amax    The maximum opening angle to consider (in radians).
2130     *  @return The sharpness of the sharpest triangle found is returned (radians).
2131     */
2132    private static double fitTriangle(PointString<GeomPoint> points, int ip, double dmin2, double dmax2, double amax) {
2133        double sharpest = Math.PI;
2134        
2135        GeomPoint p = points.get(ip);
2136        int ipm = ip - 1;
2137        int ipp = ip + 1;
2138        boolean firstPass = true;
2139        boolean notDone = true;
2140        while (notDone) {
2141            //  Get end points of the triangle.
2142            GeomPoint pm = points.get(ipm);
2143            GeomPoint pp = points.get(ipp);
2144            double a2 = p.distanceSqValue(pp);
2145            double b2 = p.distanceSqValue(pm);
2146            
2147            //  Check criteria.
2148            boolean c2 = (a2 < dmax2) || firstPass;
2149            boolean c4 = (b2 < dmax2) || firstPass;
2150            if (c2 && c4 && a2 > dmin2 && b2 > dmin2) {
2151                firstPass = false;
2152                
2153                //  Calculate the opening angle of the triangle.
2154                double alpha = calcAlpha(a2, b2, pp.distanceSqValue(pm));
2155                
2156                if (alpha < amax) {
2157                    //  Calculate sharpness for this triangle.
2158                    double sharpness = Math.PI - Math.abs(alpha);
2159                    if (sharpness < sharpest)
2160                        sharpest = sharpness;
2161                } else
2162                    notDone = false;
2163            }
2164            
2165            //  If we are not further away than the tolerance, then try another triangle.
2166            if (c2 && c4) {
2167                ++ipp;
2168                --ipm;
2169                if (ipp > points.size() - 1)
2170                    ipp = points.size() - 1;
2171                if (ipm < 0)
2172                    notDone = false;
2173            } else {
2174                notDone = false;
2175            }
2176        }
2177        
2178        return sharpest;
2179    }
2180
2181    /**
2182     * Method that calculates a triangle opening angle (the angle at the tip of
2183     * the triangle that has legs of length "a", and "b" and a base of length
2184     * "c"). Returns the opening angle in radians.
2185     */
2186    private static double calcAlpha(double a2, double b2, double c2) {
2187        double ab = Math.sqrt(a2 * b2);
2188        double den = 2 * ab;
2189        double num = a2 + b2 - c2;
2190        return Math.acos(num / den);
2191    }
2192
2193    /**
2194     * Method that removes adjacent corners. It searches through the provided array of
2195     * corners and removes any that are closer together than <code>dmin2</code>.
2196     *
2197     * @param points  A list of 2D GeomPoint objects representing the points in a planar curve.
2198     * @param sValues An array of sharpness values for each point in the point list
2199     *                (radians).
2200     * @param tol2    The square of the tolerance to use on determining if corners are
2201     *                adjacent.
2202     * @return A count of the number of corners remaining in the curve.
2203     */
2204    private static int removeAdjacentCorners(PointString<GeomPoint> points, double[] sValues, double tol2) {
2205        int cornerCount = 0;
2206        int numPntsm1 = points.size() - 1;
2207
2208        for (int j = 1; j < numPntsm1; ++j) {
2209            double sharpness = sValues[j];
2210            if (sharpness < Math.PI) {
2211                //  Potential corner found.
2212                ++cornerCount;
2213
2214                //  Does it have any sharper neighbors.
2215                int pp = j + 1;
2216                GeomPoint pointj = points.get(j);
2217
2218                while (pp < numPntsm1) {
2219                    //  Make sure we aren't to far away from "j".
2220                    double d2 = pointj.distanceSqValue(points.get(pp));
2221                    if (d2 >= tol2)
2222                        break;
2223
2224                    if (sValues[pp] < sharpness) {
2225                        //  Sharper neighbor found, discard corner "j".
2226                        sValues[j] = Math.PI;
2227                        --cornerCount;
2228                        break;
2229
2230                    } else // If it is not sharper than "j", then discard it.
2231                        sValues[pp] = Math.PI;
2232
2233                    //  Move on to the next point.
2234                    ++pp;
2235                }
2236            }
2237        }
2238
2239        return cornerCount;
2240    }
2241
2242    /**
2243     * Return an ordered list of 2D points that represent the convex hull of a
2244     * collection of unordered 2D points.
2245     *
2246     * @param points A collection of 2D points to find the convex hull of. If
2247     * the input points have dimensions &gt; 2, then only the 1st two dimensions
2248     * are used (higher dimensions are dropped).
2249     * @return The ordered 2D points representing the convex hull of the input
2250     * collection of points.
2251     */
2252    public static PointString<Point> convexHull(Collection<? extends GeomPoint> points) {
2253        if (points.isEmpty())
2254            return PointString.newInstance();
2255        
2256        //  Convert the GeomSS points into a set of apache commons 2D vectors.
2257        Unit<Length> units = null;
2258        boolean firstPass = true;
2259        Collection<Vector2D> points2d = new ArrayList();
2260        for (GeomPoint p : points) {
2261            if (firstPass) {
2262                firstPass = false;
2263                units = p.getUnit();
2264            }
2265            Vector2D v2d = new Vector2D(p.getValue(Point.X, units), p.getValue(Point.Y, units));
2266            points2d.add(v2d);
2267        }
2268        
2269        //  Create a convex hull from the 2D points.
2270        points2d = AklToussaintHeuristic.reducePoints(points2d);
2271        MonotoneChain mchain = new MonotoneChain();
2272        List<Vector2D> vertices2d = new ArrayList(mchain.findHullVertices(points2d));
2273 
2274        //  Convert the 2D apache commons verticies into 2D GeomSS Point objects.
2275        PointString<Point> chullPnts = PointString.newInstance();
2276        for (Vector2D v2d : vertices2d) {
2277            chullPnts.add(Point.valueOf(v2d.getX(),v2d.getY(),units));
2278        }
2279        
2280        return chullPnts;
2281    }
2282    
2283    /**
2284     * Return a list of strings of ordered 2D points that represent the 2D
2285     * convex hulls of each input collection of 2D points. The convex hull for
2286     * each collection of points in the list will be run concurrently.
2287     *
2288     * @param listOfPointCollections The list of collections of 2D points to
2289     * find the convex hull of. If the input points have dimensions &gt; 2, then
2290     * only the 1st two dimensions are used (higher dimensions are dropped).
2291     * @return A list of strings of ordered 2D points that represent the convex
2292     * hull of the input collections of points.
2293     */
2294    public static GeomList<PointString<Point>> convexHullList(List<Collection<? extends GeomPoint>> listOfPointCollections) {
2295        if (listOfPointCollections.isEmpty())
2296            return GeomList.newInstance();
2297        
2298        //  Create an array to hold the output strings of points.
2299        PointString<Point>[] outputArr = new PointString[listOfPointCollections.size()];
2300        
2301        //  Use a ConcurrentContext to run each convex hull in parallel.
2302        ConcurrentContext.enter();
2303        try {
2304            int size = listOfPointCollections.size();
2305            for (int i=0; i < size; ++i) {
2306                Collection<? extends GeomPoint> pointCollection = listOfPointCollections.get(i);
2307                
2308                //  Run each convex hull concurrently on its own thread.
2309                Runnable runner = new ConvexHullRunner(pointCollection, outputArr, i);
2310                ConcurrentContext.execute(runner);
2311            }
2312        } finally {
2313            ConcurrentContext.exit();
2314        }
2315
2316        //  Convert the outputs from an array to a GeomList.
2317        GeomList<PointString<Point>> output = GeomList.newInstance();
2318        output.addAll(outputArr);
2319        
2320        return output;
2321    }
2322
2323    /**
2324     * A Runnable for use with computing the convex hull of a collection of 2D points.
2325     */
2326    private static class ConvexHullRunner implements Runnable {
2327        private final Collection<? extends GeomPoint> points;
2328        private final PointString<Point>[] output;
2329        private final int idx;
2330        
2331        public ConvexHullRunner(Collection<? extends GeomPoint> points, 
2332                PointString<Point>[] outputArr, int outIdx) {
2333            this.points = points;
2334            this.output = outputArr;
2335            this.idx = outIdx;
2336        }
2337
2338        @Override
2339        public void run() {
2340            //  Run the convex hull on this collection of points and store the outputs.
2341            output[idx] = convexHull(points);
2342        }
2343    }
2344}