001/**
002 * LoftedSurface -- A surface defined by a list of cross-section curves.
003 *
004 * Copyright (C) 2010-2025, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom;
019
020import geomss.geom.nurbs.*;
021import jahuwaldt.js.param.Parameter;
022import jahuwaldt.tools.math.MathTools;
023import java.text.MessageFormat;
024import java.util.*;
025import static java.util.Objects.nonNull;
026import static java.util.Objects.requireNonNull;
027import javax.measure.converter.ConversionException;
028import javax.measure.quantity.Length;
029import javax.measure.unit.SI;
030import javax.measure.unit.Unit;
031import javax.swing.event.ChangeListener;
032import javolution.context.ArrayFactory;
033import javolution.context.ObjectFactory;
034import javolution.lang.Immutable;
035import javolution.text.Text;
036import javolution.text.TextBuilder;
037import javolution.util.FastTable;
038import javolution.xml.XMLFormat;
039import javolution.xml.stream.XMLStreamException;
040
041/**
042 * Represents a "lofted" or "skinned" surface defined from a list of defining
043 * {@link Curve curves} that each define a cross-section of the surface. Each curve
044 * defines the local parameterization in the "s" direction and the spacing between them
045 * defines the parameterization in the "t" direction. Any number of curves may be added to
046 * a lofted surface, but all curves must have the same physical dimensions.
047 *
048 * <p> Modified by: Joseph A. Huwaldt </p>
049 *
050 * @author Joseph A. Huwaldt, Date: June 24, 2010
051 * @version February 16, 2025
052 */
053@SuppressWarnings({"serial", "CloneableImplementsClone"})
054public class LoftedSurface extends AbstractSurface<LoftedSurface> implements GeometryList<LoftedSurface,Curve> {
055
056    //  The list behind this implementation.
057    private FastTable<Curve> _crvs;
058
059    //  The degree of the surface in the "t" direction.
060    private int _q = 1;
061
062    /**
063     * Reference to a change listener for this object's child curves.
064     */
065    private final ChangeListener _childChangeListener = new ForwardingChangeListener(this);
066
067    /**
068     * Returns a new, empty, preallocated or recycled <code>LoftedSurface</code> instance
069     * (on the stack when executing in a <code>StackContext</code>), that can store a list
070     * of {@link Curve} objects. The list is initially empty and therefore the surface is
071     * initially undefined.
072     *
073     * @param q The degree of the surface across the defining curves (in the "t"
074     *          direction).
075     * @return A new empty LoftedSurface.
076     */
077    public static LoftedSurface newInstance(int q) {
078        return newInstance(null, q);
079    }
080
081    /**
082     * Returns a new, empty, preallocated or recycled <code>LoftedSurface</code> instance
083     * (on the stack when executing in a <code>StackContext</code>) with the specified
084     * name, that can store a list of {@link Curve} objects. The list is initially empty
085     * and therefore the surface is initially undefined.
086     *
087     * @param name The name to be assigned to this surface (may be <code>null</code>).
088     * @param q    The degree of the surface across the defining curves (in the "t"
089     *             direction).
090     * @return A new empty LoftedSurface.
091     */
092    public static LoftedSurface newInstance(String name, int q) {
093        if (q < 1)
094            throw new IllegalArgumentException(RESOURCES.getString("tDirDegreeErr"));
095        LoftedSurface list = FACTORY.object();
096        list._crvs = FastTable.newInstance();
097        list._q = q;
098        list.setName(name);
099        return list;
100    }
101
102    /**
103     * Return a LoftedSurface made up of the {@link Curve} objects in the specified
104     * collection.
105     *
106     * @param name   The name to be assigned to this surface (may be <code>null</code>).
107     * @param q      The degree of the surface across the defining curves (in the "t"
108     *               direction).
109     * @param curves A collection of curves that define the surface. May not be null.
110     * @return A new LoftedSurface made up of the curves in the specified collection.
111     */
112    public static LoftedSurface valueOf(String name, int q, Collection<? extends Curve> curves) {
113        requireNonNull(curves);
114        LoftedSurface list = LoftedSurface.newInstance(name, q);
115        list.addAll(curves);
116
117        return list;
118    }
119
120    /**
121     * Return a LoftedSurface made up of the {@link Curve} objects in the specified
122     * collection.
123     *
124     * @param q      The degree of the surface across the defining curves (in the "t"
125     *               direction).
126     * @param curves A collection of curves that define the surface. May not be null.
127     * @return A new LoftedSurface made up of the curves in the specified collection.
128     */
129    public static LoftedSurface valueOf(int q, Collection<? extends Curve> curves) {
130
131        LoftedSurface list = LoftedSurface.valueOf(null, q, curves);
132
133        return list;
134    }
135
136    /**
137     * Return a LoftedSurface made up of the {@link Curve} objects in the specified array.
138     *
139     * @param name   The name to be assigned to this surface (may be <code>null</code>).
140     * @param q      The degree of the surface across the defining curves (in the "t"
141     *               direction).
142     * @param curves An array of curves that define the surface. May not be null.
143     * @return A new LoftedSurface made up of the curves in the specified array.
144     */
145    public static LoftedSurface valueOf(String name, int q, Curve... curves) {
146        requireNonNull(curves);
147        LoftedSurface list = LoftedSurface.newInstance(name, q);
148        list.addAll(Arrays.asList(curves));
149
150        return list;
151    }
152
153    /**
154     * Return a LoftedSurface made up of the {@link Curve} objects in the specified array.
155     *
156     * @param q      The degree of the surface across the defining curves (in the "t"
157     *               direction).
158     * @param curves An array of curves that define the surface. May not be null.
159     * @return A new LoftedSurface made up of the curves in the specified array.
160     */
161    public static LoftedSurface valueOf(int q, Curve... curves) {
162        return LoftedSurface.valueOf(null, q, curves);
163    }
164
165    /**
166     * Return the degree of the surface in the t-direction (across the defining curves).
167     *
168     * @return degree of surface in t-direction
169     */
170    public int getTDegree() {
171        return _q;
172    }
173
174    /**
175     * Validate that the surface is properly formed, otherwise throw an exception.
176     */
177    private void validateSurface() {
178        if (size() < _q + 1)
179            throw new IllegalArgumentException(MessageFormat.format(
180                    RESOURCES.getString("incDefiningCrvCount"), "LoftedSurface", _q + 1, size()));
181    }
182
183    /**
184     * Calculate a point on the surface for the given parametric position on the surface.
185     *
186     * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0
187     *          inclusive).
188     * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0
189     *          inclusive).
190     * @return The calculated point on the surface at the specified parameter values.
191     * @throws IllegalArgumentException if there is any problem with the parameter values.
192     */
193    @Override
194    public Point getRealPoint(double s, double t) {
195        validateSurface();
196        validateParameter(s, t);
197
198        //  Fit a curve through the points on each defining curve at "s".
199        BasicNurbsCurve tCrv = createTCurve(s);
200
201        //  Now get the point at the desired "t" position.
202        Point p = tCrv.getRealPoint(t);
203        BasicNurbsCurve.recycle(tCrv);
204
205        return p;
206    }
207
208    /**
209     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
210     * respect to parametric s-position on the surface for the given parametric position
211     * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>.
212     * <p>
213     * Example:<br>
214     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br>
215     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc.
216     * </p>
217     *
218     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
219     *               1.0 inclusive).
220     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
221     *               1.0 inclusive).
222     * @param grade  The maximum grade to calculate the u-derivatives for (1=1st
223     *               derivative, 2=2nd derivative, etc)
224     * @param scaled Pass <code>true</code> for properly scaled derivatives or
225     *               <code>false</code> if the magnitude of the derivative vector is not
226     *               required -- this sometimes results in faster calculation times.
227     * @return A list of s-derivatives up to the specified grade of the surface at the
228     *         specified parametric position.
229     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
230     * invalid.
231     */
232    @Override
233    public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) {
234        validateSurface();
235        validateParameter(s, t);
236        if (grade < 0)
237            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
238
239        //  Create a list of point strings, each string is for a particular grade.
240        FastTable<PointString> sDers = FastTable.newInstance();
241        for (int i = 0; i <= grade; ++i)
242            sDers.add(PointString.newInstance());
243
244        //  Get the derivatives on each of the defining curves at the input "s" position.
245        int size = _crvs.size();
246        for (int j = 0; j < size; ++j) {
247            Curve crv = _crvs.get(j);
248            List<Vector<Length>> ders = crv.getSDerivatives(s, grade);
249            for (int i = 0; i <= grade; ++i) {
250                Vector<Length> der = ders.get(i);
251                sDers.get(i).add(Point.valueOf(der));
252            }
253        }
254        
255        //  Remove any derivative points that are approximately equal.
256        FastTable<PointString> sDers2 = FastTable.newInstance();
257        for (int i=0; i <= grade; ++i) {
258            PointString<GeomPoint> ders = sDers.get(i);
259            ders = ders.unique(Parameter.valueOf(MathTools.SQRT_EPS, SI.METER));
260            sDers2.add(ders);
261        }
262        FastTable.recycle(sDers);
263        sDers = sDers2;
264        
265        //  Create an array of output derivatives.
266        FastTable<Vector<Length>> ders = FastTable.newInstance();
267
268        //  Fit curves to each derivative string and find the derivative at the input "t" position.
269        Point origin = this.getRealPoint(s, t);
270        for (int i = 0; i <= grade; ++i) {
271            PointString<GeomPoint> sDers_i = sDers.get(i);
272            Vector<Length> der;
273            if (sDers_i.size() > 1) {
274                // There are different derivatives for each section curve.
275                // Fit a curve of appropriate degree to those derivatives and interpolate the derivative at "t".
276                int q = _q;
277                if (sDers_i.size() <= q)
278                    q = sDers_i.size() - 1;
279                BasicNurbsCurve tCrv = CurveFactory.fitPoints(q, sDers_i);
280                der = tCrv.getRealPoint(t).toGeomVector();
281            } else {
282                //  There is only a single derivative in this direction across all the section curves.  Use it.
283                der = sDers_i.get(0).toGeomVector();
284            }
285            der.setOrigin(origin);
286            ders.add(der);
287        }
288
289        return ders;
290    }
291    
292    /**
293     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
294     * respect to parametric t-position on the surface for the given parametric position
295     * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>.
296     * <p>
297     * Example:<br>
298     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br>
299     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc.
300     * </p>
301     *
302     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
303     *               1.0 inclusive).
304     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
305     *               1.0 inclusive).
306     * @param grade  The maximum grade to calculate the v-derivatives for (1=1st
307     *               derivative, 2=2nd derivative, etc)
308     * @param scaled Pass <code>true</code> for properly scaled derivatives or
309     *               <code>false</code> if the magnitude of the derivative vector is not
310     *               required -- this sometimes results in faster calculation times.
311     * @return A list of t-derivatives up to the specified grade of the surface at the
312     *         specified parametric position.
313     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
314     * invalid.
315     */
316    @Override
317    public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) {
318        validateSurface();
319        validateParameter(s, t);
320        if (grade < 0)
321            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
322
323        //  Fit a curve through the points on each defining curve at "s".
324        BasicNurbsCurve tCrv = createTCurve(s);
325
326        //  Get the derivatives on the t-curve at the input "t" position.
327        Point origin = this.getRealPoint(s, t);
328        List<Vector<Length>> ders = tCrv.getSDerivatives(t, grade);
329        int size = ders.size();
330        for (int i = 0; i < size; ++i) {
331            Vector<Length> v = ders.get(i);
332            v.setOrigin(origin);
333        }
334
335        return ders;
336    }
337
338    /**
339     * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the
340     * specified position on this surface.
341     *
342     * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to
343     *          1.0 inclusive).
344     * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to
345     *          1.0 inclusive).
346     * @return The twist vector of this surface at the specified parametric position.
347     * @throws IllegalArgumentException if the parameter values are invalid.
348     */
349    @Override
350    public Vector<Length> getTwistVector(double s, double t) {
351        validateSurface();
352        validateParameter(s, t);
353
354        //  Create a point string to hold the derivatives in the s-direction on each defining curve.
355        PointString<Point> sDers = PointString.newInstance();
356
357        //  Get the s-direction derivatives on each of the defining curves at the input "s" position.
358        int size = _crvs.size();
359        for (int i = 0; i < size; ++i) {
360            Curve crv = _crvs.get(i);
361            Vector der = crv.getSDerivative(s, 1);
362            sDers.add(Point.valueOf(der));
363        }
364
365        //  Fit a curve to the derivative string and find the t-direction derivative at the input "t" position.
366        BasicNurbsCurve tCrv = CurveFactory.fitPoints(_q, sDers);
367        Vector<Length> tvec = tCrv.getSDerivative(t, 1);
368        Point origin = this.getRealPoint(s, t);
369        tvec.setOrigin(origin);
370
371        //  Cleanup
372        BasicNurbsCurve.recycle(tCrv);
373        PointString.recycle(sDers);
374
375        return tvec;
376    }
377
378    /**
379     * Return the T=0 Boundary for this surface as a curve.
380     *
381     * @return The T=0 Boundary for this surface as a curve.
382     */
383    @Override
384    public Curve getT0Curve() {
385        return _crvs.get(0);
386    }
387
388    /**
389     * Return the T=1 Boundary for this surface as a curve.
390     *
391     * @return The T=1 Boundary for this surface as a curve.
392     */
393    @Override
394    public Curve getT1Curve() {
395        return _crvs.get(_crvs.size() - 1);
396    }
397
398    /**
399     * Return the S=0 Boundary for this surface as a curve.
400     *
401     * @return The S=0 Boundary for this surface as a curve.
402     */
403    @Override
404    public Curve getS0Curve() {
405        return createTCurve(0);
406    }
407
408    /**
409     * Return the S=1 Boundary for this surface as a curve.
410     *
411     * @return The S=1 Boundary for this surface as a curve.
412     */
413    @Override
414    public Curve getS1Curve() {
415        return createTCurve(1);
416    }
417
418    /**
419     * Create and return a curve, across the defining curves, at the specified "s"
420     * position.
421     */
422    private BasicNurbsCurve createTCurve(double s) {
423
424        //  Get the points on each of the defining curves at the input "s" position.
425        PointString<Point> sPnts = PointString.newInstance();
426        int size = _crvs.size();
427        for (int i = 0; i < size; ++i) {
428            Curve crv = _crvs.get(i);
429            sPnts.add(crv.getRealPoint(s));
430        }
431
432        //  Fit a curve through the points on each defining curve.
433        BasicNurbsCurve tCrv = CurveFactory.fitPoints(_q, sPnts);
434        PointString.recycle(sPnts);
435
436        return tCrv;
437    }
438
439    /**
440     * Return the parameterizations of each defining section in T at the specified "s"
441     * value. The returned array was created using ArrayFactor.DOUBLES_FACTORY and can be
442     * recycled.
443     */
444    private double[] sectionParams(double s) {
445
446        //  Get the points on each of the defining curves at the input "s" position.
447        PointString sPnts = PointString.newInstance();
448        int size = _crvs.size();
449        for (int i = 0; i < size; ++i) {
450            Curve crv = _crvs.get(i);
451            sPnts.add(crv.getRealPoint(s));
452        }
453
454        //  Parameterize the string of points.
455        double[] params = CurveFactory.parameterizeString(sPnts);
456        PointString.recycle(sPnts);
457
458        return params;
459    }
460
461    /**
462     * Returns the number of physical dimensions of the geometry element. This
463     * implementation always returns the physical dimension of the underlying
464     * {@link Curve} objects or 0 if this list has no Curve objects in it.
465     *
466     * @return The number of physical dimensions of the geometry element.
467     */
468    @Override
469    public int getPhyDimension() {
470        if (isEmpty())
471            return 0;
472        return get(0).getPhyDimension();
473    }
474
475    /**
476     * Method that guesses the most likely location for the closest or farthest point on a
477     * surface and returns that location as a 2D point containing the "s" and "t"
478     * parameter values. This is called by getClosest() and getFarthest(). This is
479     * required in order for the root-finding algorithm to reliably refine the closest
480     * point to the correct location.
481     * <p>
482     * This implementation finds the closest/farthest point on each member curve and
483     * returns the parametric position of the closest of those.
484     * </p>
485     *
486     * @param point   The point to find the closest point on this surface to. May not be
487     *                null.
488     * @param closest Set to <code>true</code> to return the closest point or
489     *                <code>false</code> to return the farthest point.
490     * @return The 2D parametric point on this surface that is closest to the specified
491     *         point.
492     */
493    @Override
494    protected GeomPoint guessClosestFarthest(GeomPoint point, boolean closest) {
495        requireNonNull(point);
496        if (size() <= 3)
497            return super.guessClosestFarthest(point, closest);
498
499        //  Find the closest/furthest point on each member curve to get the parametric s-position.
500        SubrangePoint closestPnt = get(0).getClosest(point, GTOL);
501        double dOpt = point.distanceValue(closestPnt);
502        int size = size();
503        int crvIdx = 0;
504        for (int i = 1; i < size; ++i) {
505            SubrangePoint p = get(i).getClosest(point, GTOL);
506            double dist = point.distanceValue(p);
507            if (closest) {
508                if (dist < dOpt) {
509                    dOpt = dist;
510                    closestPnt = p;
511                    crvIdx = i;
512                }
513            } else {
514                if (dist > dOpt) {
515                    dOpt = dist;
516                    closestPnt = p;
517                    crvIdx = i;
518                }
519            }
520        }
521        double s = closestPnt.getParPosition().getValue(0);
522
523        //  Calculate the parametric t-position of the closest defining section.
524        FastTable<Double> tmp = FastTable.newInstance();
525        Point pm1 = this.get(0).getRealPoint(s);
526        double d = 0;
527        for (int i = 1; i <= crvIdx; ++i) {
528            Point p = this.get(i).getRealPoint(s);
529            double distV = p.distanceValue(pm1);
530            d += distV;
531            tmp.add(distV);
532            pm1 = p;
533        }
534
535        double t = 0;
536        for (int i = 0; i < crvIdx; ++i) {
537            t += tmp.get(i) / d;
538        }
539
540        return Point.valueOf(s, t);
541    }
542
543    /**
544     * Return the input index normalized into the range 0 &le; index &lt; size(). This
545     * allows negative indexing (-1 referring to the last element in the list), but does
546     * not allow wrap-around positive indexing.
547     */
548    private int normalizeIndex(int index) {
549        int size = size();
550        while (index < 0)
551            index += size;
552        return index;
553    }
554
555    /**
556     * Returns an unmodifiable list view of the curves in this surface. Attempts to modify
557     * the returned collection result in an UnsupportedOperationException being thrown.
558     *
559     * @return the unmodifiable view over this list of curves.
560     */
561    @Override
562    public List<Curve> unmodifiableList() {
563        return _crvs.unmodifiable();
564    }
565
566    /**
567     * Returns <code>true</code> if this collection contains no elements.
568     */
569    @Override
570    public boolean isEmpty() {
571        return _crvs.isEmpty();
572    }
573
574    /**
575     * Returns <code>true</code> if this list actually contains any curves and
576     * <code>false</code> if this list is empty.
577     *
578     * @return true if this list actually contains geometry.
579     */
580    @Override
581    public boolean containsGeometry() {
582        return !isEmpty();
583    }
584
585    /**
586     * Returns the number of elements in this surface (the number of defining curves that
587     * make up this surface). If the surface contains more than Integer.MAX_VALUE
588     * elements, returns Integer.MAX_VALUE.
589     *
590     * @return the number of elements in this list of curves.
591     */
592    @Override
593    public int size() {
594        return _crvs.size();
595    }
596
597    /**
598     * Returns the Curve at the specified position in this surface's list of curves.
599     *
600     * @param index index of element to return (0 returns the 1st element, -1 returns the
601     *              last, -2 returns the 2nd from last, etc).
602     * @return the Curve at the specified position in this surface.
603     * @throws IndexOutOfBoundsException if the given index is out of range:
604     * <code>index &gt; size()</code>
605     */
606    @Override
607    public Curve get(int index) {
608        index = normalizeIndex(index);
609        return _crvs.get(index);
610    }
611
612    /**
613     * Returns the first curve from this surface's list of curves.
614     *
615     * @return the first curve in this list.
616     */
617    @Override
618    public Curve getFirst() {
619        return get(0);
620    }
621
622    /**
623     * Returns the last curve from this surface's list of curves.
624     *
625     * @return the last curve in this list.
626     */
627    @Override
628    public Curve getLast() {
629        return get(size() - 1);
630    }
631
632    /**
633     * Returns the range of Curves in this surface from the specified start and ending
634     * indexes as a new LoftedSurface.
635     *
636     * @param first index of the first element to return (0 returns the 1st element, -1
637     *              returns the last, etc).
638     * @param last  index of the last element to return (0 returns the 1st element, -1
639     *              returns the last, etc).
640     * @return A LoftedSurface made up of the curves in the given range from this surface.
641     * @throws IndexOutOfBoundsException if the given index is out of range:
642     * <code>index &ge; size()</code>
643     */
644    @Override
645    public LoftedSurface getRange(int first, int last) {
646        first = normalizeIndex(first);
647        last = normalizeIndex(last);
648
649        LoftedSurface list = LoftedSurface.newInstance(getTDegree());
650        for (int i = first; i <= last; ++i)
651            list.add(get(i));
652
653        return list;
654    }
655
656    /**
657     * Returns the {@link Curve} with the specified name from this list.
658     *
659     * @param name The name of the curve we are looking for in the list.
660     * @return The curve matching the specified name. If the specified element name
661     *         isn't found in the list, then <code>null</code> is returned.
662     */
663    @Override
664    public Curve get(String name) {
665
666        Curve element = null;
667        int index = getIndexFromName(name);
668        if (index >= 0)
669            element = this.get(index);
670
671        return element;
672    }
673
674    /**
675     * Returns a view of the portion of this list between fromIndex, inclusive, and
676     * toIndex, exclusive. (If fromIndex and toIndex are equal, the returned list is
677     * empty.) The returned list is backed by this list, so changes in the returned list
678     * are reflected in this list, and vice-versa. The returned list supports all of the
679     * optional list operations supported by this list.
680     *
681     * This method eliminates the need for explicit range operations (of the sort that
682     * commonly exist for arrays). Any operation that expects a list can be used as a
683     * range operation by passing a subList view instead of a whole list. For example, the
684     * following idiom removes a range of values from a list: <code>
685     * list.subList(from, to).clear();</code> Similar idioms may be constructed for
686     * <code>indexOf</code> and <code>lastIndexOf</code>, and all of the algorithms in the
687     * <code>Collections</code> class can be applied to a subList.
688     *
689     * The semantics of the list returned by this method become undefined if the backing
690     * list (i.e., this list) is <i>structurally modified</i> in any way other than via
691     * the returned list (structural modifications are those that change the size of this
692     * list, or otherwise perturb it in such a fashion that iterations in progress may
693     * yield incorrect results).
694     *
695     * @param fromIndex low endpoint (inclusive) of the subList.
696     * @param toIndex   high endpoint (exclusive) of the subList.
697     * @return a view of the specified range within this list.
698     * @throws IndexOutOfBoundsException if the given index is out of range:
699     * <code>index &gt; size()</code>
700     */
701    @Override
702    public List<Curve> subList(int fromIndex, int toIndex) {
703        fromIndex = normalizeIndex(fromIndex);
704        toIndex = normalizeIndex(toIndex);
705        return _crvs.subList(fromIndex, toIndex);
706    }
707
708    /**
709     * Returns an new {@link GeomList} with the elements in this list.
710     *
711     * @return A new GeomList with the elements in this list.
712     */
713    @Override
714    public GeomList<Curve> getAll() {
715        GeomList<Curve> list = GeomList.newInstance();
716        list.addAll(this);
717        return list;
718    }
719
720    /**
721     * Returns an new {@link LoftedSurface} with the curves in this surface in reverse
722     * order. This is identical to "reverseT()".
723     *
724     * @return A new LoftedSurface with the curves in this surface in reverse order.
725     * @see #reverseT
726     */
727    @Override
728    public LoftedSurface reverse() {
729        LoftedSurface list = LoftedSurface.newInstance(_q);
730        copyState(list);
731        int size = this.size();
732        for (int i = size - 1; i >= 0; --i) {
733            list.add(get(i));
734        }
735        return list;
736    }
737
738    /**
739     * Returns the index in this list of the first occurrence of the specified element, or
740     * -1 if the list does not contain this element.
741     *
742     * @param element The element to search for.
743     * @return the index in this List of the first occurrence of the specified element, or
744     *         -1 if the List does not contain this element.
745     */
746    @Override
747    public int indexOf(Object element) {
748        return _crvs.indexOf(element);
749    }
750
751    /**
752     * Returns the index in this list of the last occurrence of the specified element, or
753     * -1 if the list does not contain this element. More formally, returns the highest
754     * index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no
755     * such index.
756     *
757     * @param element The element to search for.
758     * @return the index in this list of the last occurrence of the specified element, or
759     *         -1 if the list does not contain this element.
760     */
761    @Override
762    public int lastIndexOf(Object element) {
763        return _crvs.lastIndexOf(element);
764    }
765
766    /**
767     * Returns true if this collection contains the specified element. More formally,
768     * returns true if and only if this collection contains at least one element e such
769     * that (o==null ? e==null : o.equals(e)).
770     *
771     * @param o object to be checked for containment in this collection.
772     * @return <code>true</code> if this collection contains the specified element.
773     */
774    @Override
775    public boolean contains(Object o) {
776        return _crvs.contains(o);
777    }
778
779    /**
780     * Returns true if this collection contains all of the elements in the specified
781     * collection.
782     *
783     * @param c collection to be checked for containment in this collection.
784     * @return <code>true</code> if this collection contains all of the elements in the
785     *         specified collection.
786     */
787    @Override
788    public boolean containsAll(Collection<?> c) {
789        return _crvs.containsAll(c);
790    }
791
792    /**
793     * Return the index to the 1st Curve in this list with the specified name.
794     *
795     * @param name The name of the Curve to find in this list
796     * @return The index to the named Curve or -1 if it is not found.
797     */
798    @Override
799    public int getIndexFromName(String name) {
800        if (name == null)
801            return -1;
802
803        int result = -1;
804        int size = this.size();
805        for (int i = 0; i < size; ++i) {
806            GeomElement element = this.get(i);
807            String eName = element.getName();
808            if (name.equals(eName)) {
809                result = i;
810                break;
811            }
812        }
813        
814        return result;
815    }
816
817    /**
818     * Inserts the specified {@link Curve} at the specified position in this list. Shifts
819     * the element currently at that position (if any) and any subsequent elements to the
820     * right (adds one to their indices). Null values are ignored. The input value must
821     * have the same physical dimensions as the other items in this list, or an exception
822     * is thrown.
823     * <p>
824     * Note: If this method is used concurrent access must be synchronized (the table is
825     * not thread-safe).
826     * </p>
827     *
828     * @param index the index at which the specified element is to be inserted. (0 returns
829     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
830     *              etc).
831     * @param value the element to be inserted. May not be null.
832     * @throws IndexOutOfBoundsException if <code>index &gt; size()</code>
833     * @throws DimensionException if the input value dimensions are different from this
834     * list's dimensions.
835     */
836    @Override
837    @SuppressWarnings("null")
838    public void add(int index, Curve value) {
839        if (!isEmpty() && value.getPhyDimension() != getPhyDimension())
840            throw new DimensionException(MessageFormat.format(
841                    RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension()));
842        index = normalizeIndex(index);
843
844        _crvs.add(index, value);
845        if (!(value instanceof Immutable) && _childChangeListener != null)
846            value.addChangeListener(_childChangeListener);
847
848        fireChangeEvent();  //  Notify change listeners.
849    }
850
851    /**
852     * Appends the specified {@link Curve} to the end of this surface's list of curves.
853     * Null values are ignored. The input value must have the same physical dimensions as
854     * the other items in this list, or an exception is thrown.
855     * <p>
856     * Note: If this method is used concurrent access must be synchronized (the table is
857     * not thread-safe).
858     * </p>
859     *
860     * @param value the curve to be appended to this list. May not be null.
861     * @throws DimensionException if the input element's dimensions are different from
862     * this list's dimensions.
863     */
864    @Override
865    public boolean add(Curve value) {
866        add(size(), value);
867        return true;
868    }
869
870    /**
871     * Appends all of the elements in the specified list of arguments to this geometry
872     * element list. The input values must have the same physical dimensions as the other
873     * items in this list, or an exception is thrown.
874     *
875     * @param array elements to be inserted into this collection. May not be null.
876     * @return <code>true</code> if this collection changed as a result of the call.
877     * @throws DimensionException if the input element's dimensions are different from
878     * this list's dimensions.
879     */
880    @Override
881    public boolean add(Curve... array) {
882        return add(size(), array);
883    }
884
885    /**
886     * Inserts all of the {@link Curve} objects in the specified list of arguments into
887     * this list at the specified position. Shifts the element currently at that position
888     * (if any) and any subsequent elements to the right (increases their indices). The
889     * new elements will appear in this list in the order that they are appeared in the
890     * array. The input values must have the same physical dimensions as the other items
891     * in this list, or an exception is thrown.
892     *
893     * @param index index at which to insert first element from the specified array.
894     * @param array elements to be inserted into this collection. May not be null.
895     * @return <code>true</code> if this collection changed as a result of the call.
896     * @throws DimensionException if the input element's dimensions are different from
897     * this list's dimensions.
898     */
899    @Override
900    public boolean add(int index, Curve... array) {
901        return addAll(index, Arrays.asList(requireNonNull(array)));
902    }
903
904    /**
905     * Adds all of the curves in the specified collection to this surface. The behavior of
906     * this operation is undefined if the specified collection is modified while the
907     * operation is in progress. This implies that the behavior of this call is undefined
908     * if the specified collection is this collection, and this collection is nonempty.
909     * The input elements must have the same physical dimensions as the other items in
910     * this list, or an exception is thrown.
911     *
912     * @param c curves to be inserted into this surface. May not be null.
913     * @return <code>true</code> if this surface changed as a result of the call
914     * @throws DimensionException if the input element's dimensions are different from
915     * this list's dimensions.
916     */
917    @Override
918    public boolean addAll(Collection<? extends Curve> c) {
919        return addAll(size(), c);
920    }
921
922    /**
923     * Inserts all of the curves in the specified collection into this surface at the
924     * specified position. Shifts the curve currently at that position (if any) and any
925     * subsequent curves to the right (increases their indices). The new curves will
926     * appear in this list in the order that they are returned by the specified
927     * collection's iterator. The behavior of this operation is unspecified if the
928     * specified collection is modified while the operation is in progress. Note that
929     * this will occur if the specified collection is this list, and it's nonempty.
930     * The input elements must have the same physical dimensions as the other items in
931     * this list, or an exception is thrown.
932     *
933     * @param index index at which to insert first curve from the specified collection.
934     * @param c     curves to be inserted into this collection. May not be null.
935     * @return <code>true</code> if this surface changed as a result of the call
936     * @throws DimensionException if the input element's dimensions are different from
937     * this list's dimensions.
938     */
939    @Override
940    @SuppressWarnings("null")
941    public boolean addAll(int index, Collection<? extends Curve> c) {
942        requireNonNull(c);
943        if (!isEmpty()) {
944            int dim = getPhyDimension();
945            for (Curve crv : c) {
946                requireNonNull(crv, RESOURCES.getString("collectionElementsNullErr"));
947                if (crv.getPhyDimension() != dim)
948                    throw new DimensionException(MessageFormat.format(
949                            RESOURCES.getString("incCrvDimension"),"curves", getPhyDimension()));
950            }
951        }
952        index = normalizeIndex(index);
953        boolean changed = _crvs.addAll(index, c);
954        if (changed) {
955            for (Curve crv : c) {
956                if (!(crv instanceof Immutable) && _childChangeListener != null)
957                    crv.addChangeListener(_childChangeListener);
958            }
959            fireChangeEvent();  //  Notify change listeners.
960        }
961        return changed;
962    }
963
964    /**
965     * Appends all of the curves in the specified array to this LoftedSurface. The
966     * behavior of this operation is undefined if the specified collection is modified
967     * while the operation is in progress. The input array elements must have the same
968     * physical dimensions as the other items in this list, or an exception is thrown.
969     *
970     * @param arr curves to be appended onto this collection. May not be null.
971     * @return <code>true</code> if this collection changed as a result of the call.
972     * @throws DimensionException if the input element's dimensions are different from
973     * this list's dimensions.
974     */
975    @Override
976    public boolean addAll(Curve[] arr) {
977        return addAll(size(), arr);
978    }
979
980    /**
981     * Inserts all of the {@link Curve} objects in the specified array into this list at
982     * the specified position. Shifts the element currently at that position (if any) and
983     * any subsequent elements to the right (increases their indices). The new elements
984     * will appear in this list in the order that they are returned by the specified
985     * collection's iterator. The input array elements must have the same physical
986     * dimensions as the other items in this list, or an exception is thrown.
987     *
988     * @param index index at which to insert first element from the specified array.
989     * @param arr   elements to be inserted into this collection. May not be null.
990     * @return <code>true</code> if this collection changed as a result of the call.
991     * @throws DimensionException if the input element's dimensions are different from
992     * this list's dimensions.
993     */
994    @Override
995    public boolean addAll(int index, Curve[] arr) {
996        return addAll(index, Arrays.asList(requireNonNull(arr)));
997    }
998
999    /**
1000     * Replaces the Curve at the specified position in this surface's list of
1001     * curves with the specified element. Null elements are ignored. The input element
1002     * must have the same physical dimensions as the other items in this list, or an
1003     * exception is thrown.
1004     *
1005     * @param index   The index of the element to replace (0 returns the 1st element, -1
1006     *                returns the last, -2 returns the 2nd from last, etc).
1007     * @param element The curve to be stored at the specified position. May not be null.
1008     * @return The curve previously at the specified position in this list.
1009     * @throws java.lang.IndexOutOfBoundsException - if  <code>index &gt; size()</code>
1010     * @throws DimensionException if the input element's dimensions are different from
1011     * this list's dimensions.
1012     */
1013    @Override
1014    @SuppressWarnings("null")
1015    public Curve set(int index, Curve element) {
1016        if (!isEmpty() && element.getPhyDimension() != getPhyDimension())
1017            throw new DimensionException(MessageFormat.format(
1018                    RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension()));
1019        index = normalizeIndex(index);
1020
1021        Curve old = _crvs.set(index, element);
1022        if (!(old instanceof Immutable) && _childChangeListener != null)
1023            old.removeChangeListener(_childChangeListener);
1024        if (!(element instanceof Immutable) && _childChangeListener != null)
1025            element.addChangeListener(_childChangeListener);
1026
1027        fireChangeEvent();  //  Notify change listeners.
1028
1029        return old;
1030    }
1031
1032    /**
1033     * Removes from this list all the elements that are contained in the specified
1034     * collection.
1035     *
1036     * @param c collection that defines which elements will be removed from this list. May
1037     *          not be null.
1038     * @return <code>true</code> if this list changed as a result of the call.
1039     */
1040    @Override
1041    public boolean removeAll(Collection<?> c) {
1042        boolean modified = false;
1043        Iterator<?> it = iterator();
1044        while (it.hasNext()) {
1045            if (c.contains(it.next())) {
1046                it.remove();
1047                modified = true;
1048            }
1049        }
1050        return modified;
1051    }
1052
1053    /**
1054     * Retains only the elements in this list that are contained in the specified
1055     * collection. In other words, removes from this list all the elements that are not
1056     * contained in the specified collection.
1057     *
1058     * @param c collection that defines which elements this set will retain. May not be
1059     *          null.
1060     * @return <code>true</code> if this list changed as a result of the call.
1061     */
1062    @Override
1063    public boolean retainAll(Collection<?> c) {
1064        boolean modified = false;
1065        Iterator<Curve> it = iterator();
1066        while (it.hasNext()) {
1067            if (!c.contains(it.next())) {
1068                it.remove();
1069                modified = true;
1070            }
1071        }
1072        return modified;
1073    }
1074
1075    /**
1076     * Removes a single instance of the specified element from this collection, if it is
1077     * present. More formally, removes an element e such that (o==null ? e==null :
1078     * o.equals(e)), if this collection contains one or more such elements. Returns true
1079     * if this collection contained the specified element (or equivalently, if this
1080     * collection changed as a result of the call).
1081     *
1082     * @param o element to be removed from this collection, if present.
1083     * @return <code>true</code> if this collection changed as a result of the call
1084     */
1085    @Override
1086    public boolean remove(Object o) {
1087        boolean changed = _crvs.remove(o);
1088        if (changed) {
1089            if (o instanceof AbstractGeomElement && !(o instanceof Immutable)) {
1090                ((AbstractGeomElement)o).removeChangeListener(_childChangeListener);
1091            }
1092            fireChangeEvent();
1093        }
1094        return changed;
1095    }
1096
1097    /**
1098     * Removes the curve at the specified position in this surface's list of curves.
1099     * Shifts any subsequent curves to the left (subtracts one from their indices).
1100     * Returns the curve that was removed from the list.
1101     *
1102     * @param index the index of the curve to remove. (0 returns the 1st element, -1
1103     *              returns the last, -2 returns the 2nd from last, etc).
1104     * @return the curve previously at the specified position.
1105     */
1106    @Override
1107    @SuppressWarnings("null")
1108    public Curve remove(int index) {
1109        index = normalizeIndex(index);
1110        Curve old = _crvs.remove(index);
1111        if (!(old instanceof Immutable) && _childChangeListener != null)
1112            old.removeChangeListener(_childChangeListener);
1113        fireChangeEvent();  //  Notify change listeners.
1114        return old;
1115    }
1116
1117    /**
1118     * Removes the curve with the specified name from this list. Shifts any subsequent
1119     * elements to the left (subtracts one from their indices). Returns the element that
1120     * was removed from the list.
1121     *
1122     * @param name the name of the element to remove.
1123     * @return the element previously at the specified position.
1124     */
1125    @Override
1126    public Curve remove(String name) {
1127
1128        Curve element = null;
1129        int index = getIndexFromName(name);
1130        if (index >= 0)
1131            element = this.remove(index);
1132
1133        return element;
1134    }
1135
1136    /**
1137     * Removes all of the curves from this surface. The surface will be empty and
1138     * undefined after this call returns.
1139     */
1140    @Override
1141    @SuppressWarnings("null")
1142    public void clear() {
1143        int size = _crvs.size();
1144        for (int i = 0; i < size; ++i) {
1145            Curve crv = _crvs.get(i);
1146            if (!(crv instanceof Immutable) && _childChangeListener != null)
1147                crv.removeChangeListener(_childChangeListener);
1148        }
1149        _crvs.clear();
1150        fireChangeEvent();  //  Notify change listeners.
1151    }
1152
1153    /**
1154     * Returns an iterator over the curves in this surface's list of curves.
1155     *
1156     * @return an iterator over this list values.
1157     */
1158    @Override
1159    public java.util.Iterator<Curve> iterator() {
1160        return _crvs.iterator();
1161    }
1162
1163    /**
1164     * Returns a list iterator over the curves in this surface.
1165     *
1166     * @return an iterator over this list values.
1167     */
1168    @Override
1169    public java.util.ListIterator<Curve> listIterator() {
1170        return _crvs.listIterator();
1171    }
1172
1173    /**
1174     * Returns a list iterator from the specified position.
1175     *
1176     * @param index the index of first value to be returned from the list iterator (by a
1177     *              call to the next method).
1178     * @return a list iterator of the values in this list starting at the specified
1179     *         position in this list.
1180     */
1181    @Override
1182    public java.util.ListIterator<Curve> listIterator(int index) {
1183        return _crvs.listIterator(index);
1184    }
1185
1186    /**
1187     * Returns an array containing all of the elements in this collection.
1188     */
1189    @Override
1190    public Curve[] toArray() {
1191        return (Curve[])_crvs.toArray();
1192    }
1193
1194    /**
1195     * Returns an array containing all of the elements in this collection. If the
1196     * collection fits in the specified array, it is returned therein. Otherwise, a new
1197     * array is allocated with the runtime type of the specified array and the size of
1198     * this collection.
1199     *
1200     * @param <T> The type of elements in this LoftedSurface (curve type).
1201     * @param a   the array into which the elements of the collection are to be stored, if
1202     *            it is big enough; otherwise, a new array of the same type is allocated
1203     *            for this purpose.
1204     * @return an array containing the elements of the collection.
1205     */
1206    @Override
1207    @SuppressWarnings("SuspiciousToArrayCall")
1208    public <T> T[] toArray(T[] a) {
1209        return _crvs.toArray(a);
1210    }
1211
1212    /**
1213     * Return a new surface that is identical to this one, but with the T-parameterization
1214     * reversed. This is identical to "reverse()".
1215     *
1216     * @return A new surface that is identical to this one, but with the
1217     *         T-parameterization reversed.
1218     * @see #reverse
1219     * @see #reverseS
1220     */
1221    @Override
1222    public LoftedSurface reverseT() {
1223        return reverse();
1224    }
1225
1226    /**
1227     * Return a new surface that is identical to this one, but with the S-parameterization
1228     * reversed.
1229     *
1230     * @return A new surface that is identical to this one, but with the
1231     *         S-parameterization reversed.
1232     * @see #reverseT
1233     */
1234    @Override
1235    public LoftedSurface reverseS() {
1236        LoftedSurface list = LoftedSurface.newInstance(_q);
1237        copyState(list);
1238        int size = this.size();
1239        for (int i = 0; i < size; ++i) {
1240            Curve crv = this.get(i);
1241            list.add(crv.reverse());
1242        }
1243        return list;
1244    }
1245
1246    /**
1247     * Return a new surface that is identical to this one but with the transpose of the
1248     * parameterization of this surface. The S and T directions will be swapped.
1249     *
1250     * @return A new surface that is identical to this one but with the transpose of the
1251     *         parameterization of this surface.
1252     * @see #reverseT
1253     * @see #reverseS
1254     */
1255    @Override
1256    public LoftedSurface transpose() {
1257        LoftedSurface srf = reverseS().reverse();
1258        return srf;
1259    }
1260
1261    /**
1262     * Split this {@link LoftedSurface} at the specified parametric S-position returning a
1263     * list containing two new surfaces (a lower surface with smaller S-parametric
1264     * positions than "s" and an upper surface with larger S-parametric positions). This
1265     * method splits all the defining section curves to create two new lofted surfaces.
1266     *
1267     * @param s The S-parametric position where this surface should be split (must not be
1268     *          0 or 1!).
1269     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
1270     *         surface.
1271     */
1272    @Override
1273    public GeomList<LoftedSurface> splitAtS(double s) {
1274        validateSurface();
1275        validateParameter(s, 0);
1276        if (parNearEnds(s, TOL_ST))
1277            throw new IllegalArgumentException(MessageFormat.format(
1278                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
1279
1280        LoftedSurface srfL = LoftedSurface.newInstance(_q);
1281        copyState(srfL);
1282        LoftedSurface srfU = LoftedSurface.newInstance(_q);
1283        copyState(srfU);
1284
1285        //  Split each defining section.
1286        int size = this.size();
1287        for (int i = 0; i < size; ++i) {
1288            Curve crv = this.get(i);
1289            GeomList<Curve> crvs = crv.splitAt(s);
1290            srfL.add(crvs.get(0));
1291            srfU.add(crvs.get(1));
1292            GeomList.recycle(crvs);
1293        }
1294
1295        //  Create the output list.
1296        GeomList<LoftedSurface> output = GeomList.newInstance();
1297        output.add(srfL, srfU);
1298
1299        return output;
1300    }
1301
1302    /**
1303     * Split this surface at the specified parametric T-position returning a list
1304     * containing two new surfaces (a lower surface with smaller T-parametric positions
1305     * than "t" and an upper surface with larger T-parametric positions). This method
1306     * interpolates a new defining section at "t" and uses the defining sections below it
1307     * and above it to create two new lofted surfaces.
1308     *
1309     * @param t The T-parametric position where this surface should be split (must not be
1310     *          0 or 1!).
1311     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
1312     *         surface.
1313     */
1314    @Override
1315    @SuppressWarnings("null")
1316    public GeomList<LoftedSurface> splitAtT(double t) {
1317        validateSurface();
1318        validateParameter(0, t);
1319        if (parNearEnds(t, TOL_ST))
1320            throw new IllegalArgumentException(MessageFormat.format(
1321                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
1322
1323        //  Determine the parameterizations of the existing defining sections in T.
1324        double[] tParams = sectionParams(0);
1325
1326        //  Find or create the split curve and find the split index.
1327        Curve splitCrv = null;
1328        int numCrvs = size();
1329        int i = 0;
1330        for (; i < numCrvs; ++i) {
1331            Curve crv = get(i);
1332            double ct = tParams[i];
1333
1334            //  Check for an exact match to the split parameter.
1335            if (MathTools.isApproxEqual(t, ct)) {
1336                --i;
1337                splitCrv = crv;
1338                break;
1339            }
1340
1341            //  Look for a parameter that just exceeds the split parameter.
1342            if (ct > t) {
1343                --i;
1344
1345                //  Create an interpolating curve on this surface at t.
1346                Point p0 = Point.valueOf(0, t);
1347                Point p1 = Point.valueOf(1, t);
1348                splitCrv = SubrangeCurve.newInstance(this, CurveFactory.createLine(p0, p1));
1349                break;
1350            }
1351        }
1352        ArrayFactory.DOUBLES_FACTORY.recycle(tParams);
1353
1354        //  Create the lower surface.
1355        LoftedSurface srfL = LoftedSurface.valueOf(_q, getRange(0, i));
1356        srfL.add(splitCrv);
1357
1358        //  Create the upper surface.
1359        LoftedSurface srfU = LoftedSurface.newInstance(_q);
1360        srfU.add(splitCrv.copy());
1361        srfU.addAll(getRange(i + 1, -1));
1362
1363        //  Create the output list.
1364        GeomList<LoftedSurface> output = GeomList.newInstance();
1365        output.add(srfL, srfU);
1366
1367        return output;
1368    }
1369
1370    /**
1371     * Return a NURBS surface representation of this surface to within the specified
1372     * tolerance. If the curves making up this surface are NURBS curves, then the
1373     * resulting NURBS surface is an exact representation of this surface (and tol is
1374     * ignored). However, if any of the member curves are not NURBS curves, then tol is
1375     * used to make a NURBS approximation of that curve (and this surface).
1376     *
1377     * @param tol The greatest possible difference between this surface and the NURBS
1378     *            representation returned. May not be null.
1379     * @return A NURBS surface that represents this surface to within the specified
1380     *         tolerance.
1381     */
1382    @Override
1383    public NurbsSurface toNurbs(Parameter<Length> tol) {
1384        validateSurface();
1385
1386        //  Go off and create a skinned NURBS surface.
1387        BasicNurbsSurface srf = SurfaceFactory.createSkinnedSurface(_crvs, _q, requireNonNull(tol));
1388        copyState(srf); //  Copy over the super-class state for this surface to the new one.
1389
1390        return srf;
1391    }
1392
1393    /**
1394     * Return <code>true</code> if this LoftedSurface contains valid and finite numerical
1395     * components. A value of <code>false</code> will be returned if any of the member
1396     * curves are not valid.
1397     *
1398     * @return true if this LoftedSurface contains valid and finite numerical components.
1399     */
1400    @Override
1401    public boolean isValid() {
1402        //  Make sure there are sufficient member curves.
1403        if (size() < _q + 1)
1404            return false;
1405
1406        //  Make sure all the member curves are valid.
1407        int size = this.size();
1408        for (int i = 0; i < size; ++i) {
1409            Curve crv = this.get(i);
1410            if (!crv.isValid())
1411                return false;
1412        }
1413
1414        return true;
1415    }
1416
1417    /**
1418     * Return <code>true</code> if this surface is degenerate (i.e.: has area less than
1419     * the specified tolerance squared).
1420     *
1421     * @param tol The tolerance for determining if this surface is degenerate. May not be
1422     *            null.
1423     * @return true if this surface is degenerate (i.e.: has area less than the specified
1424     *         tolerance squared).
1425     */
1426    @Override
1427    public boolean isDegenerate(Parameter<Length> tol) {
1428        requireNonNull(tol);
1429        
1430        //  A lofted surface is degenerate if all of it's member curves are degenerate.
1431        int size = this.size();
1432        for (int i = 0; i < size; ++i) {
1433            Curve crv = this.get(i);
1434            if (!crv.isDegenerate(tol))
1435                return false;
1436        }
1437        return true;
1438    }
1439
1440    /**
1441     * Returns a copy of this <code>LoftedSurface</code> instance
1442     * {@link javolution.context.AllocatorContext allocated} by the calling thread
1443     * (possibly on the stack).
1444     *
1445     * @return an identical and independent copy of this object.
1446     */
1447    @Override
1448    public LoftedSurface copy() {
1449        return copyOf(this);
1450    }
1451
1452    /**
1453     * Return a copy of this object with any transformations or subranges removed
1454     * (applied).
1455     *
1456     * @return A copy of this object with any transformations or subranges removed.
1457     */
1458    @Override
1459    public LoftedSurface copyToReal() {
1460        LoftedSurface newSrf = LoftedSurface.newInstance(_q);
1461        copyState(newSrf);
1462
1463        int size = this.size();
1464        for (int i = 0; i < size; ++i) {
1465            Curve crv = this.get(i);
1466            Curve crv2 = crv.copyToReal();
1467            newSrf.add(crv2);
1468        }
1469
1470        return newSrf;
1471    }
1472
1473    /**
1474     * Returns a transformed version of this element. The returned list of objects
1475     * implement {@link GeomTransform} and contains transformed versions of the contents
1476     * of this list as children.
1477     *
1478     * @param transform The transformation to apply to this geometry. May not be null.
1479     * @return A new LoftedSurface that is identical to this one with the specified
1480     *         transformation applied to member curves.
1481     * @throws DimensionException if this surface is not 3D.
1482     */
1483    @Override
1484    public LoftedSurface getTransformed(GTransform transform) {
1485        requireNonNull(transform);
1486        LoftedSurface list = LoftedSurface.newInstance(getTDegree());
1487        copyState(list);
1488        
1489        int size = _crvs.size();
1490        for (int i = 0; i < size; ++i) {
1491            Curve element = _crvs.get(i);
1492            list._crvs.add((Curve)element.getTransformed(transform));
1493        }
1494        return list;
1495    }
1496
1497    /**
1498     * Return the coordinate point representing the minimum bounding box corner of this
1499     * geometry element (e.g.: min X, min Y, min Z).
1500     *
1501     * @return The minimum bounding box coordinate for this geometry element.
1502     * @throws IndexOutOfBoundsException if this list contains no geometry.
1503     */
1504    @Override
1505    public Point getBoundsMin() {
1506        if (isEmpty())
1507            throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry"));
1508
1509        Point minPoint = _crvs.get(0).getBoundsMin();
1510        int size = _crvs.size();
1511        for (int i = 1; i < size; ++i) {
1512            GeomElement element = _crvs.get(i);
1513            minPoint = minPoint.min(element.getBoundsMin());
1514        }
1515
1516        return minPoint;
1517    }
1518
1519    /**
1520     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
1521     * X, max Y, max Z).
1522     *
1523     * @return The maximum bounding box coordinate for this geometry element.
1524     * @throws IndexOutOfBoundsException if this list contains no elements.
1525     */
1526    @Override
1527    public Point getBoundsMax() {
1528        if (isEmpty())
1529            throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry"));
1530
1531        Point maxPoint = _crvs.get(0).getBoundsMax();
1532        int size = _crvs.size();
1533        for (int i = 1; i < size; ++i) {
1534            GeomElement element = _crvs.get(i);
1535            maxPoint = maxPoint.max(element.getBoundsMax());
1536        }
1537
1538        return maxPoint;
1539    }
1540
1541    /**
1542     * Returns the unit in which the <I>first</I> curve in this list is stated. If the
1543     * list contains no curves, then the default unit is returned.
1544     *
1545     * @return The unit in which the first curve in this surface is stated or the default
1546     *         unit if this surface has no defining curves.
1547     */
1548    @Override
1549    public Unit<Length> getUnit() {
1550        if (isEmpty())
1551            return GeomUtil.getDefaultUnit();
1552
1553        return _crvs.get(0).getUnit();
1554    }
1555
1556    /**
1557     * Returns the equivalent to this surface but stated in the specified unit.
1558     *
1559     * @param unit the length unit of the surface to be returned. May not be null.
1560     * @return an equivalent to this surface but stated in the specified unit.
1561     * @throws ConversionException if the the input unit is not a length unit.
1562     */
1563    @Override
1564    public LoftedSurface to(Unit<Length> unit) throws ConversionException {
1565        if (unit.equals(getUnit()))
1566            return this;
1567
1568        //  Convert the curves.
1569        FastTable<Curve> nCrvs = FastTable.newInstance();
1570        int size = _crvs.size();
1571        for (int i = 0; i < size; ++i) {
1572            Curve crv = _crvs.get(i);
1573            nCrvs.add(crv.to(unit));
1574        }
1575        LoftedSurface srf = LoftedSurface.valueOf(_q, nCrvs);
1576        copyState(srf);
1577
1578        return srf;
1579    }
1580
1581    /**
1582     * Return the equivalent of this surface converted to the specified number of physical
1583     * dimensions. If the number of dimensions is greater than this element, then zeros
1584     * are added to the additional dimensions. If the number of dimensions is less than
1585     * this element, then the extra dimensions are simply dropped (truncated). If the new
1586     * dimensions are the same as the dimension of this element, then this element is
1587     * simply returned.
1588     *
1589     * @param newDim The dimension of the surface to return.
1590     * @return This surface converted to the new dimensions.
1591     */
1592    @Override
1593    public LoftedSurface toDimension(int newDim) {
1594        if (getPhyDimension() == newDim)
1595            return this;
1596
1597        //  Convert the curves.
1598        FastTable<Curve> nCrvs = FastTable.newInstance();
1599        int size = _crvs.size();
1600        for (int i = 0; i < size; ++i) {
1601            Curve crv = _crvs.get(i);
1602            nCrvs.add(crv.toDimension(newDim));
1603        }
1604        LoftedSurface srf = LoftedSurface.valueOf(_q, nCrvs);
1605        copyState(srf);
1606
1607        return srf;
1608    }
1609
1610    /**
1611     * Compares the specified object with this list of <code>Curve</code> objects for
1612     * equality. Returns true if and only if both collections are of the same type and
1613     * both collections contain equal elements in the same order.
1614     *
1615     * @param obj the object to compare with.
1616     * @return <code>true</code> if this list is identical to that list;
1617     *         <code>false</code> otherwise.
1618     */
1619    @Override
1620    public boolean equals(Object obj) {
1621        if (this == obj)
1622            return true;
1623        if ((obj == null) || (obj.getClass() != this.getClass()))
1624            return false;
1625
1626        LoftedSurface that = (LoftedSurface)obj;
1627        return this._crvs.equals(that._crvs)
1628                && super.equals(obj);
1629    }
1630
1631    /**
1632     * Returns the hash code for this <code>LoftedSurface</code>.
1633     *
1634     * @return the hash code value.
1635     */
1636    @Override
1637    public int hashCode() {
1638        return 31*super.hashCode() + Objects.hash(_crvs);
1639    }
1640
1641    /**
1642     * Returns the text representation of this geometry element.
1643     *
1644     * @return The text representation of this geometry element.
1645     */
1646    @Override
1647    public Text toText() {
1648        TextBuilder tmp = TextBuilder.newInstance();
1649        String className = this.getClass().getName();
1650        tmp.append(className.substring(className.lastIndexOf(".") + 1));
1651        tmp.append(": {");
1652        String nameStr = getName();
1653        boolean hasName = nonNull(nameStr);
1654        if (hasName) {
1655            tmp.append(nameStr);
1656            tmp.append(" = {");
1657        }
1658        tmp.append("\n");
1659        int size = this.size();
1660        for (int i = 0; i < size; ++i) {
1661            GeomElement e = this.get(i);
1662            tmp.append(e.toText());
1663            if (i < size - 1)
1664                tmp.append(",\n");
1665            else
1666                tmp.append("\n");
1667        }
1668        if (hasName)
1669            tmp.append('}');
1670        tmp.append("}");
1671        Text txt = tmp.toText();
1672        TextBuilder.recycle(tmp);
1673        return txt;
1674    }
1675
1676    /**
1677     * Resets the internal state of this object to its default values. Subclasses that
1678     * override this method must call <code>super.reset();</code> to ensure that the state
1679     * is reset properly.
1680     */
1681    @Override
1682    public void reset() {
1683        clear();
1684        _crvs.reset();
1685        super.reset();
1686    }
1687
1688    /**
1689     * Holds the default XML representation for this object.
1690     */
1691    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
1692    protected static final XMLFormat<LoftedSurface> XML = new XMLFormat<LoftedSurface>(LoftedSurface.class) {
1693
1694        @Override
1695        public LoftedSurface newInstance(Class<LoftedSurface> cls, XMLFormat.InputElement xml) throws XMLStreamException {
1696            LoftedSurface obj = FACTORY.object();
1697            return obj;
1698        }
1699
1700        @Override
1701        public void read(XMLFormat.InputElement xml, LoftedSurface obj) throws XMLStreamException {
1702            int q = Integer.valueOf(xml.getAttribute("q").toString());
1703            obj._q = q;
1704
1705            AbstractSurface.XML.read(xml, obj);     // Call parent read.
1706
1707            FastTable<Curve> lst = xml.get("Contents", FastTable.class);
1708            obj._crvs = lst;
1709        }
1710
1711        @Override
1712        public void write(LoftedSurface obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1713            xml.setAttribute("q", Integer.toString(obj._q));
1714
1715            AbstractSurface.XML.write(obj, xml);    // Call parent write.
1716
1717            xml.add(obj._crvs, "Contents", FastTable.class);
1718        }
1719    };
1720
1721    //////////////////////
1722    // Factory Creation //
1723    //////////////////////
1724    /**
1725     * Do not allow the default constructor to be used except by subclasses.
1726     */
1727    protected LoftedSurface() { }
1728
1729    private static final ObjectFactory<LoftedSurface> FACTORY = new ObjectFactory<LoftedSurface>() {
1730        @Override
1731        protected LoftedSurface create() {
1732            return new LoftedSurface();
1733        }
1734
1735        @Override
1736        protected void cleanup(LoftedSurface obj) {
1737            obj.reset();
1738            obj._crvs = null;
1739        }
1740    };
1741
1742    /**
1743     * Recycles a case instance immediately (on the stack when executing in a
1744     * StackContext).
1745     *
1746     * @param instance The instance to be recycled.
1747     */
1748    public static void recycle(LoftedSurface instance) {
1749        FACTORY.recycle(instance);
1750    }
1751
1752    @SuppressWarnings("unchecked")
1753    private static LoftedSurface copyOf(LoftedSurface original) {
1754        LoftedSurface obj = FACTORY.object();
1755        obj._crvs = FastTable.newInstance();
1756        obj._q = original._q;
1757
1758        int size = original.size();
1759        for (int i = 0; i < size; ++i) {
1760            Curve crv = original.get(i);
1761            crv = crv.copy();
1762            obj.add(crv);
1763        }
1764
1765        original.copyState(obj);
1766        return obj;
1767    }
1768
1769}