001/**
002 * SubrangeSurface -- A Surface that is a subrange on another parametric surface.
003 *
004 * Copyright (C) 2013-2015, 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.*;
021import jahuwaldt.js.param.Parameter;
022import jahuwaldt.tools.math.MathTools;
023import java.text.MessageFormat;
024import java.util.List;
025import java.util.Objects;
026import static java.util.Objects.requireNonNull;
027import javax.measure.converter.ConversionException;
028import javax.measure.quantity.Dimensionless;
029import javax.measure.quantity.Length;
030import javax.measure.unit.SI;
031import javax.measure.unit.Unit;
032import javax.swing.event.ChangeEvent;
033import javax.swing.event.ChangeListener;
034import javolution.context.ObjectFactory;
035import javolution.context.StackContext;
036import javolution.lang.Immutable;
037import javolution.text.Text;
038import javolution.text.TextBuilder;
039import javolution.xml.XMLFormat;
040import javolution.xml.stream.XMLStreamException;
041
042/**
043 * A subrange or trimmed {@link Surface} that is defined by a set of four 2D boundary
044 * curves that define the 4 parametric edges of the trimmed surface on the child surface.
045 *
046 * <p> Modified by: Joseph A. Huwaldt </p>
047 *
048 * @author Joseph A. Huwaldt, Date: April 8, 2013
049 * @version November 27, 2015
050 */
051@SuppressWarnings({"serial", "CloneableImplementsClone"})
052public class SubrangeSurface extends AbstractSurface<SubrangeSurface> implements Subrange<Surface> {
053
054    /**
055     * The number of points used when estimating the twist vector and when estimating the
056     * min/max bounding box for this surface.
057     */
058    private static final int NPTS = 21;
059
060    /**
061     * The parametric geometry object that this surface is located on.
062     */
063    private Surface _child;
064
065    /**
066     * The parametric surface defining the parametric range for this surface.
067     */
068    private Surface _u;
069
070    /**
071     * Reference to a change listener for this object's child geometry.
072     */
073    private ChangeListener _childChangeListener = new MyChangeListener(this);
074
075    /**
076     * The minimum bounding point for this subrange surface.
077     */
078    private transient Point _boundsMin;
079
080    /**
081     * The maximum bounding point for this subrange surface.
082     */
083    private transient Point _boundsMax;
084
085    // The following are cached to improve performance.
086    private transient double _s = -1, _t = -1;
087    private transient double _uA = 1;         //  uA = uS*uT (product of parametric arc lengths along local s & t directions.
088    private Boolean _isDegenerate;
089
090    /**
091     * Returns a {@link SubrangeSurface} instance referring to the specified
092     * {@link Surface} and the supplied 2D surface in parametric space which maps the
093     * SubrangeSurface to the child Surface. The mapping surface in parametric space must
094     * be in units of meters and be in the range 0,0 to 1,1.
095     *
096     * @param child The {@link Surface} that this surface is subranged onto (may not be
097     *              <code>null</code>).
098     * @param par   A 2D surface in parametric space (between 0,0 and 1,1 in meters) that
099     *              maps the parametric positions from the subrange surface to the child
100     *              surface. If <code>null</code> is passed, the full parametric boundary
101     *              of the child surface is used.
102     * @return the subrange surface having the specified mapping to the child surface.
103     * @throws DimensionException if the input parametric space surface does not have a
104     * parametric dimension equal to 2.
105     */
106    public static SubrangeSurface newInstance(Surface child, Surface par) {
107        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
108        if (par == null)
109            return SubrangeSurface.newInstance(child, 0, 0, 1, 1);
110
111        //  Handle the user inputting a subrange surface on the child surface.
112        if (par instanceof SubrangeSurface && ((SubrangeSurface)par).getChild() == child)
113            par = ((SubrangeSurface)par).getParPosition();
114
115        int pDim = 2;
116        if (par.getPhyDimension() != pDim)
117            throw new DimensionException(RESOURCES.getString("scIncParDim"));
118
119        //  Convert the parametric surface to METER units (in case it isn't already).
120        par = par.to(SI.METER);
121
122        SubrangeSurface obj = FACTORY.object();
123        obj._child = child;
124        obj._u = par;
125        obj.calcBoundsMinMax();
126
127        if (!(par instanceof Immutable))
128            par.addChangeListener(obj._childChangeListener);
129        if (!(child instanceof Immutable))
130            child.addChangeListener(obj._childChangeListener);
131
132        return obj;
133    }
134
135    /**
136     * Returns a {@link SubrangeSurface} instance referring to the specified
137     * {@link Surface} and the supplied boundary location curves in parametric space. The
138     * boundary curves in parametric space must be in units of meters and be in the range
139     * 0,0 to 1,1. The start points of the s0,t0 curves must be coincident. The start of
140     * the s1 curve and end of the t0 curve must be coincident. The end of the s1 curve
141     * and end of the t1 curve must be coincident. And, the end of the s0 curve and start
142     * of the t1 curve must be coincident. The corner points of the parametric curves are
143     * checked for proper coincidence.
144     *
145     * @param child The {@link Surface} that this surface is subranged onto (may not be
146     *              <code>null</code>).
147     * @param s0    A 2D curve of the parametric position (between 0,0 and 1,1 in meters)
148     *              along each parametric dimension or a subrange curve on the child
149     *              surface that serves as the s=0 boundary for the subrange surface.
150     * @param t0    A 2D curve of the parametric position (between 0,0 and 1,1 in meters)
151     *              along each parametric dimension or a subrange curve on the child
152     *              surface that serves as the t=0 boundary for the subrange surface.
153     * @param s1    A 2D curve of the parametric position (between 0,0 and 1,1 in meters)
154     *              along each parametric dimension that serves as the s=1 boundary for
155     *              the subrange surface.
156     * @param t1    A 2D curve of the parametric position (between 0,0 and 1,1 in meters)
157     *              along each parametric dimension or a subrange curve on the child
158     *              surface that serves as the t=1 boundary for the subrange surface.
159     * @param tol   A tolerance (in parameter space) for how closely the corner points
160     *              must match.
161     * @return the subrange surface having the specified boundary curves.
162     * @throws DimensionException if the input boundary curves are not subranges on child
163     * and their parametric dimensions are not equal to 2.
164     */
165    public static SubrangeSurface newInstance(Surface child, Curve s0, Curve t0, Curve s1, Curve t1, double tol) {
166        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
167        requireNonNull(s0, MessageFormat.format(RESOURCES.getString("paramNullErr"), "s0"));
168        requireNonNull(s1, MessageFormat.format(RESOURCES.getString("paramNullErr"), "s1"));
169        requireNonNull(t0, MessageFormat.format(RESOURCES.getString("paramNullErr"), "t0"));
170        requireNonNull(t1, MessageFormat.format(RESOURCES.getString("paramNullErr"), "t1"));
171
172        //  Handle the user inputting subrange curves on the child surface.
173        if (s0 instanceof SubrangeCurve && ((SubrangeCurve)s0).getChild() == child)
174            s0 = ((SubrangeCurve)s0).getParPosition();
175        if (s1 instanceof SubrangeCurve && ((SubrangeCurve)s1).getChild() == child)
176            s1 = ((SubrangeCurve)s1).getParPosition();
177        if (t0 instanceof SubrangeCurve && ((SubrangeCurve)t0).getChild() == child)
178            t0 = ((SubrangeCurve)t0).getParPosition();
179        if (t1 instanceof SubrangeCurve && ((SubrangeCurve)t1).getChild() == child)
180            t1 = ((SubrangeCurve)t1).getParPosition();
181
182        int pDim = child.getParDimension();
183        if (s0.getPhyDimension() != pDim)
184            throw new DimensionException(RESOURCES.getString("scIncParDim"));
185        if (s1.getPhyDimension() != pDim)
186            throw new DimensionException(RESOURCES.getString("scIncParDim"));
187        if (t0.getPhyDimension() != pDim)
188            throw new DimensionException(RESOURCES.getString("scIncParDim"));
189        if (t1.getPhyDimension() != pDim)
190            throw new DimensionException(RESOURCES.getString("scIncParDim"));
191
192        //  Convert the parametric curves to METER units (in case they aren't already).
193        s0 = s0.to(SI.METER);
194        s1 = s1.to(SI.METER);
195        t0 = t0.to(SI.METER);
196        t1 = t1.to(SI.METER);
197
198        //  Convert the boundary curves into a parametric surface.
199        NurbsSurface par = SurfaceFactory.createTFISurface(s0, t0, s1, t1, Parameter.valueOf(tol, SI.METER));
200        SubrangeSurface obj = FACTORY.object();
201        obj._child = child;
202        obj._u = par;
203        obj.calcBoundsMinMax();
204
205        if (!(child instanceof Immutable))
206            child.addChangeListener(obj._childChangeListener);
207
208        return obj;
209    }
210
211    /**
212     * Returns a SubrangeSurface on the surface <code>child</code> that covers the range
213     * of parametric positions from <code>s0,t0</code> to <code>s1,t1</code>.
214     *
215     * @param child The {@link Surface} object that this surface is subranged onto (may
216     *              not be <code>null</code>).
217     * @param s0    The parametric position on the child surface that should form the s=0
218     *              edge of the subrange surface.
219     * @param t0    The parametric position on the child surface that should form the t=0
220     *              edge of the subrange surface.
221     * @param s1    The parametric position on the child surface that should form the s=1
222     *              edge of the subrange surface.
223     * @param t1    The parametric position on the child surface that should form the t=1
224     *              edge of the subrange surface.
225     * @return the subrange surface having the specified range of parametric values on the
226     *         child surface.
227     */
228    public static SubrangeSurface newInstance(Surface child, double s0, double t0, double s1, double t1) {
229        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
230
231        //  Form the desired boundary parametric curves.
232        Point p00 = Point.valueOf(s0, t0);
233        Point p01 = Point.valueOf(s0, t1);
234        Point p11 = Point.valueOf(s1, t1);
235        Point p10 = Point.valueOf(s1, t0);
236        Curve s0crv = LineSeg.valueOf(p00, p01);
237        Curve t0crv = LineSeg.valueOf(p00, p10);
238        Curve s1crv = LineSeg.valueOf(p10, p11);
239        Curve t1crv = LineSeg.valueOf(p01, p11);
240
241        return SubrangeSurface.newInstance(child, s0crv, t0crv, s1crv, t1crv, GTOL);
242    }
243
244    //  A change listener that re-calculates the subrange bounds as well as
245    //  passing the child's change event on to any listeners.
246    private static class MyChangeListener extends ForwardingChangeListener {
247
248        private final SubrangeSurface target;
249
250        public MyChangeListener(SubrangeSurface geom) {
251            super(geom);
252            this.target = geom;
253        }
254
255        @Override
256        public void stateChanged(ChangeEvent e) {
257            //  Re-calculate the bounds of this subrange curve.
258            target.calcBoundsMinMax();
259            super.stateChanged(e);
260        }
261    }
262
263    /**
264     * Returns the child object this point is subranged onto.
265     */
266    @Override
267    public ParametricGeometry getChild() {
268        return _child;
269    }
270
271    /**
272     * Returns a 2D surface in parametric space which maps this surface's parametric
273     * positions to the child surface.
274     *
275     * @return A 2D surface in parametric space that maps this surface's parameters to the
276     *         child surface.
277     */
278    @Override
279    public Surface getParPosition() {
280        return _u;
281    }
282
283    /**
284     * Sets the range of parametric positions on the child object that this surface refers
285     * to. The parametric surface must be 2D, in units of meters and be in the range 0,0
286     * to 1,1.
287     *
288     * @param par The mapping of parametric positions (0,0 to 1,1) from this surface to
289     *            the child surface. May not be null.
290     */
291    @Override
292    public void setParPosition(Surface par) {
293        requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par"));
294
295        //  Handle the user inputting a subrange surface on the child surface.
296        if (par instanceof SubrangeSurface && ((SubrangeSurface)par).getChild() == _child)
297            par = ((SubrangeSurface)par).getParPosition();
298
299        int pDim = 2;
300        if (par.getPhyDimension() != pDim)
301            throw new DimensionException(RESOURCES.getString("scIncParDim"));
302
303        //  Convert the parametric surface to METER units (in case it isn't already).
304        par = par.to(SI.METER);
305
306        //  Change the parametric surface for this SubrangeSurface.
307        if (!(_u instanceof Immutable))
308            _u.removeChangeListener(_childChangeListener);
309
310        _u = par;
311
312        if (!(par instanceof Immutable))
313            par.addChangeListener(_childChangeListener);
314
315        calcBoundsMinMax();
316        _s = -1;
317        _t = -1;
318        _uA = 1;
319        _isDegenerate = null;
320
321        this.fireChangeEvent();
322    }
323
324    /**
325     * Returns the number of child-elements that make up this geometry element. This
326     * implementation returns the size of the child object.
327     * 
328     * @return The number of child-elements that make up this geometry element.
329     */
330    @Override
331    public int size() {
332        return _child.size();
333    }
334
335    /**
336     * Return the T=0 Boundary for this surface as a curve.
337     *
338     * @return The T=0 Boundary for this surface as a curve.
339     */
340    @Override
341    public Curve getT0Curve() {
342        return SubrangeCurve.newInstance(_child, _u.getT0Curve());
343    }
344
345    /**
346     * Return the T=1 Boundary for this surface as a curve.
347     *
348     * @return The T=1 Boundary for this surface as a curve.
349     */
350    @Override
351    public Curve getT1Curve() {
352        return SubrangeCurve.newInstance(_child, _u.getT1Curve());
353    }
354
355    /**
356     * Return the S=0 Boundary for this surface as a curve.
357     *
358     * @return The S=0 Boundary for this surface as a curve.
359     */
360    @Override
361    public Curve getS0Curve() {
362        return SubrangeCurve.newInstance(_child, _u.getS0Curve());
363    }
364
365    /**
366     * Return the S=1 Boundary for this surface as a curve.
367     *
368     * @return The S=1 Boundary for this surface as a curve.
369     */
370    @Override
371    public Curve getS1Curve() {
372        return SubrangeCurve.newInstance(_child, _u.getS1Curve());
373    }
374
375    /**
376     * Calculate a point on the surface for the given parametric position on the surface.
377     *
378     * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0
379     *          inclusive).
380     * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0
381     *          inclusive).
382     * @return The calculated point on the surface at the specified parameter values.
383     * @throws IllegalArgumentException if there is any problem with the parameter values.
384     */
385    @Override
386    public Point getRealPoint(double s, double t) {
387        validateParameter(s, t);
388        Point sT = _u.getRealPoint(s, t);   //  Convert from 0-1 to parametric position range of subrange.
389        return _child.getRealPoint(sT);
390    }
391
392    /**
393     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
394     * respect to parametric s-position on the surface for the given parametric position
395     * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>.
396     * <p>
397     * Example:<br>
398     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br>
399     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc.
400     * </p>
401     *
402     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
403     *               1.0 inclusive).
404     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
405     *               1.0 inclusive).
406     * @param grade  The maximum grade to calculate the uA-derivatives for (1=1st
407     *               derivative, 2=2nd derivative, etc)
408     * @param scaled Pass <code>true</code> for properly scaled derivatives or
409     *               <code>false</code> if the magnitude of the derivative vector is not
410     *               required -- this sometimes results in faster calculation times.
411     * @return A list of s-derivatives up to the specified grade of the surface at the
412     *         specified parametric position.
413     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
414     * invalid.
415     */
416    @Override
417    public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) {
418
419        Point sT = _u.getRealPoint(s, t);   //  Convert from 0-1 to parametric position range of subrange.
420
421        //  Calculate the derivatives of the child object.
422        List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade);
423        Point origin = Point.valueOf(dersLst.get(0).get(0));
424
425        //  Have to account for derivatives in 2-dimensions.
426        //  Get the direction cosines of the parametric surface S tangent vector.
427        Vector<Dimensionless> utangent = _u.getSDerivative(s, t, 1).toUnitVector();
428        double ks = utangent.get(Vector.X).getValue();
429        double kt = utangent.get(Vector.Y).getValue();
430
431        //  Get the surface derivatives in the s and t directions.
432        List<Vector<Length>> dP_dss = dersLst.get(0);
433        List<Vector<Length>> dP_dts = dersLst.get(1);
434
435        //  Create the output array and put in the surface/curve point.
436        List<Vector<Length>> ders = dP_dss;
437
438        //  Use the direction cosines to combine the surface s & t direction
439        //  derivatives into the parametric surface's s direction: dP_dps = ks*dP_dss + kt*dP_dts
440        for (int i = 1; i <= grade; ++i)
441            ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt)));
442
443        if (scaled) {
444            //  Scale all the derivatives by the ratio of the parametric distance in the s & t
445            //  directions to the underlying surface parametric range (uS*uT/(1*1)).
446            double uA = calcParametricArcLengthProduct(s, t);
447            double f = 1;
448            for (int i = 1; i <= grade; ++i) {
449                f *= uA;
450                ders.set(i, ders.get(i).times(f));
451            }
452        }
453
454        //  Change the derivative vector origin to be subranges of this surface.
455        for (int i = 0; i <= grade; ++i)
456            ders.get(i).setOrigin(origin);
457
458        return ders;
459    }
460
461    /**
462     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
463     * respect to parametric t-position on the surface for the given parametric position
464     * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>.
465     * <p>
466     * Example:<br>
467     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br>
468     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc.
469     * </p>
470     *
471     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
472     *               1.0 inclusive).
473     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
474     *               1.0 inclusive).
475     * @param grade  The maximum grade to calculate the v-derivatives for (1=1st
476     *               derivative, 2=2nd derivative, etc)
477     * @param scaled Pass <code>true</code> for properly scaled derivatives or
478     *               <code>false</code> if the magnitude of the derivative vector is not
479     *               required -- this sometimes results in faster calculation times.
480     * @return A list of t-derivatives up to the specified grade of the surface at the
481     *         specified parametric position.
482     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
483     * invalid.
484     */
485    @Override
486    public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) {
487
488        Point sT = _u.getRealPoint(s, t);   //  Convert from 0-1 to parametric position range of subrange.
489
490        //  Calculate the derivatives of the child object.
491        List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade);
492        Point origin = Point.valueOf(dersLst.get(0).get(0));
493
494        //  Have to account for derivatives in 2-dimensions.
495        //  Get the direction cosines of the parametric surface T tangent vector.
496        Vector<Dimensionless> utangent = _u.getTDerivative(s, t, 1).toUnitVector();
497        double ks = utangent.get(Vector.X).getValue();
498        double kt = utangent.get(Vector.Y).getValue();
499
500        //  Get the surface derivatives in the s and t directions.
501        List<Vector<Length>> dP_dss = dersLst.get(0);
502        List<Vector<Length>> dP_dts = dersLst.get(1);
503
504        //  Create the output array and put in the surface/curve point.
505        List<Vector<Length>> ders = dP_dss;
506
507        //  Use the direction cosines to combine the surface s & t direction
508        //  derivatives into the parametric surface's s direction: dP_dps = ks*dP_dss + kt*dP_dts
509        for (int i = 1; i <= grade; ++i)
510            ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt)));
511
512        if (scaled) {
513            //  Scale all the derivatives by the ratio of the parametric distance in the s & t
514            //  directions to the underlying surface parametric range (uS*uT/(1*1)).
515            double uA = calcParametricArcLengthProduct(s, t);
516            double f = 1;
517            for (int i = 1; i <= grade; ++i) {
518                f *= uA;
519                ders.set(i, ders.get(i).times(f));
520            }
521        }
522
523        //  Change the derivative vector origin to be subranges of this surface.
524        for (int i = 0; i <= grade; ++i)
525            ders.get(i).setOrigin(origin);
526
527        return ders;
528    }
529
530    /**
531     * Method that calculates and returns the product of the local parametric arc lengths
532     * in the s & t directions: uA = uS*uT.
533     */
534    private double calcParametricArcLengthProduct(double s, double t) {
535        if (MathTools.isApproxEqual(s, _s, TOL_ST) && MathTools.isApproxEqual(t, _t, TOL_ST))
536            return _uA;
537
538        StackContext.enter();
539        try {
540            SubrangeCurve scrv = _u.getSCurve(s);
541            double uS = scrv.getArcLength(GTOL).getValue();
542            SubrangeCurve tcrv = _u.getTCurve(t);
543            double uT = tcrv.getArcLength(GTOL).getValue();
544            _uA = uS * uT;
545            _s = s;
546            _t = t;
547        } finally {
548            StackContext.exit();
549        }
550
551        return _uA;
552    }
553
554    /**
555     * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the
556     * specified position on this surface.
557     *
558     * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to
559     *          1.0 inclusive).
560     * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to
561     *          1.0 inclusive).
562     * @return The twist vector of this surface at the specified parametric position.
563     * @throws IllegalArgumentException if the parameter values are invalid.
564     */
565    @Override
566    public Vector<Length> getTwistVector(double s, double t) {
567        validateParameter(s, t);
568
569        StackContext.enter();
570        try {
571            //  Create a point string to hold the derivatives in the s-direction.
572            PointString<Point> sDers = PointString.newInstance();
573
574            //  Space out some points to use for exracting derivatives along a constant-s line.
575            List<Double> spacing = GridSpacing.linear(NPTS);
576
577            //  Insert in the input t position to the list.
578            for (int i = 1; i < NPTS; ++i) {
579                if (spacing.get(i) > t) {
580                    if (spacing.get(i - 1) != t)
581                        spacing.add(i, t);
582                    break;
583                }
584            }
585
586            //  Compute the s-direction derivatives at each of the t-positions for the input "s" position.
587            for (Double tpos : spacing) {
588                Vector<Length> der = this.getSDerivative(s, tpos, 1);
589                sDers.add(Point.valueOf(der));
590            }
591
592            //  Fit a curve to the derivative string and find the t-direction derivative at the input "t" position.
593            BasicNurbsCurve tCrv = CurveFactory.fitPoints(3, sDers);
594            Vector<Length> tvec = tCrv.getSDerivative(t, 1);
595            tvec.setOrigin(getRealPoint(s, t));
596
597            return StackContext.outerCopy(tvec);
598
599        } finally {
600            StackContext.exit();
601        }
602    }
603
604    /**
605     * Return a new surface that is identical to this one, but with the S-parameterization
606     * reversed.
607     *
608     * @return A new surface that is identical to this one, but with the
609     *         S-parameterization reversed.
610     * @see #reverseT
611     */
612    @Override
613    public SubrangeSurface reverseS() {
614        Surface u = _u.reverseS();
615
616        SubrangeSurface srf = SubrangeSurface.newInstance(_child, u);
617        copyState(srf); //  Copy over the super-class state for this surface to the new one.
618
619        return srf;
620    }
621
622    /**
623     * Return a new surface that is identical to this one, but with the T-parameterization
624     * reversed.
625     *
626     * @return A new surface that is identical to this one, but with the
627     *         T-parameterization reversed.
628     * @see #reverseS
629     */
630    @Override
631    public SubrangeSurface reverseT() {
632        Surface u = _u.reverseT();
633
634        SubrangeSurface srf = SubrangeSurface.newInstance(_child, u);
635        copyState(srf); //  Copy over the super-class state for this surface to the new one.
636
637        return srf;
638    }
639
640    /**
641     * Return a new surface that is identical to this one but with the transpose of the
642     * parameterization of this surface. The S and T directions will be swapped.
643     *
644     * @return A new surface that is identical to this one but with the transpose of the
645     *         parameterization of this surface.
646     * @see #reverseT
647     * @see #reverseS
648     */
649    @Override
650    public SubrangeSurface transpose() {
651        Surface u = _u.transpose();
652
653        SubrangeSurface srf = SubrangeSurface.newInstance(_child, u);
654        copyState(srf); //  Copy over the super-class state for this surface to the new one.
655
656        return srf;
657    }
658
659    /**
660     * Split this {@link SubrangeSurface} at the specified parametric S-position returning
661     * a list containing two new surfaces (a lower surface with smaller S-parametric
662     * positions than "s" and an upper surface with larger S-parametric positions).
663     *
664     * @param s The S-parametric position where this surface should be split (must not be
665     *          0 or 1!).
666     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
667     *         surface.
668     */
669    @Override
670    public GeomList<SubrangeSurface> splitAtS(double s) {
671        validateParameter(s, 0);
672        if (parNearEnds(s, TOL_ST))
673            throw new IllegalArgumentException(MessageFormat.format(
674                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
675
676        //  Split the parametric space surface.
677        GeomList<Surface> par = _u.splitAtS(s);
678
679        //  Split the surface.
680        SubrangeSurface srfL = SubrangeSurface.newInstance(_child, par.get(0));
681        SubrangeSurface srfU = SubrangeSurface.newInstance(_child, par.get(1));
682
683        //  Create the output list.
684        GeomList<SubrangeSurface> output = GeomList.valueOf(srfL, srfU);
685        return output;
686    }
687
688    /**
689     * Split this {@link SubrangeSurface} at the specified parametric T-position returning
690     * a list containing two new surfaces (a lower surface with smaller T-parametric
691     * positions than "t" and an upper surface with larger T-parametric positions).
692     *
693     * @param t The T-parametric position where this surface should be split (must not be
694     *          0 or 1!).
695     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
696     *         surface.
697     */
698    @Override
699    public GeomList<SubrangeSurface> splitAtT(double t) {
700        validateParameter(0, t);
701        if (parNearEnds(t, TOL_ST))
702            throw new IllegalArgumentException(MessageFormat.format(
703                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
704
705        //  Split the parametric space surface.
706        GeomList<Surface> par = _u.splitAtT(t);
707
708        //  Split the surface.
709        SubrangeSurface srfL = SubrangeSurface.newInstance(_child, par.get(0));
710        SubrangeSurface srfU = SubrangeSurface.newInstance(_child, par.get(1));
711
712        //  Create the output list.
713        GeomList<SubrangeSurface> output = GeomList.valueOf(srfL, srfU);
714        return output;
715    }
716
717    /**
718     * Returns transformed version of this element. The returned object implements
719     * {@link GeomTransform} and contains this element as a child.
720     *
721     * @param transform The transformation to apply to this geometry. May not be null.
722     * @return A new triangle that is identical to this one with the specified
723     *         transformation applied.
724     * @throws DimensionException if this point is not 3D.
725     */
726    @Override
727    public SubrangeSurface getTransformed(GTransform transform) {
728        requireNonNull(transform);
729        return SubrangeSurface.newInstance((Surface)_child.getTransformed(transform), _u);
730    }
731
732    /**
733     * Return the coordinate point representing the minimum bounding box corner of this
734     * geometry element (e.g.: min X, min Y, min Z).
735     *
736     * @return The minimum bounding box coordinate for this geometry element.
737     * @throws IndexOutOfBoundsException if this list contains no elements.
738     */
739    @Override
740    public Point getBoundsMin() {
741        return _boundsMin;
742    }
743
744    /**
745     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
746     * X, max Y, max Z).
747     *
748     * @return The maximum bounding box coordinate for this geometry element.
749     * @throws IndexOutOfBoundsException if this list contains no elements.
750     */
751    @Override
752    public Point getBoundsMax() {
753        return _boundsMax;
754    }
755
756    /**
757     * Calculate the minimum & maximum bounding box corner points of this geometry
758     * element.
759     */
760    private void calcBoundsMinMax() {
761
762        StackContext.enter();
763        try {
764            //  Space out some points on the subrange surface.
765            List<Double> spacing = GridSpacing.linear(NPTS);
766            PointArray<SubrangePoint> parr = _u.extractGrid(GridRule.PAR, spacing, spacing, spacing, spacing);
767            PointArray<Point> arr = PointArray.newInstance();
768            for (int i = 0; i < NPTS; ++i) {
769                PointString<Point> str = PointString.newInstance();
770                for (int j = 0; j < NPTS; ++j) {
771                    Point ppnt = parr.get(i, j).copyToReal();
772                    str.add(_child.getRealPoint(ppnt));
773                }
774                arr.add(str);
775            }
776
777            //  Get the bounds of the grid of points.
778            _boundsMin = StackContext.outerCopy(arr.getBoundsMin());
779            _boundsMax = StackContext.outerCopy(arr.getBoundsMax());
780
781        } finally {
782            StackContext.exit();
783        }
784    }
785
786    /**
787     * Returns the number of physical dimensions of the geometry element. This
788     * implementation always returns the physical dimension of the underlying
789     * {@link Surface} objects.
790     *
791     * @return The number of physical dimensions of the geometry element.
792     */
793    @Override
794    public int getPhyDimension() {
795        return _child.getPhyDimension();
796    }
797
798    /**
799     * Return the equivalent of this surface converted to the specified number of physical
800     * dimensions. If the number of dimensions is greater than this element, then zeros
801     * are added to the additional dimensions. If the number of dimensions is less than
802     * this element, then the extra dimensions are simply dropped (truncated). If the new
803     * dimensions are the same as the dimension of this element, then this element is
804     * simply returned.
805     *
806     * @param newDim The dimension of the surface to return.
807     * @return The equivalent of this surface converted to the new dimensions.
808     */
809    @Override
810    public SubrangeSurface toDimension(int newDim) {
811        if (getPhyDimension() == newDim)
812            return this;
813
814        return SubrangeSurface.newInstance(_child.toDimension(newDim), _u);
815    }
816
817    /**
818     * Return a NURBS surface representation of this surface to within the specified
819     * tolerance.
820     *
821     * @param tol The greatest possible difference between this surface and the NURBS
822     *            representation returned. May not be null.
823     * @return A NURBS surface that represents this surface to within the specified
824     *         tolerance.
825     */
826    @Override
827    public NurbsSurface toNurbs(Parameter<Length> tol) {
828        requireNonNull(tol);
829        StackContext.enter();
830        try {
831            PointArray<SubrangePoint> arr = this.gridToTolerance(tol);
832
833            //  Fit a cubic NURBS surface to the points.
834            int sdeg = 3, tdeg = 3;
835            if (arr.size() <= tdeg)
836                tdeg = arr.size() - 1;
837            if (arr.get(0).size() <= sdeg)
838                sdeg = arr.get(0).size() - 1;
839            BasicNurbsSurface surface = SurfaceFactory.fitPoints(sdeg, tdeg, arr);
840            copyState(surface); //  Copy over the super-class state for this surface to the new one.
841
842            return StackContext.outerCopy(surface);
843
844        } finally {
845            StackContext.exit();
846        }
847    }
848
849    /**
850     * Returns the unit in which this surface is stated.
851     *
852     * @return The unit in which this surface is stated.
853     */
854    @Override
855    public Unit<Length> getUnit() {
856        return _child.getUnit();
857    }
858
859    /**
860     * Returns the equivalent to this surface but stated in the specified unit.
861     *
862     * @param unit the length unit of the surface to be returned.
863     * @return an equivalent to this surface but stated in the specified unit.
864     * @throws ConversionException if the the input unit is not a length unit.
865     */
866    @Override
867    public SubrangeSurface to(Unit<Length> unit) throws ConversionException {
868        if (unit.equals(getUnit()))
869            return this;
870
871        //  Convert the curves.
872        SubrangeSurface srf = SubrangeSurface.newInstance(_child.to(unit), _u);
873
874        return srf;
875    }
876
877    /**
878     * Return <code>true</code> if this SubrangeSurface contains valid and finite
879     * numerical components. A value of <code>false</code> will be returned if any of the
880     * member curves are not valid.
881     *
882     * @return true if this SubrangeSurface contains valid and finite numerical
883     *         components.
884     */
885    @Override
886    public boolean isValid() {
887        return _child.isValid() && _u.isValid();
888    }
889
890    /**
891     * Return <code>true</code> if this surface is degenerate (i.e.: has area less than
892     * the specified tolerance squared).
893     *
894     * @param tol The tolerance for determining if this surface is degenerate. May not be
895     *            null.
896     * @return true if this surface is degenerate.
897     */
898    @Override
899    public boolean isDegenerate(Parameter<Length> tol) {
900        requireNonNull(tol);
901        if (_isDegenerate != null)
902            return _isDegenerate;
903
904        _isDegenerate = _u.isDegenerate(tol);
905        if (_isDegenerate)
906            return true;
907        _isDegenerate = _child.isDegenerate(tol);
908        return _isDegenerate;
909    }
910
911    /**
912     * Compares the specified object with this <code>SubrangeSurface</code> for equality.
913     * Returns true if and only if both surfaces are of the same type, have the same child
914     * surface, and both contain the same boundary curves in the same order.
915     *
916     * @param obj the object to compare with.
917     * @return <code>true</code> if this surface is identical to that surface;
918     *         <code>false</code> otherwise.
919     */
920    @Override
921    public boolean equals(Object obj) {
922        if (this == obj)
923            return true;
924        if ((obj == null) || (obj.getClass() != this.getClass()))
925            return false;
926
927        SubrangeSurface that = (SubrangeSurface)obj;
928        return this._child.equals(that._child)
929                && this._u.equals(that._u)
930                && super.equals(obj);
931    }
932
933    /**
934     * Returns the hash code for this <code>SubrangeSurface</code>.
935     *
936     * @return the hash code value.
937     */
938    @Override
939    public int hashCode() {
940        return 31*super.hashCode() + Objects.hash(_u, _child);
941    }
942
943    /**
944     * Return a copy of this object with any transformations or subranges removed
945     * (applied). This method is not yet implemented and current returns a new
946     * SubrangeSurface with the child surface and subrange curves copied to real.
947     *
948     * @return A copy of this object with any transformations or subranges removed.
949     */
950    @Override
951    public SubrangeSurface copyToReal() {
952        //  TODO: Add support for this.
953        //throw new UnsupportedOperationException( RESOURCES.getString("scCopy2RealNotSupported") );
954        return SubrangeSurface.newInstance((Surface)_child.copyToReal(), (Surface)_u.copyToReal());
955    }
956
957    /**
958     * Returns a copy of this <code>SubrangeSurface</code> instance
959     * {@link javolution.context.AllocatorContext allocated} by the calling thread
960     * (possibly on the stack).
961     *
962     * @return an identical and independent copy of this object.
963     */
964    @Override
965    public SubrangeSurface copy() {
966        return copyOf(this);
967    }
968
969    /**
970     * Returns the text representation of this geometry element.
971     *
972     * @return The text representation of this geometry element.
973     */
974    @Override
975    public Text toText() {
976        TextBuilder tmp = TextBuilder.newInstance();
977        String className = this.getClass().getName();
978        tmp.append(className.substring(className.lastIndexOf(".") + 1));
979
980        tmp.append(": {child = {\n");
981        tmp.append(_child.toText());
982        tmp.append("}\n");
983        tmp.append(", u = {\n");
984        tmp.append(_u.toText());
985        tmp.append("}\n");
986
987        tmp.append("}");
988        Text txt = tmp.toText();
989        TextBuilder.recycle(tmp);
990        return txt;
991    }
992
993    /**
994     * Holds the default XML representation for this object.
995     */
996    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
997    protected static final XMLFormat<SubrangeSurface> XML = new XMLFormat<SubrangeSurface>(SubrangeSurface.class) {
998
999        @Override
1000        public SubrangeSurface newInstance(Class<SubrangeSurface> cls, XMLFormat.InputElement xml) throws XMLStreamException {
1001            return FACTORY.object();
1002        }
1003
1004        @Override
1005        public void read(XMLFormat.InputElement xml, SubrangeSurface obj) throws XMLStreamException {
1006            AbstractSurface.XML.read(xml, obj);     // Call parent read.
1007
1008            Surface par = xml.get("ParPos");
1009            obj._u = par;
1010            Surface child = xml.get("Child");
1011            obj._child = child;
1012            if (!(par instanceof Immutable))
1013                par.addChangeListener(obj._childChangeListener);
1014            if (!(child instanceof Immutable))
1015                child.addChangeListener(obj._childChangeListener);
1016            obj.calcBoundsMinMax();
1017
1018        }
1019
1020        @Override
1021        public void write(SubrangeSurface obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1022            AbstractSurface.XML.write(obj, xml);    // Call parent write.
1023
1024            xml.add(obj._u, "ParPos");
1025            xml.add(obj._child, "Child");
1026
1027        }
1028    };
1029
1030    //////////////////////
1031    // Factory Creation //
1032    //////////////////////
1033    /**
1034     * Do not allow the default constructor to be used except by subclasses.
1035     */
1036    protected SubrangeSurface() { }
1037
1038    private static final ObjectFactory<SubrangeSurface> FACTORY = new ObjectFactory<SubrangeSurface>() {
1039        @Override
1040        protected SubrangeSurface create() {
1041            return new SubrangeSurface();
1042        }
1043
1044        @Override
1045        protected void cleanup(SubrangeSurface obj) {
1046            obj.reset();
1047            if (!(obj._u instanceof Immutable))
1048                obj._u.removeChangeListener(obj._childChangeListener);
1049            obj._u = null;
1050            if (!(obj._child instanceof Immutable))
1051                obj._child.removeChangeListener(obj._childChangeListener);
1052            obj._child = null;
1053            obj._boundsMin = null;
1054            obj._boundsMax = null;
1055            obj._isDegenerate = null;
1056            obj._s = -1;
1057            obj._t = -1;
1058            obj._uA = 1;
1059        }
1060    };
1061
1062    /**
1063     * Recycles a SubrangeSurface instance immediately (on the stack when executing in a
1064     * StackContext).
1065     *
1066     * @param instance The instance to be recycled.
1067     */
1068    public static void recycle(SubrangeSurface instance) {
1069        FACTORY.recycle(instance);
1070    }
1071
1072    @SuppressWarnings("unchecked")
1073    private static SubrangeSurface copyOf(SubrangeSurface original) {
1074        SubrangeSurface obj = FACTORY.object();
1075        obj._child = original._child.copy();
1076        obj._u = original._u.copy();
1077        obj._boundsMax = original._boundsMax.copy();
1078        obj._boundsMin = original._boundsMin.copy();
1079        if (!(obj._u instanceof Immutable))
1080            obj._u.addChangeListener(obj._childChangeListener);
1081        if (!(obj._child instanceof Immutable))
1082            obj._child.addChangeListener(obj._childChangeListener);
1083        original.copyState(obj);
1084        return obj;
1085    }
1086
1087}