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