001/*
002 *   LineSegment  -- Represents an abstract line segment in n-D space.
003 *
004 *   Copyright (C) 2013-2017, Joseph A. Huwaldt.
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom;
023
024import geomss.geom.nurbs.CurveFactory;
025import geomss.geom.nurbs.NurbsCurve;
026import jahuwaldt.js.param.Parameter;
027import jahuwaldt.tools.math.BracketRoot1D;
028import jahuwaldt.tools.math.Evaluatable1D;
029import jahuwaldt.tools.math.RootException;
030import java.text.MessageFormat;
031import java.util.List;
032import static java.util.Objects.nonNull;
033import static java.util.Objects.requireNonNull;
034import java.util.logging.Level;
035import java.util.logging.Logger;
036import javax.measure.converter.ConversionException;
037import javax.measure.quantity.Dimensionless;
038import javax.measure.quantity.Length;
039import javax.measure.unit.SI;
040import javax.measure.unit.Unit;
041import javolution.lang.MathLib;
042import javolution.text.Text;
043import javolution.text.TextBuilder;
044import javolution.util.FastTable;
045
046/**
047 * The interface and implementation in common to all line segments in n-dimensional space.
048 * 
049 * <p> Modified by: Joseph A. Huwaldt </p>
050 * 
051 * @author Joseph A. Huwaldt, Date: January 14, 2012
052 * @version January 31, 2017
053 */
054@SuppressWarnings({"serial", "CloneableImplementsClone"})
055public abstract class LineSegment extends AbstractCurve<LineSegment> implements PointGeometry<LineSegment> {
056
057    /**
058     * Return the starting point of the line segment.
059     *
060     * @return The starting point for this line segment.
061     */
062    public abstract GeomPoint getStart();
063
064    /**
065     * Return the end point of the line segment.
066     *
067     * @return The end point of this line segment.
068     */
069    public abstract GeomPoint getEnd();
070
071    /**
072     * Get unit direction vector for the line segment.
073     *
074     * @return A unit vector indicating the direction of this line segment.
075     */
076    public abstract GeomVector<Dimensionless> getUnitVector();
077
078    /**
079     * Return the dimensional direction/derivative vector for this line segment. The
080     * length of this vector is the length of the line segment, the origin is at the start
081     * point and the end of the vector is the line end.
082     *
083     * @return The dimensional derivative vector for this line segment.
084     */
085    public abstract GeomVector<Length> getDerivativeVector();
086
087    /**
088     * Returns the number of child-elements that make up this geometry element. This
089     * implementation always returns 2 as a LineSegment has two end points.
090     *
091     * @return The number of points in this line segment (always returns 2).
092     */
093    @Override
094    public int size() {
095        return 2;
096    }
097
098    /**
099     * Returns the number of physical dimensions of the geometry element.
100     *
101     * @return The number of physical dimensions of this geometry element.
102     */
103    @Override
104    public int getPhyDimension() {
105        return getStart().getPhyDimension();
106    }
107
108    /**
109     * Return the total number of points in this geometry element.
110     *
111     * @return Always returns 2.
112     */
113    @Override
114    public int getNumberOfPoints() {
115        return 2;
116    }
117
118    /**
119     * Split this curve at the specified parametric position returning a list containing
120     * two curves (a lower curve with smaller parametric positions than "s" and an upper
121     * curve with larger parametric positions).
122     *
123     * @param s The parametric position where this curve should be split (must not be 0 or
124     *          1!).
125     * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve.
126     */
127    @Override
128    public GeomList<LineSegment> splitAt(double s) {
129        validateParameter(s);
130        s = roundParNearEnds(s);
131        if (parNearEnds(s, TOL_S))
132            throw new IllegalArgumentException(MessageFormat.format(
133                    RESOURCES.getString("canNotSplitAtEnds"), "curve"));
134
135        //  Get the point at the specified split point.
136        Point p = getRealPoint(s);
137
138        //  Create the curve segments.
139        LineSeg cl = LineSeg.valueOf(getStart().immutable(), p);
140        LineSeg cu = LineSeg.valueOf(p, getEnd().immutable());
141
142        //  Create the output list.
143        GeomList output = GeomList.newInstance();
144        output.add(cl, cu);
145
146        return output;
147    }
148
149    /**
150     * Return the curvature (kappa = 1/rho; where rho = the radius of curvature) of the
151     * curve at the parametric position <code>s</code>. This implementation always returns
152     * zero.
153     *
154     * @param s Parametric distance to calculate the curvature for (0.0 to 1.0 inclusive).
155     * @return The curvature of the curve at the specified parametric position in units of
156     *         1/Length.
157     */
158    @Override
159    public Parameter getCurvature(double s) {
160        validateParameter(s);
161        return Parameter.valueOf(0, getUnit().inverse());
162    }
163
164    /**
165     * Return the variation of curvature or rate of change of curvature (VOC or
166     * dKappa(s)/ds) at the parametric position <code>s</code>. The VOC for a line segment
167     * is always zero.
168     *
169     * @param s Parametric distance to calculate the variation of curvature for (0.0 to
170     *          1.0 inclusive).
171     * @return The variation of curvature of the curve at the specified parametric
172     *         position in units of 1/Length.
173     */
174    @Override
175    public Parameter getVariationOfCurvature(double s) {
176        validateParameter(s);
177        return Parameter.valueOf(0, getUnit().inverse());
178    }
179
180    /**
181     * Return the torsion of the curve at the parametric position <code>s</code>. The
182     * torsion is a measure of the rotation or twist about the tangent vector. Torsion for
183     * a line segment is always zero.
184     *
185     * @param s Parametric distance to calculate the torsion for (0.0 to 1.0 inclusive).
186     * @return The torsion of the curve at the specified parametric position in units of
187     *         1/Length.
188     */
189    @Override
190    public Parameter getTorsion(double s) {
191        validateParameter(s);
192        return Parameter.valueOf(0, getUnit().inverse());
193    }
194
195    /**
196     * Return the arc length of a segment of this curve.
197     *
198     * @param s1  The starting parametric distance along the curve to begin the arc-length
199     *            calculation from.
200     * @param s2  The ending parametric distance along the curve to end the arc-length
201     *            calculation at.
202     * @param eps The desired fractional accuracy on the arc-length. For this curve type,
203     *            this parameter is ignored and the exact arc length is returned.
204     * @return The arc length of the specified segment of the curve.
205     */
206    @Override
207    public Parameter<Length> getArcLength(double s1, double s2, double eps) {
208        validateParameter(s1);
209        validateParameter(s2);
210
211        return getArcLength(0).times(MathLib.abs(s2 - s1));
212    }
213
214    /**
215     * Return a subrange point at the position on the curve with the specified arc-length.
216     * If the requested arc-length is &le; 0, the start of the curve is returned. If the
217     * requested arc length is &gt; the total arc length of the curve, then the end point
218     * is returned.
219     *
220     * @param targetArcLength The target arc length to find in meters. May not be null.
221     * @param tol             Fractional tolerance (in parameter space) to refine the
222     *                        point position to. For this curve, this parameter is ignored
223     *                        and the exact point position is always returned.
224     * @return A subrange point on the curve at the specified arc-length position.
225     */
226    @Override
227    public SubrangePoint getPointAtArcLength(Parameter<Length> targetArcLength, double tol) {
228        //  Deal with an obvious case first.
229        if (targetArcLength.isApproxZero())
230            return SubrangePoint.newInstance(this, Point.valueOf(0));
231
232        double arcLengthValue = targetArcLength.getValue(SI.METER);
233
234        //  Calculate the full arc-length of the curve (tolerance is ignored).
235        double fullArcLength = getArcLength(0).getValue(SI.METER);
236
237        //  Is the requested arc length > than the actual arc length?
238        if (arcLengthValue > fullArcLength)
239            return SubrangePoint.newInstance(this, Point.valueOf(1));
240
241        //  The parametric position is the ratio of the desired arc-length to the full arc-length.
242        double s = arcLengthValue / fullArcLength;
243
244        return getPoint(s);
245    }
246
247    /**
248     * Return a string of points that are gridded onto the curve using the specified
249     * spacing and gridding rule.
250     *
251     * @param gridRule Specifies whether the subdivision spacing is applied with respect
252     *                 to arc-length in real space or parameter space. May not be null.
253     * @param spacing  A list of values used to define the subdivision spacing.
254     *                  <code>gridRule</code> specifies whether the subdivision spacing is
255     *                 applied with respect to arc-length in real space or parameter
256     *                 space. If the spacing is arc-length, then the values are
257     *                 interpreted as fractions of the arc length of the curve from 0 to
258     *                 1. May not be null.
259     * @return A string of subrange points gridded onto the curve at the specified
260     *         parametric positions.
261     */
262    @Override
263    public PointString<SubrangePoint> extractGrid(GridRule gridRule, List<Double> spacing) {
264
265        PointString<SubrangePoint> str = PointString.newInstance();
266
267        switch (gridRule) {
268            case PAR:
269                //  Parametric spacing.
270                for (double s : spacing) {
271                    if (s > 1)
272                        s = 1;
273                    else if (s < 0)
274                        s = 0;
275                    str.add(this.getPoint(s));
276                }
277                break;
278
279            case ARC:
280                //  Arc-length spacing.
281                Parameter<Length> fullArcLength = getArcLength(0);
282                for (double s : spacing) {
283                    SubrangePoint p = getPointAtArcLength(fullArcLength.times(s), 0);
284                    str.add(p);
285                }
286                break;
287
288            default:
289                throw new IllegalArgumentException(
290                        MessageFormat.format(RESOURCES.getString("crvUnsupportedGridRule"),
291                                gridRule.toString()));
292
293        }
294
295        return str;
296    }
297
298    /**
299     * Return a string of points that are gridded onto the curve with the number of points
300     * and spacing chosen to result in straight line segments between the points that have
301     * mid-points that are all within the specified tolerance of this curve.
302     *
303     * @param tol The maximum distance that a straight line between gridded points may
304     *            deviate from this curve. Ignored by this implementation.
305     * @return A string of subrange points gridded onto the curve.
306     */
307    @Override
308    public PointString<SubrangePoint> gridToTolerance(Parameter<Length> tol) {
309
310        //  Create the list of points.
311        PointString str = PointString.newInstance();
312        str.add(getPoint(0));
313        str.add(getPoint(1));
314
315        return str;
316    }
317
318    /**
319     * Return the intersection between a plane and this curve.
320     *
321     * @param plane The plane to intersect with this curve. May not be null.
322     * @param tol   Fractional tolerance (in parameter space) to refine the point
323     *              positions to. For this curve, this parameter is ignored and the exact
324     *              point positions are returned.
325     * @return A PointString containing zero or one subrange points made by the
326     *         intersection of this curve with the specified plane. If no intersection is
327     *         found, an empty PointString is returned.
328     */
329    @Override
330    public PointString<SubrangePoint> intersect(GeomPlane plane, double tol) {
331        requireNonNull(plane);
332        
333        //  Intersect the plane with an infinite line.
334        MutablePoint p = MutablePoint.newInstance(getPhyDimension(), getUnit());
335        IntersectType type = GeomUtil.linePlaneIntersect(getStart(), getDerivativeVector(), plane, p);
336
337        //  Check for no intersection.
338        PointString<SubrangePoint> str = PointString.newInstance();
339        if (type == IntersectType.DISJOINT)
340            return str;
341
342        //  Determine the parametric position of the intersection point.
343        Parameter sDir = p.minus(getStart()).toGeomVector().dot(getDerivativeVector());
344        if (sDir.getValue() >= 0) {
345            //  The intersection is not at a negative parametric position.
346            double length = getArcLength(0).getValue();
347            double pp0_dist = p.distance(getStart()).getValue();
348            if (pp0_dist <= length) {
349                //  The intersection is not beyond the end of the line.
350                double s = pp0_dist / length;
351                str.add(getPoint(s));
352            }
353        }
354
355        return str;
356    }
357
358    /**
359     * Return the intersection between a line segment and this curve.
360     *
361     * @param line The line segment to intersect. May not be null.
362     * @param tol  Tolerance (in physical space) to refine the point positions to. A null
363     *             value indicates an exact intersection is required.
364     * @return A list containing two PointString instances each containing zero or 1
365     *         subrange points, on this line segment and the input line segment
366     *         respectively, made by the intersection of this line segment with the
367     *         specified line segment. If no intersections are found a list of two empty
368     *         PointStrings are returned.
369     */
370    @Override
371    public GeomList<PointString<SubrangePoint>> intersect(LineSegment line, Parameter<Length> tol) {
372        //  This is simply a single line-line intersection problem.
373
374        //  Extract the start point and tangent vector of the two lines.
375        GeomPoint p1 = this.getStart();
376        GeomVector<Length> t1 = this.getDerivativeVector();
377        GeomPoint p2 = line.getStart();
378        GeomVector<Length> t2 = line.getDerivativeVector();
379
380        //  Do the line-line intersection problem.
381        MutablePoint sOut = MutablePoint.newInstance(2);
382        IntersectType type = GeomUtil.lineLineIntersect(p1, t1, p2, t2, tol, sOut, null, null);
383
384        //  Collect the outputs.
385        GeomList<PointString<SubrangePoint>> output = GeomList.newInstance();
386        output.add(PointString.newInstance());
387        output.add(PointString.newInstance());
388        if (type == IntersectType.DISJOINT) {
389            MutablePoint.recycle(sOut);
390            return output;
391        }
392
393        output.get(0).add(getPoint(sOut.getValue(0)));
394        output.get(1).add(getPoint(sOut.getValue(1)));
395        MutablePoint.recycle(sOut);
396
397        return output;
398    }
399
400    /**
401     * Return a list of brackets that surround possible closest/farthest points. Each of
402     * the brackets will be refined using a root solver in order to find the actual
403     * closest or farthest points.
404     * <p>
405     * This implementation sub-divides the curve into 2 equal-sized segments and looks for
406     * a single bracket inside each segment.
407     * </p>
408     *
409     * @param point   The point to find the closest/farthest point on this curve to/from
410     *                (guaranteed to be the same physical dimension before this method is
411     *                called). Ignored by this implementation.
412     * @param closest Set to <code>true</code> to return the closest point or
413     *                <code>false</code> to return the farthest point. Ignored by this
414     *                implementation.
415     * @param eval    A function that calculates the dot product of (p(s)-q) and ps(s) on
416     *                the curve. May not be null.
417     * @return A list of brackets that each surround a potential closest/farthest point.
418     */
419    @Override
420    protected List<BracketRoot1D> guessClosestFarthest(GeomPoint point, boolean closest,
421            Evaluatable1D eval) {
422
423        //  Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and
424        //  bracketing any roots that occur in any of those segments.
425        List<BracketRoot1D> brackets;
426        try {
427
428            brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, 2);
429
430        } catch (RootException err) {
431            Logger.getLogger(LineSegment.class.getName()).log(Level.WARNING,
432                    "Failed to bracket closest/farthest point.", err);
433            return FastTable.newInstance();
434        }
435
436        return brackets;
437    }
438
439    /**
440     * Return a list of brackets that surround possible points on this curve that are
441     * closest to the target geometry object. Each of the brackets will be refined using a
442     * root solver in order to find the actual closest points.
443     * <p>
444     * This implementation sub-divides the curve into 2 equal-sized segments and looks for
445     * a single bracket inside each segment.
446     * </p>
447     *
448     * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on the
449     *             curve where "q" is the closest point on the target geometry object.
450     *             May not be null.
451     * @return A list of brackets that each surround a potential closest point on this
452     *         curve.
453     */
454    @Override
455    protected List<BracketRoot1D> guessClosest(PerpPointPlaneEvaluatable eval) {
456
457        //  Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and
458        //  bracketing any roots that occur in any of those segments.
459        List<BracketRoot1D> brackets;
460        try {
461
462            brackets = BracketRoot1D.findBrackets(eval, 0, 1, 2);
463
464        } catch (RootException err) {
465            Logger.getLogger(LineSegment.class.getName()).log(Level.WARNING,
466                    "Failed to bracket closest point.", err);
467            return FastTable.newInstance();
468        }
469
470        return brackets;
471    }
472
473    /**
474     * Returns the most extreme point, either minimum or maximum, in the specified
475     * coordinate direction on this curve.
476     *
477     * @param dim An index indicating the dimension to find the min/max point for (0=X,
478     *            1=Y, 2=Z, etc).
479     * @param max Set to <code>true</code> to return the maximum value, <code>false</code>
480     *            to return the minimum.
481     * @param tol Fractional tolerance (in parameter space) to refine the min/max point
482     *            position to.
483     * @return The point found on this curve that is the min or max in the specified
484     *         coordinate direction.
485     */
486    @Override
487    public SubrangePoint getLimitPoint(int dim, boolean max, double tol) {
488        //  Check the input dimension for consistancy.
489        int thisDim = getPhyDimension();
490        if (dim < 0 || dim >= thisDim)
491            throw new DimensionException(MessageFormat.format(
492                    RESOURCES.getString("inputDimOutOfRange"), "line"));
493
494        //  Get the start and end points.
495        GeomPoint p0 = getStart();
496        GeomPoint p1 = getEnd();
497
498        //  Limit point must always be one of the ends of a line segment.
499        if (max) {
500            if (p0.get(dim).isGreaterThan(p1.get(dim)))
501                return getPoint(0);     //  p0
502            else
503                return getPoint(1);     //  p1
504
505        }
506        //  else min
507        if (p0.get(dim).isLessThan(p1.get(dim)))
508            return getPoint(0);     //  p0
509
510        return getPoint(1);         //  p1
511    }
512
513    /**
514     * Return the number of points per segment that should be used when analyzing curve
515     * segments by certain methods. Subclasses should override this method to provide a
516     * more appropriate number of points per segment.
517     *<p>
518     * This implementation always returns 1.
519     *</p>
520     * 
521     * @return Always returns 1.
522     */
523    @Override
524    protected int getNumPointsPerSegment() {
525        return 1;
526    }
527
528    /**
529     * Return <code>true</code> if this LineSegment contains valid and finite numerical
530     * components. A value of <code>false</code> will be returned if any of the coordinate
531     * values are NaN or Inf.
532     *
533     * @return true if this line segment contains valid and finite data.
534     */
535    @Override
536    public boolean isValid() {
537        return getStart().isValid() && getEnd().isValid();
538    }
539
540    /**
541     * Returns <code>true</code> if this curve is a line to within the specified
542     * tolerance. This implementation always returns <code>true</code> if the line is not
543     * degenerate.
544     *
545     * @param tol The tolerance for determining if this curve is a line. May not be null.
546     * @return Always returns <code>true</code> if the line is not degenerate.
547     */
548    @Override
549    public boolean isLine(Parameter<Length> tol) {
550        return !isDegenerate(tol);
551    }
552
553    /**
554     * Return <code>true</code> if this curve is planar or <code>false</code> if it is
555     * not. This implementation always returns <code>true</code>
556     *
557     * @param tol The geometric tolerance to use in determining if the curve is planar.
558     *            Ignored by this implementation.
559     *
560     * @return Always returns true.
561     */
562    @Override
563    public boolean isPlanar(Parameter<Length> tol) {
564        return true;
565    }
566
567    /**
568     * Returns <code>true</code> if this curve is a circular arc to within the specified
569     * tolerance. This implementation always returns <code>false</code>.
570     *
571     * @param tol The tolerance for determining if this curve is a circular arc.
572     *            Ignored by this implementation.
573     *
574     * @return Always returns false.
575     */
576    @Override
577    public boolean isCircular(Parameter<Length> tol) {
578        return false;
579    }
580
581    /**
582     * Returns transformed version of this element. The returned object implements
583     * {@link geomss.geom.GeomTransform} and contains this element as a child.
584     *
585     * @param transform The transformation to apply to this geometry. May not be null.
586     * @return A new line segment that is identical to this one with the specified
587     *         transformation applied.
588     * @throws DimensionException if this point is not 3D.
589     */
590    @Override
591    public LineSegTrans getTransformed(GTransform transform) {
592        return LineSegTrans.newInstance(this, requireNonNull(transform));
593    }
594
595    /**
596     * Returns the unit in which this curve is stated.
597     *
598     * @return The unit in which this curve is stated.
599     */
600    @Override
601    public Unit<Length> getUnit() {
602        return getStart().getUnit();
603    }
604
605    /**
606     * Return a NURBS curve representation of this line segment. An exact representation
607     * is returned and the tolerance parameter is ignored.
608     *
609     * @param tol Ignored. May pass <code>null</code>.
610     * @return A NURBS curve that exactly represents this line segment.
611     */
612    @Override
613    public NurbsCurve toNurbs(Parameter<Length> tol) {
614        NurbsCurve curve = CurveFactory.createLine(getStart(), getEnd());
615        copyState(curve);   //  Copy over the super-class state for this curve to the new one.
616        return curve;
617    }
618
619    /**
620     * Return a NURBS curve representation of this line segment. An exact representation
621     * is returned.
622     *
623     * @return A NURBS curve that exactly represents this line segment.
624     */
625    public NurbsCurve toNurbs() {
626        return toNurbs(null);
627    }
628
629    /**
630     * Returns the text representation of this geometry element that consists of the name
631     * followed by the start and end points. For example:
632     * <pre>
633     *   {aLine = {{0.0 m, 0.0 m},{1.0 , 0.0 }}
634     * </pre>
635     * If there is no name, then the output looks like this:
636     * <pre>
637     *   {{0.0 m, 0.0 m},{1.0 , 0.0 }}
638     * </pre>
639     *
640     * @return the text representation of this geometry element.
641     */
642    @Override
643    public Text toText() {
644        TextBuilder tmp = TextBuilder.newInstance();
645        tmp.append('{');
646        String nameStr = getName();
647        boolean hasName = nonNull(nameStr);
648        if (hasName) {
649            tmp.append(nameStr);
650            tmp.append(" = {");
651        }
652
653        tmp.append(getStart().toText());
654        tmp.append(",");
655        tmp.append(getEnd().toText());
656
657        if (hasName)
658            tmp.append('}');
659        tmp.append('}');
660        Text txt = tmp.toText();
661        TextBuilder.recycle(tmp);
662        return txt;
663    }
664
665    /**
666     * Returns the equivalent to this LineSegment object but stated in the specified
667     * unit.
668     *
669     * @param unit the length unit of the line segment to be returned. May not be null.
670     * @return an equivalent to this line segment object but stated in the specified
671     *         unit.
672     * @throws ConversionException if the the input unit is not a length unit.
673     */
674    @Override
675    public abstract LineSegment to(Unit<Length> unit) throws ConversionException;
676    
677}