001/**
002 * BasicCSTCurve -- An implementation of a Class-Shape-Transform planar curve.
003 *
004 * Copyright (C) 2014-2015, 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.cst;
019
020import geomss.geom.*;
021import geomss.geom.nurbs.*;
022import jahuwaldt.js.math.BinomialCoef;
023import jahuwaldt.js.param.Parameter;
024import jahuwaldt.tools.math.MathTools;
025import java.text.MessageFormat;
026import java.util.List;
027import java.util.Objects;
028import static java.util.Objects.requireNonNull;
029import javax.measure.converter.ConversionException;
030import javax.measure.quantity.Dimensionless;
031import javax.measure.quantity.Length;
032import javax.measure.unit.SI;
033import javax.measure.unit.Unit;
034import javolution.context.ArrayFactory;
035import javolution.context.ObjectFactory;
036import javolution.context.StackContext;
037import javolution.lang.ValueType;
038import javolution.util.FastTable;
039import javolution.xml.XMLFormat;
040import javolution.xml.stream.XMLStreamException;
041
042/**
043 * An implementation of the Class-Shape-Transform (CST) planar curve. This curve type is
044 * useful for defining smooth aircraft component shapes with a minimum number of defining
045 * parameters.
046 *
047 * <p> Modified by: Joseph A. Huwaldt </p>
048 *
049 * @author Joseph A. Huwaldt, Date: March 14, 2014
050 * @version November 28, 2015
051 */
052@SuppressWarnings({"serial", "CloneableImplementsClone"})
053public final class BasicCSTCurve extends CSTCurve implements ValueType {
054    // Reference: Kulfan, B.M., Bussoletti, J.E., "'Fundamental' Parametric Geometry
055    //          Representations for Aircraft Component Shapes", AIAA-2006-6948.
056
057    /**
058     * The class function for this curve.
059     */
060    private CSTClassFunction Cf;
061
062    /**
063     * The shape function for this curve.
064     */
065    private CSTShapeFunction Sf;
066
067    /**
068     * The trailing edge offset for this curve.
069     */
070    private double offsetTE_c;
071
072    /**
073     * The origin of this curve (the location of the leading edge; gives the curve
074     * location).
075     */
076    private Point origin;
077
078    /**
079     * The chord length of this curve (gives the curve scale).
080     */
081    private Parameter<Length> chord;
082
083    /**
084     * The X-direction for the curve (gives the curve direction).
085     */
086    private Vector<Dimensionless> xhat;
087
088    /**
089     * The Y-direction for the curve (gives the curve direction).
090     */
091    private Vector<Dimensionless> yhat;
092
093    /**
094     * A parametric mapping between the input "s" and the "s" used in calculations.
095     */
096    private NurbsCurve pmap;
097    
098    /**
099     * The minimum bounding point for this subrange curve.
100     */
101    private Point _boundsMin;
102
103    /**
104     * The maximum bounding point for this subrange curve.
105     */
106    private Point _boundsMax;
107
108    /**
109     * Return a new planar {@link BasicCSTCurve} instance using the supplied input
110     * parameters.
111     *
112     * @param Cf       The class function for this BasicCSTCurve. May not be null.
113     * @param Sf       The shape function for this BasicCSTCurve. May not be null.
114     * @param offsetTE The trailing edge offset from the chord-line for this
115     *                 BasicCSTCurve. May not be null.
116     * @param origin   The origin of the leading edge of this curve (gives the curve
117     *                 location). May not be null.
118     * @param chord    The chord length for this curve (gives the curve scale). May not be
119     *                 null.
120     * @param xhat     The chord-wise direction of this curve (the curve X-axis
121     *                 direction). May not be null.
122     * @param yhat     The direction perpendicular to xhat for this curve (the curve
123     *                 Y-axis direction). May not be null.
124     * @return A new BasicCSTCurve instance
125     */
126    public static BasicCSTCurve newInstance(CSTClassFunction Cf, CSTShapeFunction Sf, Parameter<Length> offsetTE,
127            Point origin, Parameter<Length> chord, GeomVector<Dimensionless> xhat, GeomVector<Dimensionless> yhat) {
128        requireNonNull(Cf);
129        requireNonNull(Sf);
130        requireNonNull(offsetTE);
131        
132        int numDims = GeomUtil.maxPhyDimension(origin, xhat, yhat);
133        if (numDims < 2)
134            throw new DimensionException(
135                    MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"),
136                            "input origin or normal", numDims));
137
138        //  Convert everything to common units and dimensions.
139        Unit<Length> unit = origin.getUnit();
140        origin = origin.toDimension(numDims);
141        chord = chord.to(unit);
142        xhat = xhat.toDimension(numDims);
143        yhat = yhat.toDimension(numDims);
144
145        //  Create an instance of this object and store all the inputs.
146        BasicCSTCurve obj = FACTORY.object();
147        obj.pmap = CurveFactory.createLine(Point.valueOf(0), Point.valueOf(1));
148        obj.Cf = Cf;
149        obj.Sf = Sf;
150        obj.offsetTE_c = offsetTE.divide(chord).asType(Dimensionless.class).getValue(Dimensionless.UNIT);
151        obj.origin = origin;
152        obj.chord = chord;
153        obj.xhat = xhat.toUnitVector();
154        obj.yhat = yhat.toUnitVector();
155
156        //  Compute and store the bounds min/max values.
157        obj.calcBoundsMinMax();
158
159        return obj;
160    }
161
162    /**
163     * Return a new 2D, planar {@link BasicCSTCurve} instance using the supplied input
164     * parameters with the origin at (0,0), a chord length of 1.0 m and with the chord
165     * aligned with the X-axis.
166     *
167     * @param Cf     The class function for this BasicCSTCurve. May not be null.
168     * @param Sf     The shape function for this BasicCSTCurve. May not be null.
169     * @param dZTE_c The trailing edge offset from the chord-line to chord length ratio
170     *               for this BasicCSTCurve.
171     * @return A new BasicCSTCurve instance
172     */
173    public static BasicCSTCurve newInstance(CSTClassFunction Cf, CSTShapeFunction Sf, double dZTE_c) {
174        Parameter<Length> chord = Parameter.valueOf(1, SI.METER);
175        Parameter<Length> dZTE = chord.times(dZTE_c);
176        BasicCSTCurve crv = BasicCSTCurve.newInstance(Cf, Sf, dZTE, Point.newInstance(2), chord,
177                Vector.valueOf(1, 0), Vector.valueOf(0, 1));
178        return crv;
179    }
180
181    /**
182     * Return the class function associated with this CST curve.
183     *
184     * @return The class function associated with this CST curve.
185     */
186    @Override
187    public CSTClassFunction getCFunction() {
188        return Cf;
189    }
190
191    /**
192     * Return the shape function associated with this CST curve.
193     *
194     * @return The shape function associated with this CST curve.
195     */
196    @Override
197    public CSTShapeFunction getSFunction() {
198        return Sf;
199    }
200
201    /**
202     * Return the offset of the trailing edge of the curve from the chord line.
203     *
204     * @return The offset of the trailing edge of the curve from the chord line.
205     */
206    @Override
207    public Parameter<Length> getOffsetTE() {
208        return chord.times(offsetTE_c);
209    }
210
211    /**
212     * Return the chord length of the curve. This defines the scale or size of the curve.
213     *
214     * @return The chord length of the curve.
215     */
216    @Override
217    public Parameter<Length> getChord() {
218        return chord;
219    }
220
221    /**
222     * Return the origin of the leading edge of this curve. This defines the s=0 location
223     * of the curve in space.
224     *
225     * @return The origin of the leading edge of this curve.
226     */
227    @Override
228    public Point getOrigin() {
229        return origin;
230    }
231
232    /**
233     * Return the chord-wise direction of this curve (the curve X-direction).
234     *
235     * @return The chord-wise direction of this curve.
236     */
237    @Override
238    public Vector<Dimensionless> getXhat() {
239        return xhat;
240    }
241
242    /**
243     * Return the in-plane direction of this curve perpendicular to the chord-wise
244     * direction (the curve Y-direction).
245     *
246     * @return The in-plane direction of this curve perpendicular to Xhat.
247     */
248    @Override
249    public Vector<Dimensionless> getYhat() {
250        return yhat;
251    }
252
253    /**
254     * Return a copy of this CSTCurve with the class function changed to the specified
255     * class function.
256     *
257     * @param Cf The new class function for this BasicCSTCurve. May not be null.
258     * @return A copy of this CSTCurve with the class function changed to the specified
259     *         class function.
260     */
261    public BasicCSTCurve changeCFunction(CSTClassFunction Cf) {
262        requireNonNull(Cf);
263        BasicCSTCurve obj = copy();
264        obj.Cf = Cf;
265        obj.calcBoundsMinMax();
266        return obj;
267    }
268
269    /**
270     * Return a copy of this CSTCurve with the shape function changed to the specified
271     * shape function.
272     *
273     * @param Sf The new shape function for this BasicCSTCurve. May not be null.
274     * @return A copy of this CSTCurve with the shape function changed to the specified
275     *         shape function.
276     */
277    public BasicCSTCurve changeSFunction(CSTShapeFunction Sf) {
278        requireNonNull(Sf);
279        BasicCSTCurve obj = copy();
280        obj.Sf = Sf;
281        obj.calcBoundsMinMax();
282        return obj;
283    }
284
285    /**
286     * Return a copy of this CSTCurve with the shape function Bernstein Polynomial
287     * coefficients changed to the specified coefficients.
288     *
289     * @param order  The order of the shape function to use.
290     * @param Acoefs The new coefficients of the Bernstein Polynomial used to construct
291     *               the shape function. If more than "order" coefficients are provided
292     *               then the additional coefficients are ignored. May not be null.
293     * @return A copy of this CSTCurve with the shape function Bernstein Polynomial
294     *         coefficients changed to the specified coefficients.
295     */
296    public BasicCSTCurve changeBFCoefficients(int order, double... Acoefs) {
297        CSTShapeFunction Sfunc = CSTShapeFunction.newInstance(order, requireNonNull(Acoefs));
298        return changeSFunction(Sfunc);
299    }
300
301    /**
302     * Return a copy of this CSTCurve with the trailing edge offset from the chord-line
303     * changed to the specified value.
304     *
305     * @param offsetTE The new trailing edge offset from the chord-line for this
306     *                 BasicCSTCurve. May not be null.
307     * @return A copy of this CSTCurve with the trailing edge offset changed to the
308     *         specified value.
309     */
310    public BasicCSTCurve changeOffsetTE(Parameter<Length> offsetTE) {
311        BasicCSTCurve obj = copy();
312        obj.offsetTE_c = offsetTE.divide(obj.chord).asType(Dimensionless.class).getValue(Dimensionless.UNIT);
313        obj.calcBoundsMinMax();
314        return obj;
315    }
316    
317    /**
318     * Return a copy of this CSTCurve with the trailing edge offset from the chord-line to
319     * chord ratio changed to the specified value.
320     *
321     * @param offsetTE_c The new trailing edge offset from the chord-line to chord ratio
322     *                   for this BasicCSTCurve.
323     * @return A copy of this CSTCurve with the trailing edge offset changed to the
324     *         specified value.
325     */
326    public BasicCSTCurve changeOffsetTE(double offsetTE_c) {
327        BasicCSTCurve obj = copy();
328        obj.offsetTE_c = offsetTE_c;
329        obj.calcBoundsMinMax();
330        return obj;
331    }
332    
333    /**
334     * Return a copy of this CSTCurve with the curve origin or leading-edge point changed
335     * to the specified value.
336     *
337     * @param origin The new origin of the leading edge of this curve (gives the curve
338     *               location). May not be null.
339     * @return A copy of this CSTCurve with the origin changed to the specified value.
340     */
341    public BasicCSTCurve changeOrigin(Point origin) {
342        requireNonNull(origin);
343        BasicCSTCurve obj = copy();
344        obj.origin = origin;
345        obj.calcBoundsMinMax();
346        return obj;
347    }
348    
349    /**
350     * Return a copy of this CSTCurve with the chord length changed to the specified
351     * value.
352     *
353     * @param chord The chord length for this curve (gives the curve scale). May not be null.
354     * @return A copy of this CSTCurve with the chord length changed to the specified
355     *         value.
356     */
357    public BasicCSTCurve changeChord(Parameter<Length> chord) {
358        requireNonNull(chord);
359        BasicCSTCurve obj = copy();
360        obj.chord = chord;
361        obj.calcBoundsMinMax();
362        return obj;
363    }
364    
365    /**
366     * Returns the number of physical dimensions of the geometry element.
367     *
368     * @return The number of physical dimensions of the geometry element.
369     */
370    @Override
371    public int getPhyDimension() {
372        return origin.getPhyDimension();
373    }
374
375    /**
376     * Return the order of this CST curve.
377     *
378     * @return order of this curve (degree + 1)
379     *
380     */
381    public int getOrder() {
382        return Sf.getOrder();
383    }
384
385    /**
386     * Return the value of this CST curve function at the specified parametric location.
387     */
388    private double getValue(double s) {
389        double S = Sf.getValue(s);
390        double C = Cf.getValue(s);
391        return C * S + offsetTE_c * s;
392    }
393
394    /**
395     * Calculate a point on the curve for the given parametric distance along the curve,
396     * <code>p(s)</code>.
397     *
398     * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive).
399     * @return the calculated point
400     */
401    @Override
402    public Point getRealPoint(double s) {
403        validateParameter(s);
404
405        StackContext.enter();
406        try {
407            //  Map "s" from parameter space to CST space.
408            s = pmap.getRealPoint(s).getValue(Point.X);
409
410            double ch = chord.getValue(SI.METER);
411            double aValue = ch * s;
412            double bValue = ch * getValue(s);
413            Point p = Point.valueOf(xhat.times(aValue).plus(yhat.times(bValue))).to(getUnit());
414            p = p.plus(origin);
415
416            return StackContext.outerCopy(p);
417        } finally {
418            StackContext.exit();
419        }
420    }
421
422    /**
423     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
424     * respect to parametric distance on the curve for the given parametric distance along
425     * the curve, <code>d^{grade}p(s)/d^{grade}s</code>.
426     * <p>
427     * Example:<br>
428     * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
429     * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
430     * </p>
431     *
432     * @param s     Parametric distance to calculate derivatives for (0.0 to 1.0
433     *              inclusive).
434     * @param grade The maximum grade to calculate the derivatives for (1=1st derivative,
435     *              2=2nd derivative, etc)
436     * @return A list of derivatives up to the specified grade of the curve at the
437     *         specified parametric position.
438     * @throws IllegalArgumentException if the grade is &lt; 0.
439     */
440    @Override
441    public List<Vector<Length>> getSDerivatives(double s, int grade) {
442        validateParameter(s);
443        s = roundParNearEnds(s);
444
445        if (grade < 0)
446            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
447
448        //  CST Terms:  1: C(s)*S(s), 2: s*offsetTE_c
449        Vector[] ders = Vector.allocateArray(grade + 1);   //  Created outside of StackContext.
450        StackContext.enter();
451        try {
452            //  Map "s" from user space to calculation space.
453            s = pmap.getRealPoint(s).getValue(Point.X);
454
455            //  Get derivatives of the class and shape functions.
456            double[] Cders = Cf.getDerivatives(s, grade);
457            double[] Sders = Sf.getDerivatives(s, grade);
458
459            //  Calculate components of the 1st term derivatives using Leibniz's Rule.
460            double[] term1 = leibnizRule(Cders, Sders, grade);
461
462            GeomVector<Length> o = origin.toGeomVector();
463            Unit<Length> unit = getUnit();
464            for (int n = 0; n <= grade; ++n) {
465                //  Get the derivatives of the 1st term.
466                double v = term1[n];
467
468                //  Add in the derivatives of the 2nd term (and of "s").
469                double ds = 0;  //  Derivate of "s".
470                if (n == 0) {
471                    v += s * offsetTE_c;
472                    ds = s;
473                } else if (n == 1) {
474                    v += offsetTE_c;
475                    ds = 1;
476                }
477                //  2nd term derivatives are 0 for all higher grades.
478
479                Vector<Dimensionless> vhat = xhat.times(ds).plus(yhat.times(v));
480                Vector<Length> vec = vhat.times(chord).to(unit);
481                if (n == 0)
482                    vec = vec.plus(o);
483                ders[n] = StackContext.outerCopy(vec);
484            }
485        } finally {
486            StackContext.exit();
487        }
488
489        //  Convert to a list of Vector objets.
490        FastTable<Vector<Length>> output = FastTable.newInstance();
491        Point o = Point.valueOf(ders[0]);
492        for (int i = 0; i <= grade; ++i) {
493            Vector<Length> v = ders[i];
494            v.setOrigin(o);
495            output.add(v);
496        }
497
498        Vector.recycleArray(ders);
499
500        return output;
501    }
502
503    /**
504     * Compute the derivatives of the product of two functions using the Leibniz General
505     * Rule for Differentiation: <code>d^n(f(s)*g(s))/ds^n</code>.
506     *
507     * @param f     The derivatives of the function "f" from 0 up to grade.
508     * @param g     The derivatives of the function "g" from 0 up to grade.
509     * @param grade The grade to which the derivative terms are to be calculated.
510     * @return An array of the derivatives, from 0 to grade, of the product of two
511     *         functions: d^n(f(s)*g(s))/ds^n. The returned array was allocated using
512     *         javolution.context.ArrayFactory.DOUBLES_FACTORY, could be longer than the
513     *         number of derivatives requested, and could be recycled by the user when no
514     *         longer needed.
515     */
516    private static double[] leibnizRule(double[] f, double[] g, int grade) {
517        //  Reference: https://en.wikipedia.org/wiki/General_Leibniz_rule
518
519        int gradeP1 = grade + 1;
520        BinomialCoef bin = BinomialCoef.newInstance(gradeP1);
521        double[] fgn = ArrayFactory.DOUBLES_FACTORY.array(gradeP1);
522        for (int n = 0; n < gradeP1; ++n) {
523            for (int k = n; k >= 0; --k) {
524                double bCoef = bin.get(n, k);
525                double fk = f[k];
526                double gnmk = g[n - k];
527                double der = fk * gnmk * bCoef;
528                fgn[n] += der;
529            }
530        }
531        BinomialCoef.recycle(bin);
532
533        return fgn;
534    }
535
536    /**
537     * Return the coordinate point representing the minimum bounding box corner of this
538     * geometry element (e.g.: min X, min Y, min Z).
539     *
540     * @return The minimum bounding box coordinate for this geometry element.
541     * @throws IndexOutOfBoundsException if this list contains no geometry.
542     */
543    @Override
544    public Point getBoundsMin() {
545        return _boundsMin;
546    }
547
548    /**
549     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
550     * X, max Y, max Z).
551     *
552     * @return The maximum bounding box coordinate for this geometry element.
553     * @throws IndexOutOfBoundsException if this list contains no elements.
554     */
555    @Override
556    public Point getBoundsMax() {
557        return _boundsMax;
558    }
559
560    private static final int NPTS = 51;
561
562    /**
563     * Calculate the minimum & maximum bounding box corner points of this geometry
564     * element. This method may ONLY be called at the time that an instance of this object
565     * is created by a "newInstance()" method.
566     */
567    private void calcBoundsMinMax() {
568        StackContext.enter();
569        try {
570            //  Space out some points on the curve.
571            List<Double> spacing = GridSpacing.linear(NPTS);
572            PointString<SubrangePoint> str = this.extractGrid(GridRule.PAR, spacing);
573
574            //  Get the bounds of the grid of points.
575            _boundsMin = StackContext.outerCopy(str.getBoundsMin());
576            _boundsMax = StackContext.outerCopy(str.getBoundsMax());
577        } finally {
578            StackContext.exit();
579        }
580    }
581
582    /**
583     * Return a new curve that is identical to this one, but with the parameterization
584     * reversed. This does not change the origin or axis directions, but only the curve
585     * parameterization; what was s=0 will become s=1, etc.
586     *
587     * @return A new curve identical to this one, but with the parameterization reversed.
588     */
589    @Override
590    public CSTCurve reverse() {
591        BasicCSTCurve obj = copy();
592        obj.pmap = this.pmap.reverse();
593        return obj;
594    }
595    
596    /**
597     * Split this curve at the specified parametric position returning a list containing
598     * two curves (a lower curve with smaller parametric positions than "s" and an upper
599     * curve with larger parametric positions). The origin, chord length, and trailing
600     * edge offset from chord-line will be identical for the the returned segments and
601     * this curve.  The only change is in the parameterization.
602     *
603     * @param s The parametric position where this curve should be split (must not be 0 or
604     *          1!).
605     * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve.
606     */
607    @Override
608    public GeomList<CSTCurve> splitAt(double s) {
609        validateParameter(s);
610        if (parNearEnds(s, TOL_S))
611            throw new IllegalArgumentException(
612                    MessageFormat.format(RESOURCES.getString("canNotSplitAtEnds"), "curve"));
613        
614        GeomList<CSTCurve> output = GeomList.newInstance();
615        
616        //  Split the parametric mapping at "s".
617        GeomList<NurbsCurve> p_split = pmap.splitAt(s);
618        
619        //  Create a "lower" curve segment.
620        BasicCSTCurve cl = copy();
621        cl.pmap = p_split.getFirst();
622        cl.calcBoundsMinMax();
623        BasicCSTCurve cu = copy();
624        cu.pmap = p_split.getLast();
625        cu.calcBoundsMinMax();
626        
627        output.add(cl);
628        output.add(cu);
629        
630        return output;
631    }
632    
633    /**
634     * Return <code>true</code> if this curve contains valid and finite numerical
635     * components. A value of <code>false</code> will be returned if any of the coordinate
636     * values are NaN or Inf.
637     *
638     * @return true if this element contains valid and finite numerical values.
639     */
640    @Override
641    public boolean isValid() {
642        return origin.isValid() && xhat.isValid() && yhat.isValid()
643                && !chord.isInfinite() && !chord.isNaN()
644                && !Double.isInfinite(offsetTE_c) && !Double.isNaN(offsetTE_c);
645    }
646
647    /**
648     * Returns the unit in which this curve is stated.
649     *
650     * @return The unit that this curves points are stated in.
651     */
652    @Override
653    public Unit<Length> getUnit() {
654        return origin.getUnit();
655    }
656
657    /**
658     * Returns the equivalent to this curve but stated in the specified unit.
659     *
660     * @param unit the length unit of the curve to be returned. May not be null.
661     * @return an equivalent to this curve but stated in the specified unit.
662     * @throws ConversionException if the the input unit is not a length unit.
663     */
664    @Override
665    public BasicCSTCurve to(Unit<Length> unit) throws ConversionException {
666        if (unit.equals(getUnit()))
667            return this;
668        BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.to(unit), chord.to(unit), xhat, yhat);
669        return copyState(crv);
670    }
671
672    /**
673     * Return the equivalent of this curve converted to the specified number of physical
674     * dimensions. If the number of dimensions is greater than this element, then zeros
675     * are added to the additional dimensions. If the number of dimensions is less than
676     * this element, then the extra dimensions are simply dropped (truncated). If the new
677     * dimensions are the same as the dimension of this element, then this element is
678     * simply returned.
679     *
680     * @param newDim The dimension of the curve to return.
681     * @return The equivalent of this curve converted to the new dimensions.
682     */
683    @Override
684    public BasicCSTCurve toDimension(int newDim) {
685        if (getPhyDimension() == newDim)
686            return this;
687        BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.toDimension(newDim), chord,
688                xhat.toDimension(newDim), yhat.toDimension(newDim));
689        return copyState(crv);
690    }
691
692    /**
693     * Return a NURBS curve representation of this curve to within the specified
694     * tolerance.
695     *
696     * @param tol The greatest possible difference between this curve and the NURBS
697     *            representation returned. May not be null.
698     * @return A NURBS curve that represents this curve to within the specified tolerance.
699     */
700    @Override
701    public NurbsCurve toNurbs(Parameter<Length> tol) {
702        StackContext.enter();
703        try {
704            //  Can this curve be represented exactly by a NURBS curve?
705            //  Only integer N2 (either 0 or 1) can be represented exactly as NURBS.
706            if (MathTools.isApproxEqual(Cf.getN2(), 1.0)) {
707                if (MathTools.isApproxEqual(Cf.getN1(), 0.5)) {
708                    BasicNurbsCurve curve = bluntAirfoil2Bezier();
709                    curve = matchParameterization(curve);
710                    copyState(curve);
711                    return StackContext.outerCopy(curve);
712                }
713                if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) {
714                    BasicNurbsCurve curve = sharpAirfoil2Bezier();
715                    curve = matchParameterization(curve);
716                    copyState(curve);
717                    return StackContext.outerCopy(curve);
718                }
719                
720            } else if (MathTools.isApproxEqual(Cf.getN2(), 0)) {
721                if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) {
722                    BasicNurbsCurve curve = cone2Bezier();
723                    curve = matchParameterization(curve);
724                    copyState(curve);
725                    return StackContext.outerCopy(curve);
726                }
727                if (MathTools.isApproxEqual(Cf.getN1(), 0.0)) {
728                    BasicNurbsCurve curve = rectangular2Bezier();
729                    curve = matchParameterization(curve);
730                    copyState(curve);
731                    return StackContext.outerCopy(curve);
732                }
733            }
734
735            //  Fall-back to gridding points onto the curve and then fitting a curve through them.
736            
737            //  Grid some points onto the curve.
738            PointString<SubrangePoint> str = this.gridToTolerance(requireNonNull(tol));
739
740            //  Fit a cubic NURBS curve to the points.
741            int deg = Sf.getOrder() - 1;
742            if (deg < 3)
743                deg = 3;
744            if (str.size() <= deg)
745                deg = str.size() - 1;
746            BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str);
747            copyState(curve);   //  Copy over the super-class state for this curve to the new one.
748
749            return StackContext.outerCopy(curve);
750
751        } finally {
752            StackContext.exit();
753        }
754    }
755
756    /**
757     * Modify the input NurbsCurve to have the same parameterization as this CST curve.
758     * 
759     * @param crv The NURBS curve to be modified.
760     * @return The modified NURBS curve.
761     */
762    private BasicNurbsCurve matchParameterization(BasicNurbsCurve crv) {
763        //  Get the limits of this curve's parameter mapping.
764        double s0 = pmap.getRealPoint(0).getValue(0);
765        double s1 = pmap.getRealPoint(1).getValue(0);
766
767        //  Is the parameterization reversed?
768        boolean reversed = s1 < s0;
769
770        if (!reversed) {
771            if (!parNearStart(s0, TOL_S)) {
772                //  s0 is not at the start of the curve.
773                
774                //  Find s0 on the NurbsCurve and split the NurbsCurve there.
775                SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S);
776                double s_split = p.getParPosition().getValue(0);
777                GeomList<NurbsCurve> lst = crv.splitAt(s_split);
778                crv = (BasicNurbsCurve)lst.getLast();
779            }
780            if (!parNearEnd(s1, TOL_S)) {
781                //  s1 is not at the end of the curve.
782                
783                //  Find s1 on the NurbsCurve and split the NurbsCurve there.
784                SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S);
785                double s_split = p.getParPosition().getValue(0);
786                GeomList<NurbsCurve> lst = crv.splitAt(s_split);
787                crv = (BasicNurbsCurve)lst.getFirst();
788            }
789
790        } else {
791            //  The parameterization is reversed.
792            if (!parNearStart(s1, TOL_S)) {
793                //  s01 is not at the start of the curve.
794                
795                //  Find s1 on the NurbsCurve and split the NurbsCurve there.
796                SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S);
797                double s_split = p.getParPosition().getValue(0);
798                GeomList<NurbsCurve> lst = crv.splitAt(s_split);
799                crv = (BasicNurbsCurve)lst.getLast();
800            }
801            if (!parNearEnd(s0, TOL_S)) {
802                //  s0 is not at the end of the curve.
803                
804                //  Find s0 on the NurbsCurve and split the NurbsCurve there.
805                SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S);
806                double s_split = p.getParPosition().getValue(0);
807                GeomList<NurbsCurve> lst = crv.splitAt(s_split);
808                crv = (BasicNurbsCurve)lst.getFirst();
809            }
810            crv = (BasicNurbsCurve)crv.reverse();
811        }
812
813        return crv;
814    }
815    
816    /**
817     * An exact conversion to a Bezier curve is possible for a blunt-nosed airfoil CST curve.
818     * 
819     * @return A NURBS curve that represents this airfoil curve exactly.
820     */
821    private BasicNurbsCurve bluntAirfoil2Bezier() {
822        //  Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes",
823        //  21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013.
824        
825        int n = Sf.getOrder() - 1;      //  Degree of the shape function.
826        int m = 2*n + 3;                //  Degree of output NURBS curve.
827        
828        //  Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i
829        BinomialCoef bin = BinomialCoef.newInstance(m + 1);   //  Get a set of binomial coefficients.
830        double[] a = shape2Monomial(Sf.getBasisFunction(), bin);
831        
832        //  Create the "c" coefficients for the new monomial using Equation 13 from the reference.
833        //  xi = s^2 due to parameter substitution (see below), so c[2] = 1.
834        double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
835        c[0] = 0;
836        c[1] = 0;
837        c[2] = 1;
838        for (int i=3; i <= m; ++i)
839            c[i] = 0;
840        
841        //  Create the "b" coefficients for the new monomial using Equation 11 from the reference.
842        //  zeta = sqrt(t)*(1-t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE
843        //      With parameter substitution:  t ==> s^2:
844        //  zeta = s*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*s^(2*i) - an*s^(2*(n+1))] + s^2*DzetaTE; or simplifying:
845        //  zeta = a0*s + DzetaTE*s^2 + sum_{i=1}^{n}{ (ai - a_(i-1)) * s^(2*i + 1) } - an*s^m = sum_{i=0}^{n}{bi*t^i}
846        double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
847        for (int i = 0; i <= m; ++i) {
848            if (i == 1)
849                b[i] = a[0];
850            else if (i == 2)
851                b[i] = offsetTE_c;
852            else if (MathTools.even(i))
853                b[i] = 0;
854            else if (i == m)
855                b[i] = -a[n];
856            else {
857                int i2 = (i - 1) / 2;
858                b[i] = a[i2] - a[i2 - 1];
859            }
860        }
861
862        //  Form the new Bezier curve from the newly computed monomial coefficients.
863        BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin);
864        
865        return newCrv;
866    }
867    
868    /**
869     * An exact conversion to a Bezier curve is possible for a sharp-nosed airfoil CST curve.
870     * 
871     * @return A NURBS curve that represents this airfoil curve exactly.
872     */
873    private BasicNurbsCurve sharpAirfoil2Bezier() {
874        //  Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes",
875        //  21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013.
876        
877        int n = Sf.getOrder() - 1;      //  Degree of the shape function.
878        int m = n + 2;                  //  Degree of output NURBS curve.
879        
880        //  Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i
881        BinomialCoef bin = BinomialCoef.newInstance(m + 1);   //  Get a set of binomial coefficients.
882        double[] a = shape2Monomial(Sf.getBasisFunction(), bin);
883        
884        //  Create the "c" coefficients for the new monomial.
885        //  xi = t, to c[1] = 1.
886        double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
887        c[0] = 0;
888        c[1] = 1;
889        for (int i=2; i <= m; ++i)
890            c[i] = 0;
891        
892        //  Create the "b" coefficients for the new monomial.
893        //  zeta = t*(1 - t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 14 with xi = t)
894        //  zeta = t*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*t^i - an*s^(n+1)] + t*DzetaTE; simplifying:
895        //  zeta = (a0 + DzetaTE)*t + sum_{i=1}^{n}{ (ai - a_(i-1))*t^(i+1) } - an*t^m = sum_{i=0}^{n}{bi*t^i}
896        double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
897        for (int i = 0; i <= m; ++i) {
898            if (i == 0)
899                b[i] = 0;
900            else if (i == 1)
901                b[i] = a[0] + offsetTE_c;
902            else if (i == m)
903                b[i] = -a[n];
904            else {
905                int i2 = i - 1;
906                b[i] = a[i2] - a[i2 - 1];
907            }
908        }
909
910        //  Form the new Bezier curve from the newly computed monomial coefficients.
911        BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin);
912        
913        return newCrv;
914    }
915    
916    /**
917     * An exact conversion to a Bezier curve is possible for a cone or wedge shaped CST curve.
918     * 
919     * @return A NURBS curve that represents this CST curve exactly.
920     */
921    private BasicNurbsCurve cone2Bezier() {
922        //  Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes",
923        //  21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013.
924        
925        int n = Sf.getOrder() - 1;      //  Degree of the shape function.
926        int m = n + 1;                  //  Degree of output NURBS curve.
927        
928        //  Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i
929        BinomialCoef bin = BinomialCoef.newInstance(m + 1);   //  Get a set of binomial coefficients.
930        double[] a = shape2Monomial(Sf.getBasisFunction(), bin);
931        
932        //  Create the "c" coefficients for the new monomial.
933        //  xi = t, to c[1] = 1.
934        double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
935        c[0] = 0;
936        c[1] = 1;
937        for (int i=2; i <= m; ++i)
938            c[i] = 0;
939        
940        //  Create the "b" coefficients for the new monomial.
941        //  zeta = t*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 15 with xi = t)
942        //  zeta = (a0 + DzetaTE)*t + sum_{i=1}^{n}{ai*t^(i+1)} + an*t^m = sum_{i=0}^{n}{bi*t^i}
943        double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
944        for (int i = 0; i <= m; ++i) {
945            if (i == 0)
946                b[i] = 0;
947            else if (i == 1)
948                b[i] = a[0] + offsetTE_c;
949            else if (i == m)
950                b[i] = a[n];
951            else
952                b[i] = a[i];
953        }
954
955        //  Form the new Bezier curve from the newly computed monomial coefficients.
956        BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin);
957        
958        return newCrv;
959    }
960    
961    /**
962     * An exact conversion to a Bezier curve is possible for a rectangular shaped CST curve.
963     * 
964     * @return A NURBS curve that represents this CST curve exactly.
965     */
966    private BasicNurbsCurve rectangular2Bezier() {
967        //  Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes",
968        //  21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013.
969        
970        int n = Sf.getOrder() - 1;      //  Degree of the shape function.
971        int m = n;                      //  Degree of output NURBS curve.
972        
973        //  Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i
974        BinomialCoef bin = BinomialCoef.newInstance(m + 1);   //  Get a set of binomial coefficients.
975        double[] a = shape2Monomial(Sf.getBasisFunction(), bin);
976        
977        //  Create the "c" coefficients for the new monomial.
978        //  xi = t, to c[1] = 1.
979        double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1);
980        c[0] = 0;
981        c[1] = 1;
982        for (int i=2; i <= m; ++i)
983            c[i] = 0;
984        
985        //  Create the "b" coefficients for the new monomial.
986        //  zeta = sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 16 with xi = t)
987        //  zeta = a0 + (a1 + DzetaTE)*t + sum_{i=2}^{n}{ai*t^i} = sum_{i=0}^{n}{bi*t^i}
988        double[] b = a;
989        b[1] += offsetTE_c;
990
991        //  Form the new Bezier curve from the newly computed monomial coefficients.
992        BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin);
993        
994        return newCrv;
995    }
996    
997    /**
998     * Method that converts the 1D shape function Bezier curve to a monomial
999     * representation of the form: <code>S(t) = sum_{i=0}^{n}{ai*t^i}</code>.
1000     *
1001     * @param S   The Bezier curve that is the basis for the shape function.
1002     * @param bin A set of binomial coefficients of at least the S.getOrder() in size.
1003     * @return The coefficients of a monomial representation of the shape function.
1004     */
1005    private static double[] shape2Monomial(BasicNurbsCurve S, BinomialCoef bin) {
1006        
1007        //  Convert the Bezier curve shape function to a monomial of the form:
1008        //      S(t) = sum_{i=0}^{n} {ai*t^i}
1009        //  Equation 7 from reference: aj = sum_{i=0}^{j}{ (n,j) * (j,i) * (-1)^(j-i) * pi }
1010        int n = S.getDegree();
1011        List<ControlPoint> cps = S.getControlPoints();
1012        double[] a = ArrayFactory.DOUBLES_FACTORY.array(n + 1);
1013        for (int j = 0; j <= n; ++j) {
1014            a[j] = 0;
1015            for (int i = 0; i <= j; ++i) {
1016                double Bnj = bin.get(n, j);
1017                double Bji = bin.get(j, i);
1018                double sign = (MathTools.even(j - i) ? 1.0 : -1.0);   //  = (-1)^(j-i)
1019                double pi = cps.get(i).getPoint().getValue(Point.X);
1020                a[j] += Bnj * Bji * sign * pi;
1021            }
1022        }
1023        
1024        return a;
1025    }
1026    
1027    /**
1028     * Form a 2D Bezier curve (as a NURBS curve) from the input list of monomial
1029     * coefficients of degree "m".
1030     *
1031     * @param c   An array of monomial coefficients (c.length > m) for the "X" dimension.
1032     * @param b   An array of monomial coefficients (b.length > m) for the "Y" dimension.
1033     * @param m   The degree of the monomial equation.
1034     * @param bin A set of binomial coefficients of at least size m+1.
1035     * @return A new 2D Bezier curve representation of the input monomial equation
1036     *         coefficients.
1037     */
1038    private BasicNurbsCurve monomial2Bezier(double[] c, double[] b, int m, BinomialCoef bin) {
1039        
1040        //  Form the new control points using Equation 12 from the reference:
1041        //  qveci = 1/(m,n)*sum_{j=0}^{i}{ (m-j, i-j) * dvecj }
1042        //  dvecj = [cj, bj]
1043        FastTable<ControlPoint> cpLst = FastTable.newInstance();
1044        for (int i = 0; i <= m; ++i) {
1045            double invBmi = 1. / bin.get(m, i);
1046            Point qi = Point.valueOf(0, 0);
1047            for (int j = 0; j <= i; ++j) {
1048                double B = invBmi * bin.get(m - j, i - j);
1049                Point qij = Point.valueOf(c[j]*B, b[j]*B);
1050                qi = qi.plus(qij);
1051            }
1052            double ch = chord.getValue(SI.METER);
1053            double qixh = ch*qi.getValue(0);    //  qi in the xhat direction.
1054            double qiyh = ch*qi.getValue(1);    //  qi in the yhat direction.
1055            Point cpp = Point.valueOf(xhat.times(qixh).plus(yhat.times(qiyh))).to(getUnit());
1056            ControlPoint cpi = ControlPoint.valueOf(cpp, 1);
1057            cpLst.add(cpi);
1058        }
1059        
1060        //  Create a Bezier curve knot vector.
1061        KnotVector bezierKV = KnotVector.bezierKnotVector(m);
1062        
1063        //  Construct the new Bezier curve as a NURBS curve.
1064        BasicNurbsCurve newCrv = BasicNurbsCurve.newInstance(cpLst, bezierKV);
1065        
1066        return newCrv;
1067    }
1068    
1069    /**
1070     * Returns a copy of this {@link BasicCSTCurve} instance
1071     * {@link javolution.context.AllocatorContext allocated} by the calling thread
1072     * (possibly on the stack).
1073     *
1074     * @return an identical and independent copy of this curve.
1075     */
1076    @Override
1077    public BasicCSTCurve copy() {
1078        return copyOf(this);
1079    }
1080
1081    /**
1082     * Return a copy of this object with any transformations or subranges removed
1083     * (applied).
1084     *
1085     * @return A copy of this object with any transformations or subranges removed
1086     *         (applied).
1087     */
1088    @Override
1089    public BasicCSTCurve copyToReal() {
1090        return copy();
1091    }
1092
1093    /**
1094     * Return an immutable version of this CST curve. This implementation simply returns
1095     * this BasicCSTCurve instance.
1096     *
1097     * @return an immutable version of this curve.
1098     */
1099    @Override
1100    public BasicCSTCurve immutable() {
1101        return this;
1102    }
1103
1104    /**
1105     * Compares this BasicCSTCurve against the specified object for strict equality (same
1106     * values and same units).
1107     *
1108     * @param obj the object to compare with.
1109     * @return <code>true</code> if this point is identical to that point;
1110     *         <code>false</code> otherwise.
1111     */
1112    @Override
1113    public boolean equals(Object obj) {
1114        if (this == obj)
1115            return true;
1116        if ((obj == null) || (obj.getClass() != this.getClass()))
1117            return false;
1118
1119        BasicCSTCurve that = (BasicCSTCurve)obj;
1120        return this.offsetTE_c == that.offsetTE_c
1121                && this.Cf.equals(that.Cf)
1122                && this.Sf.equals(that.Sf)
1123                && this.chord.equals(that.chord)
1124                && this.origin.equals(that.origin)
1125                && this.xhat.equals(that.xhat)
1126                && this.yhat.equals(that.yhat)
1127                && super.equals(obj);
1128    }
1129
1130    /**
1131     * Returns the hash code for this parameter.
1132     *
1133     * @return the hash code value.
1134     */
1135    @Override
1136    public int hashCode() {
1137        return 31*super.hashCode() 
1138                + 31*makeVarCode(offsetTE_c) 
1139                + Objects.hash(Cf, Sf, chord, origin, xhat, yhat);
1140    }
1141
1142    private static int makeVarCode(double value) {
1143        long bits = Double.doubleToLongBits(value);
1144        int var_code = (int)(bits ^ (bits >>> 32));
1145        return var_code;
1146    }
1147
1148    /**
1149     * Recycles a <code>BasicCSTCurve</code> instance immediately (on the stack when
1150     * executing in a StackContext).
1151     *
1152     * @param instance The instance to be recycled.
1153     */
1154    public static void recycle(BasicCSTCurve instance) {
1155        FACTORY.recycle(instance);
1156    }
1157
1158    /**
1159     * Holds the default XML representation for this object.
1160     */
1161    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
1162    protected static final XMLFormat<BasicCSTCurve> XML = new XMLFormat<BasicCSTCurve>(BasicCSTCurve.class) {
1163
1164        @Override
1165        public BasicCSTCurve newInstance(Class<BasicCSTCurve> cls, XMLFormat.InputElement xml) throws XMLStreamException {
1166            return FACTORY.object();
1167        }
1168
1169        @Override
1170        public void read(XMLFormat.InputElement xml, BasicCSTCurve obj) throws XMLStreamException {
1171            double offsetTE_c = xml.getAttribute("offsetTE_c", 0.0);
1172
1173            CSTCurve.XML.read(xml, obj);     // Call parent read.
1174            
1175            NurbsCurve pmap = xml.get("pmap");
1176            CSTClassFunction Cf = xml.get("Cf");
1177            CSTShapeFunction Sf = xml.get("Sf");
1178            Parameter<Length> chord = xml.get("Chord");
1179            Point origin = xml.get("Origin");
1180            Vector<Dimensionless> xhat = xml.get("xhat");
1181            Vector<Dimensionless> yhat = xml.get("yhat");
1182
1183            obj.pmap = pmap;
1184            obj.Cf = Cf;
1185            obj.Sf = Sf;
1186            obj.offsetTE_c = offsetTE_c;
1187            obj.chord = chord;
1188            obj.origin = origin;
1189            obj.xhat = xhat;
1190            obj.yhat = yhat;
1191            obj.calcBoundsMinMax();
1192
1193        }
1194
1195        @Override
1196        public void write(BasicCSTCurve obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1197            xml.setAttribute("offsetTE_c", obj.offsetTE_c);
1198
1199            CSTCurve.XML.write(obj, xml);    // Call parent write.
1200            
1201            xml.add(obj.pmap, "pmap");
1202            xml.add(obj.Cf, "Cf");
1203            xml.add(obj.Sf, "Sf");
1204            xml.add(obj.chord, "Chord");
1205            xml.add(obj.origin, "Origin");
1206            xml.add(obj.xhat, "xhat");
1207            xml.add(obj.yhat, "yhat");
1208        }
1209    };
1210
1211    ///////////////////////
1212    // Factory creation. //
1213    ///////////////////////
1214    private BasicCSTCurve() { }
1215
1216    @SuppressWarnings("unchecked")
1217    private static final ObjectFactory<BasicCSTCurve> FACTORY = new ObjectFactory<BasicCSTCurve>() {
1218        @Override
1219        protected BasicCSTCurve create() {
1220            return new BasicCSTCurve();
1221        }
1222
1223        @Override
1224        protected void cleanup(BasicCSTCurve obj) {
1225            obj.reset();
1226            obj.pmap = null;
1227            obj.Cf = null;
1228            obj.Sf = null;
1229            obj.chord = null;
1230            obj.origin = null;
1231            obj.xhat = null;
1232            obj.yhat = null;
1233            obj._boundsMax = null;
1234            obj._boundsMin = null;
1235        }
1236    };
1237
1238    @SuppressWarnings("unchecked")
1239    private static BasicCSTCurve copyOf(BasicCSTCurve original) {
1240        BasicCSTCurve obj = FACTORY.object();
1241        obj.pmap = original.pmap.copy();
1242        obj.Cf = original.Cf.copy();
1243        obj.Sf = original.Sf.copy();
1244        obj.offsetTE_c = original.offsetTE_c;
1245        obj.origin = original.origin.copy();
1246        obj.chord = original.chord.copy();
1247        obj.xhat = original.xhat.copy();
1248        obj.yhat = original.yhat.copy();
1249        obj._boundsMax = original._boundsMax.copy();
1250        obj._boundsMin = original._boundsMin.copy();
1251        original.copyState(obj);
1252        return obj;
1253    }
1254
1255}