001/**
002 * TFISurface -- A transfinite interpolation surface defined by four boundary curves.
003 *
004 * Copyright (C) 2013-2017, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom;
019
020import geomss.geom.nurbs.BasicNurbsSurface;
021import geomss.geom.nurbs.NurbsSurface;
022import geomss.geom.nurbs.SurfaceFactory;
023import jahuwaldt.js.param.Parameter;
024import java.text.MessageFormat;
025import java.util.List;
026import java.util.Objects;
027import static java.util.Objects.isNull;
028import static java.util.Objects.requireNonNull;
029import javax.measure.converter.ConversionException;
030import javax.measure.quantity.Length;
031import javax.measure.unit.Unit;
032import javax.swing.event.ChangeListener;
033import javolution.context.ObjectFactory;
034import javolution.context.StackContext;
035import javolution.lang.Immutable;
036import javolution.text.Text;
037import javolution.text.TextBuilder;
038import javolution.util.FastTable;
039import javolution.xml.XMLFormat;
040import javolution.xml.stream.XMLStreamException;
041
042/**
043 * Represents a bi-linearly blended transfinite interpolation (TFI) or Coons patch surface
044 * defined from four boundary curves. The boundary curves must meet at each of the 4
045 * corners of the patch. It is assumed that all the input curves are oriented in an
046 * appropriate direction and order to create the desired TFI surface.
047 *
048 * <p> Modified by: Joseph A. Huwaldt </p>
049 *
050 * @author Joseph A. Huwaldt, Date: April 17, 2013
051 * @version October 29, 2017
052 */
053@SuppressWarnings({"serial", "CloneableImplementsClone"})
054public class TFISurface extends AbstractSurface<TFISurface> {
055
056    //  The four boundary curves
057    private Curve s0crv, s1crv;
058    private Curve t0crv, t1crv;
059
060    /**
061     * Reference to a change listener for this object's child curves.
062     */
063    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
064
065    /**
066     * Return a TFISurface made up of the specified boundary {@link Curve} objects. It is
067     * assumed that the boundary curves are ordered properly and that the end points are
068     * coincident. No check for this is made.
069     *
070     * @param s0 The curve that serves as the s=0 boundary of this surface. May not be
071     *           null.
072     * @param t0 The curve that serves as the t=0 boundary of this surface. May not be
073     *           null.
074     * @param s1 The curve that serves as the s=1 boundary of this surface. May not be
075     *           null.
076     * @param t1 The curve that serves as the t=1 boundary of this surface. May not be
077     *           null.
078     * @return The TFISurface with the specified boundary curves.
079     */
080    public static TFISurface newInstance(Curve s0, Curve t0, Curve s1, Curve t1) {
081        return newInstance(null, s0, t0, s1, t1);
082    }
083
084    /**
085     * Return a TFISurface made up of the specified boundary {@link Curve} objects. It is
086     * assumed that the boundary curves are ordered properly and that the end points are
087     * coincident. No check for this is made.
088     *
089     * @param name The name to be assigned to this surface (may be <code>null</code>).
090     * @param s0   The curve that serves as the s=0 boundary of this surface. May not be
091     *             null.
092     * @param t0   The curve that serves as the t=0 boundary of this surface. May not be
093     *             null.
094     * @param s1   The curve that serves as the s=1 boundary of this surface. May not be
095     *             null.
096     * @param t1   The curve that serves as the t=1 boundary of this surface. May not be
097     *             null.
098     * @return The TFISurface with the specified boundary curves.
099     */
100    public static TFISurface newInstance(String name, Curve s0, Curve t0, Curve s1, Curve t1) {
101
102        //  Make sure that each input curve is the same dimension.
103        int numDims = GeomUtil.maxPhyDimension(s0, t0, s1, t1);
104        s0 = s0.toDimension(numDims);
105        s1 = s1.toDimension(numDims);
106        t0 = t0.toDimension(numDims);
107        t1 = t1.toDimension(numDims);
108
109        //  Convert all the curves to the same units.
110        Unit<Length> unit = s0.getUnit();
111        s1 = s1.to(unit);
112        t0 = t0.to(unit);
113        t1 = t1.to(unit);
114
115        TFISurface o = FACTORY.object();
116        o.setName(name);
117        o.s0crv = s0;
118        o.s1crv = s1;
119        o.t0crv = t0;
120        o.t1crv = t1;
121
122        if (!(s0 instanceof Immutable))
123            s0.addChangeListener(o._childChangeListener);
124        if (!(s1 instanceof Immutable))
125            s1.addChangeListener(o._childChangeListener);
126        if (!(t0 instanceof Immutable))
127            t0.addChangeListener(o._childChangeListener);
128        if (!(t1 instanceof Immutable))
129            t1.addChangeListener(o._childChangeListener);
130
131        return o;
132    }
133
134    /**
135     * Returns the number of physical dimensions of the geometry element. This
136     * implementation always returns the physical dimension of the underlying
137     * {@link Curve} objects.
138     *
139     * @return The number of physical dimensions of the geometry element.
140     */
141    @Override
142    public int getPhyDimension() {
143        return s0crv.getPhyDimension();
144    }
145
146    /**
147     * Return the equivalent of this surface converted to the specified number of physical
148     * dimensions. If the number of dimensions is greater than this element, then zeros
149     * are added to the additional dimensions. If the number of dimensions is less than
150     * this element, then the extra dimensions are simply dropped (truncated). If the new
151     * dimensions are the same as the dimension of this element, then this element is
152     * simply returned.
153     *
154     * @param newDim The dimension of the surface to return.
155     * @return This surface converted to the new dimensions.
156     */
157    @Override
158    public TFISurface toDimension(int newDim) {
159        if (getPhyDimension() == newDim)
160            return this;
161
162        //  Convert the curves.
163        Curve ns0crv = s0crv.toDimension(newDim);
164        Curve ns1crv = s1crv.toDimension(newDim);
165        Curve nt0crv = t0crv.toDimension(newDim);
166        Curve nt1crv = t1crv.toDimension(newDim);
167
168        TFISurface srf = TFISurface.newInstance(getName(), ns0crv, ns1crv, nt0crv, nt1crv);
169        copyState(srf);
170
171        return srf;
172    }
173
174    /**
175     * Returns the number of child-elements that make up this geometry element. This
176     * implementation always returns 4 for the four boundary curves.
177     *
178     * @return The number of child-elements that make up this geometry element.
179     */
180    @Override
181    public int size() {
182        return 4;
183    }
184
185    /**
186     * Calculate a point on the surface for the given parametric position on the surface.
187     *
188     * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0
189     *          inclusive).
190     * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0
191     *          inclusive).
192     * @return The calculated point on the surface at the specified parameter values.
193     * @throws IllegalArgumentException if there is any problem with the parameter values.
194     */
195    @Override
196    public Point getRealPoint(double s, double t) {
197        validateParameter(s, t);
198        StackContext.enter();
199        try {
200            //  Reference:  http://en.wikipedia.org/wiki/Coons_surface
201            //  Lt = b0(t)*p(s,0) + b1(t)*p(s,1)
202            //  Ls = c0(s)*p(0,t) + c1(s)*p(1,t)
203            //  B = p00*c0(s)*b0(t) + p01*c1(s)*b0(t) + p10*c0(s)*b1(t) + p11*b1(t)*c1(s)
204            //  p = Lt + Ls - B
205
206            double b0t = 1 - t;
207            double b1t = t;
208            double c0s = 1 - s;
209            double c1s = s;
210            Point ps0 = t0crv.getRealPoint(s);
211            Point ps1 = t1crv.getRealPoint(s);
212            Point p0t = s0crv.getRealPoint(t);
213            Point p1t = s1crv.getRealPoint(t);
214
215            Point Lt = ps0.times(b0t).plus(ps1.times(b1t));
216            Point Ls = p0t.times(c0s).plus(p1t.times(c1s));
217            Point p00 = t0crv.getRealPoint(0);
218            Point p01 = t0crv.getRealPoint(1);
219            Point p10 = t1crv.getRealPoint(0);
220            Point p11 = t1crv.getRealPoint(1);
221            Point B = p00.times(c0s * b0t).plus(p01.times(c1s * b0t)).plus(p10.times(c0s * b1t)).plus(p11.times(c1s * b1t));
222
223            //  Now sum up the pieces.
224            Point p = Lt.plus(Ls).minus(B);
225            return StackContext.outerCopy(p);
226
227        } finally {
228            StackContext.exit();
229        }
230    }
231
232    /**
233     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
234     * respect to parametric s-position on the surface for the given parametric position
235     * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>.
236     * <p>
237     * Example:<br>
238     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br>
239     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc.
240     * </p>
241     *
242     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
243     *               1.0 inclusive).
244     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
245     *               1.0 inclusive).
246     * @param grade  The maximum grade to calculate the u-derivatives for (1=1st
247     *               derivative, 2=2nd derivative, etc)
248     * @param scaled Pass <code>true</code> for properly scaled derivatives or
249     *               <code>false</code> if the magnitude of the derivative vector is not
250     *               required -- this sometimes results in faster calculation times.
251     * @return A list of s-derivatives up to the specified grade of the surface at the
252     *         specified parametric position.
253     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
254     * invalid.
255     */
256    @Override
257    public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) {
258        validateParameter(s, t);
259        if (grade < 0)
260            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
261
262        //  Check for easy cases.
263        if (parNearStart(t, TOL_ST))
264            return t0crv.getSDerivatives(s, grade);
265        else if (parNearEnd(t, TOL_ST))
266            return t1crv.getSDerivatives(s, grade);
267
268        /*
269         P = Lt + Ls - B
270         Lt = b0(t)*P(s,0) + b1(t)*P(s,1)
271         Ls = c0(s)*P(0,t) + c1(s)*P(1,t)
272         B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s)
273         So:
274         dP/ds = d(Lt)/ds + d(Ls)/ds - d(B)/ds
275         Ps = dP/ds,  Lts = d(Lt)/ds, Lss = d(Ls)/ds, and Bs = d(B)/ds
276         Ps = Lts + Lss - Bs
277         Lts = b0(t)*Ps(s,0) + b1(t)*Ps(s,1)
278         Lss = P(0,t)*d(c0(s))/ds + P(1,t)*d(c1(s))/ds
279         c0ss = d(c0(s))/ds  and  c1ss = d(c1(s))/ds
280         Lss = P(0,t)*c0ss + P(1,t)*c1ss
281         Bs = P(0,0)*b0(t)*c0ss + P(0,1)*b0(t)*c1ss + P(1,0)*b1(t)*c0ss + P(1,1)*b1(t)*c1ss
282            
283         For higher derivatives, c0ss and c1ss = 0.
284         */
285        FastTable<Vector<Length>> ders = FastTable.newInstance();
286
287        //  Get the actual point on the surface.
288        Point Pst = getRealPoint(s, t);
289        ders.add(Pst.toGeomVector());
290
291        StackContext.enter();
292        try {
293            double b0t = 1 - t;
294            double b1t = t;
295            double c0ss = -1;
296            double c1ss = 1;
297
298            //  Determine derivatives in the S-direction at the t=0 and t=1 boundaries.
299            //  These are:  Ps(s,0) and Ps(s,1)
300            List<Vector<Length>> Pss0 = t0crv.getSDerivatives(s, grade);
301            List<Vector<Length>> Pss1 = t1crv.getSDerivatives(s, grade);
302
303            //  Determine the points on the s=0 and s=1 boundaries.
304            //  These are:  P(0,t) and P(1,t)
305            Point P0t = s0crv.getRealPoint(t);
306            Point P1t = s1crv.getRealPoint(t);
307
308            //  Get the corner points.
309            Point P00 = t0crv.getRealPoint(0);
310            Point P01 = t0crv.getRealPoint(1);
311            Point P10 = t1crv.getRealPoint(0);
312            Point P11 = t1crv.getRealPoint(1);
313
314            //  Combine the different elements to get the total interpolated derivatives.
315            for (int g = 1; g <= grade; ++g) {
316                Vector<Length> Pss0v = Pss0.get(g);
317                Vector<Length> Pss1v = Pss1.get(g);
318                Vector<Length> Lts = Pss0v.times(b0t).plus(Pss1v.times(b1t));
319                Point Lss = P0t.times(c0ss).plus(P1t.times(c1ss));
320                Point Bs = P00.times(c0ss * b0t);
321                Bs = Bs.plus(P01.times(c1ss * b0t));
322                Bs = Bs.plus(P10.times(c0ss * b1t));
323                Bs = Bs.plus(P11.times(c1ss * b1t));
324                Vector<Length> d = Lts.plus(Lss.minus(Bs).toGeomVector());
325                ders.add(StackContext.outerCopy(d));
326
327                //  Prepare for next grade (derivative).
328                c0ss = 0;
329                c1ss = 0;
330            }
331
332        } finally {
333            StackContext.exit();
334        }
335
336        //  Set the origin for the derivative vectors.
337        for (int i = 1; i <= grade; ++i) {
338            ders.get(i).setOrigin(Pst);
339        }
340
341        return ders;
342    }
343
344    /**
345     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
346     * respect to parametric t-position on the surface for the given parametric position
347     * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>.
348     * <p>
349     * Example:<br>
350     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br>
351     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc.
352     * </p>
353     *
354     * @param s      1st parametric dimension distance to calculate derivative for (0.0 to
355     *               1.0 inclusive).
356     * @param t      2nd parametric dimension distance to calculate derivative for (0.0 to
357     *               1.0 inclusive).
358     * @param grade  The maximum grade to calculate the v-derivatives for (1=1st
359     *               derivative, 2=2nd derivative, etc)
360     * @param scaled Pass <code>true</code> for properly scaled derivatives or
361     *               <code>false</code> if the magnitude of the derivative vector is not
362     *               required -- this sometimes results in faster calculation times.
363     * @return A list of t-derivatives up to the specified grade of the surface at the
364     *         specified parametric position.
365     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
366     * invalid.
367     */
368    @Override
369    public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) {
370        validateParameter(s, t);
371        if (grade < 0)
372            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
373
374        //  Check for easy cases.
375        if (parNearStart(s, TOL_ST))
376            return s0crv.getSDerivatives(t, grade);
377        else if (parNearEnd(s, TOL_ST))
378            return s1crv.getSDerivatives(t, grade);
379
380        /*
381         P = Lt + Ls - B
382         Lt = b0(t)*P(s,0) + b1(t)*P(s,1)
383         Ls = c0(s)*P(0,t) + c1(s)*P(1,t)
384         B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s)
385         So:
386         dP/dt = d(Lt)/dt + d(Ls)/dt - d(B)/dt
387         Pt = dP/dt,  Ltt = d(Lt)/dt, Lst = d(Ls)/dt, and Bt = d(B)/dt
388         Pt = Ltt + Lst - Bt
389         Ltt = d(b0(t))/dt*P(s,0) + d(b1(t))/dt*P(s,1)
390         b0tt = d(b0(t))/dt   and  b1tt = d(b1(t))/dt
391         Ltt = b0tt*P(s,0) + b1tt*P(s,1)
392         Lst = Pt(0,t)*c0(s) + Pt(1,t)*c1(s)
393         Bt = P(0,0)*b0tt*c0(s) + P(0,1)*b0tt*c1(s) + P(1,0)*b1tt*c0(s) + P(1,1)*b1tt*c1(s)
394            
395         For higher derivatives, b0tt and b1tt = 0.
396         */
397        FastTable<Vector<Length>> ders = FastTable.newInstance();
398
399        //  Get the actual point on the surface.
400        Point Pst = getRealPoint(s, t);
401        ders.add(Pst.toGeomVector());
402
403        StackContext.enter();
404        try {
405            double b0tt = -1;
406            double b1tt = 1;
407            double c0s = 1 - s;
408            double c1s = s;
409
410            //  Determine derivatives in the T-direction at the s=0 and s=1 boundaries.
411            //  These are:  Pt(0,t) and Pt(1,t)
412            List<Vector<Length>> Pt0t = s0crv.getSDerivatives(t, grade);
413            List<Vector<Length>> Pt1t = s1crv.getSDerivatives(t, grade);
414
415            //  Determine the points on the t=0 and t=1 boundaries.
416            //  These are:  P(s,0) and P(s,1)
417            Point Ps0 = t0crv.getRealPoint(s);
418            Point Ps1 = t1crv.getRealPoint(s);
419
420            //  Get the corner points.
421            Point P00 = t0crv.getRealPoint(0);
422            Point P01 = t0crv.getRealPoint(1);
423            Point P10 = t1crv.getRealPoint(0);
424            Point P11 = t1crv.getRealPoint(1);
425
426            //  Combine the different elements to get the total interpolated derivatives.
427            for (int g = 1; g <= grade; ++g) {
428                Vector<Length> Pt0tv = Pt0t.get(g);
429                Vector<Length> Pt1tv = Pt1t.get(g);
430                Vector<Length> Ltt = Pt0tv.times(c0s).plus(Pt1tv.times(c1s));
431                Point Lst = Ps0.times(b0tt).plus(Ps1.times(b1tt));
432                Point Bt = P00.times(c0s * b0tt);
433                Bt = Bt.plus(P01.times(c1s * b0tt));
434                Bt = Bt.plus(P10.times(c0s * b1tt));
435                Bt = Bt.plus(P11.times(c1s * b1tt));
436                Vector<Length> d = Ltt.plus(Lst.minus(Bt).toGeomVector());
437                ders.add(StackContext.outerCopy(d));
438
439                //  Prepare for next grade (derivative).
440                b0tt = 0;
441                b1tt = 0;
442            }
443
444        } finally {
445            StackContext.exit();
446        }
447
448        //  Set the origin for the derivative vectors.
449        for (int i = 1; i <= grade; ++i) {
450            ders.get(i).setOrigin(Pst);
451        }
452
453        return ders;
454    }
455
456    /**
457     * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the
458     * specified position on this surface.
459     *
460     * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to
461     *          1.0 inclusive).
462     * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to
463     *          1.0 inclusive).
464     * @return The twist vector of this surface at the specified parametric position.
465     * @throws IllegalArgumentException if the parameter values are invalid.
466     */
467    @Override
468    public Vector<Length> getTwistVector(double s, double t) {
469        validateParameter(s, t);
470
471        /*
472         P = Lt + Ls - B
473         Lt = b0(t)*P(s,0) + b1(t)*P(s,1)
474         Ls = c0(s)*P(0,t) + c1(s)*P(1,t)
475         B = P(0,0)*c0(s)*b0(t) + P(0,1)*c1(s)*b0(t) + P(1,0)*c0(s)*b1(t) + P(1,1)*b1(t)*c1(s)
476            
477         So:
478         dP/ds = d(Lt)/ds + d(Ls)/ds - d(B)/ds
479         Ps = dP/ds,  Lts = d(Lt)/ds, Lss = d(Ls)/ds, and Bs = d(B)/ds
480         Ps = Lts + Lss - Bs
481         Lts = b0(t)*Ps(s,0) + b1(t)*Ps(s,1)
482         Lss = P(0,t)*d(c0(s))/ds + P(1,t)*d(c1(s))/ds
483         c0ss = d(c0(s))/ds  and  c1ss = d(c1(s))/ds
484         Lss = P(0,t)*c0ss + P(1,t)*c1ss
485         Bs = P(0,0)*b0(t)*c0ss + P(0,1)*b0(t)*c1ss + P(1,0)*b1(t)*c0ss + P(1,1)*b1(t)*c1ss
486            
487         So:
488         d^2P/(ds*dt) = d(dP/ds)/dt = d(Ps)/dt = dPsdt = d(Lts)/dt + d(Lss)/dt - d(Bs)/dt = Ltst + Lsst - Bst
489         d(Lts)/dt = Ltst = d(b0(t))/dt*Ps(s,0) + d(b1(t))/dt*Ps(s,1)
490         b0tt = d(b0(t))/dt  and b1tt = d(b1(t))/dt
491         Ltst = Ps(s,0)*b0tt + Ps(s,1)*b1tt
492         d(Lss)/dt = Lsst = Pt(0,t)*c0ss + Pt(1,t)*c1ss
493         d(Bs)/dt = Bst = P(0,0)*b0tt*c0ss + P(0,1)*b0tt*c1ss + P(1,0)*b1tt*c0ss + P(1,1)*b1tt*c1ss
494            
495         */
496        StackContext.enter();
497        try {
498            Unit<Length> unit = getUnit();
499            double b0tt = -1;
500            double b1tt = 1;
501            double c0ss = -1;
502            double c1ss = 1;
503
504            //  Determine derivatives in the S-direction at the t=0 and t=1 boundaries.
505            //  These are:  Ps(s,0) and Ps(s,1)
506            Vector<Length> Pss0 = t0crv.getSDerivative(s, 1);
507            Vector<Length> Pss1 = t1crv.getSDerivative(s, 1);
508            Point nullPnt = Point.newInstance(getPhyDimension(), unit);
509            Pss0.setOrigin(nullPnt);
510            Pss1.setOrigin(nullPnt);
511
512            //  Determine derivatives in the T-direction at the s=0 and s=1 boundaries.
513            //  These are:  Pt(0,t) and Pt(1,t)
514            Vector<Length> Pt0t = s0crv.getSDerivative(t, 1);
515            Vector<Length> Pt1t = s1crv.getSDerivative(t, 1);
516            Pt0t.setOrigin(nullPnt);
517            Pt1t.setOrigin(nullPnt);
518
519            //  Get the corner points.
520            Point P00 = t0crv.getRealPoint(0);
521            Point P01 = t0crv.getRealPoint(1);
522            Point P10 = t1crv.getRealPoint(0);
523            Point P11 = t1crv.getRealPoint(1);
524
525            //  Get the actual point on the surface.
526            Point Pst = getRealPoint(s, t);
527
528            //  Ltst = Ps(s,0)*b0tt + Ps(s,1)*b1tt
529            Vector<Length> Ltst = Pss0.times(b0tt).plus(Pss1.times(b1tt));
530
531            //  Lsst = Pt(0,t)*c0ss + Pt(1,t)*c1ss
532            Vector<Length> Lsst = Pt0t.times(c0ss).plus(Pt1t.times(c1ss));
533
534            //  Bst = P(0,0)*b0tt*c0ss + P(0,1)*b0tt*c1ss + P(1,0)*b1tt*c0ss + P(1,1)*b1tt*c1ss
535            Point Bst = P00.times(b0tt * c0ss);
536            Bst = Bst.plus(P01.times(b0tt * c1ss));
537            Bst = Bst.plus(P10.times(b1tt * c0ss));
538            Bst = Bst.plus(P11.times(b1tt * c1ss));
539
540            //  dPsdt = Ltst + Lsst - Bst
541            Vector<Length> dPsdt = Ltst.plus(Lsst).minus(Bst.toGeomVector());
542            dPsdt.setOrigin(Pst);
543
544            return StackContext.outerCopy(dPsdt);
545
546        } finally {
547            StackContext.exit();
548        }
549    }
550
551    /**
552     * Return a new surface that is identical to this one, but with the S-parameterization
553     * reversed.
554     *
555     * @return A new surface that is identical to this one, but with the
556     *         S-parameterization reversed.
557     * @see #reverseT
558     */
559    @Override
560    public TFISurface reverseS() {
561        Curve s0 = s0crv.reverse();
562        Curve s1 = s1crv.reverse();
563        Curve t0 = t1crv;
564        Curve t1 = t0crv;
565
566        TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1);
567        copyState(srf); //  Copy over the super-class state for this surface to the new one.
568
569        return srf;
570    }
571
572    /**
573     * Return a new surface that is identical to this one, but with the T-parameterization
574     * reversed.
575     *
576     * @return A new surface that is identical to this one, but with the
577     *         T-parameterization reversed.
578     * @see #reverseS
579     */
580    @Override
581    public TFISurface reverseT() {
582        Curve s0 = s1crv;
583        Curve s1 = s0crv;
584        Curve t0 = t0crv.reverse();
585        Curve t1 = t1crv.reverse();
586
587        TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1);
588        copyState(srf); //  Copy over the super-class state for this surface to the new one.
589
590        return srf;
591    }
592
593    /**
594     * Return a new surface that is identical to this one but with the transpose of the
595     * parameterization of this surface. The S and T directions will be swapped.
596     *
597     * @return A new surface that is identical to this one but with the transpose of the
598     *         parameterization of this surface.
599     * @see #reverseT
600     * @see #reverseS
601     */
602    @Override
603    public TFISurface transpose() {
604        Curve s0 = t0crv;
605        Curve s1 = t1crv;
606        Curve t0 = s0crv;
607        Curve t1 = s1crv;
608
609        TFISurface srf = TFISurface.newInstance(s0, s1, t0, t1);
610        copyState(srf); //  Copy over the super-class state for this surface to the new one.
611
612        return srf;
613    }
614
615    /**
616     * Split this {@link SubrangeSurface} at the specified parametric S-position returning
617     * a list containing two new surfaces (a lower surface with smaller S-parametric
618     * positions than "s" and an upper surface with larger S-parametric positions).
619     *
620     * @param s The S-parametric position where this surface should be split (must not be
621     *          0 or 1!).
622     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
623     *         surface.
624     */
625    @Override
626    public GeomList<TFISurface> splitAtS(double s) {
627        validateParameter(s, 0);
628        if (parNearEnds(s, TOL_ST))
629            throw new IllegalArgumentException(MessageFormat.format(
630                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
631
632        //  Get the dividing curve from the NURBS representation.
633        //  TODO:  Figure out how to do this direclty on a TFI.
634        BasicNurbsSurface nsrf = createNurbsSurface();
635        SubrangeCurve splitC = nsrf.getSCurve(s);
636
637        //  Split the edge curves.
638        GeomList<Curve> t0crvs = t0crv.splitAt(s);
639        GeomList<Curve> t1crvs = t1crv.splitAt(s);
640
641        //  Create the new surface pieces.
642        TFISurface srfL = TFISurface.newInstance(s0crv, t0crvs.get(0), splitC, t1crvs.get(0));
643        TFISurface srfU = TFISurface.newInstance(splitC, t0crvs.get(1), s1crv, t1crvs.get(1));
644
645        //  Create the output list.
646        GeomList<TFISurface> output = GeomList.valueOf(srfL, srfU);
647        return output;
648    }
649
650    /**
651     * Split this {@link SubrangeSurface} at the specified parametric T-position returning
652     * a list containing two new surfaces (a lower surface with smaller T-parametric
653     * positions than "t" and an upper surface with larger T-parametric positions).
654     *
655     * @param t The T-parametric position where this surface should be split (must not be
656     *          0 or 1!).
657     * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper
658     *         surface.
659     */
660    @Override
661    public GeomList<TFISurface> splitAtT(double t) {
662        validateParameter(0, t);
663        if (parNearEnds(t, TOL_ST))
664            throw new IllegalArgumentException(MessageFormat.format(
665                    RESOURCES.getString("canNotSplitAtEnds"), "surface"));
666
667        //  Get the dividing curve from the NURBS representation.
668        //  TODO:  Figure out how to do this direclty on a TFI.
669        BasicNurbsSurface nsrf = createNurbsSurface();
670        SubrangeCurve splitC = nsrf.getTCurve(t);
671        BasicNurbsSurface.recycle(nsrf);
672
673        //  Split the edge curves.
674        GeomList<Curve> s0crvs = s0crv.splitAt(t);
675        GeomList<Curve> s1crvs = s1crv.splitAt(t);
676
677        //  Create the new surface pieces.
678        TFISurface srfL = TFISurface.newInstance(s0crvs.get(0), t0crv, s1crvs.get(0), splitC);
679        TFISurface srfU = TFISurface.newInstance(s0crvs.get(1), splitC, s1crvs.get(1), t1crv);
680
681        //  Create the output list.
682        GeomList<TFISurface> output = GeomList.valueOf(srfL, srfU);
683        return output;
684    }
685
686    private BasicNurbsSurface createNurbsSurface() {
687        //  Determine tolerance from size of bounding box.
688        Point range = getBoundsMax().minus(getBoundsMin());
689        Parameter<Length> tol = range.norm().divide(1000);
690        BasicNurbsSurface srf = SurfaceFactory.createTFISurface(s0crv, t0crv, s1crv, t1crv, tol);
691        return srf;
692    }
693
694    /**
695     * Return the T=0 Boundary for this surface as a curve.
696     *
697     * @return The T=0 Boundary for this surface as a curve.
698     */
699    @Override
700    public Curve getT0Curve() {
701        return t0crv;
702    }
703
704    /**
705     * Return the T=1 Boundary for this surface as a curve.
706     *
707     * @return The T=1 Boundary for this surface as a curve.
708     */
709    @Override
710    public Curve getT1Curve() {
711        return t1crv;
712    }
713
714    /**
715     * Return the S=0 Boundary for this surface as a curve.
716     *
717     * @return The S=0 Boundary for this surface as a curve.
718     */
719    @Override
720    public Curve getS0Curve() {
721        return s0crv;
722    }
723
724    /**
725     * Return the S=1 Boundary for this surface as a curve.
726     *
727     * @return The S=1 Boundary for this surface as a curve.
728     */
729    @Override
730    public Curve getS1Curve() {
731        return s1crv;
732    }
733
734    /**
735     * Returns transformed version of this element. The returned object implements
736     * {@link GeomTransform} and contains this element as a child.
737     *
738     * @param transform The transformation to apply to this geometry. May not be null.
739     * @return A new triangle that is identical to this one with the specified
740     *         transformation applied.
741     * @throws DimensionException if this point is not 3D.
742     */
743    @Override
744    public TFISurface getTransformed(GTransform transform) {
745        Curve s0 = (Curve)s0crv.getTransformed(requireNonNull(transform));
746        Curve s1 = (Curve)s1crv.getTransformed(transform);
747        Curve t0 = (Curve)t0crv.getTransformed(transform);
748        Curve t1 = (Curve)t1crv.getTransformed(transform);
749
750        return TFISurface.newInstance(s0, t0, s1, t1);
751    }
752
753    /**
754     * Return the coordinate point representing the minimum bounding box corner of this
755     * geometry element (e.g.: min X, min Y, min Z).
756     *
757     * @return The minimum bounding box coordinate for this geometry element.
758     * @throws IndexOutOfBoundsException if this list contains no geometry.
759     */
760    @Override
761    public Point getBoundsMin() {
762
763        Point minPoint = s0crv.getBoundsMin();
764        minPoint = minPoint.min(s1crv.getBoundsMin());
765        minPoint = minPoint.min(t0crv.getBoundsMin());
766        minPoint = minPoint.min(t1crv.getBoundsMin());
767
768        return minPoint;
769    }
770
771    /**
772     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
773     * X, max Y, max Z).
774     *
775     * @return The maximum bounding box coordinate for this geometry element.
776     * @throws IndexOutOfBoundsException if this list contains no elements.
777     */
778    @Override
779    public Point getBoundsMax() {
780
781        Point maxPoint = s0crv.getBoundsMax();
782        maxPoint = maxPoint.max(s1crv.getBoundsMax());
783        maxPoint = maxPoint.max(t0crv.getBoundsMax());
784        maxPoint = maxPoint.max(t1crv.getBoundsMax());
785
786        return maxPoint;
787    }
788
789    /**
790     * Returns the unit in which this surface is stated.
791     *
792     * @return The unit in which this surface is stated.
793     */
794    @Override
795    public Unit<Length> getUnit() {
796        return s0crv.getUnit();
797    }
798
799    /**
800     * Returns the equivalent to this surface but stated in the specified unit.
801     *
802     * @param unit the length unit of the surface to be returned. May not be null.
803     * @return an equivalent to this surface but stated in the specified unit.
804     * @throws ConversionException if the the input unit is not a length unit.
805     */
806    @Override
807    public TFISurface to(Unit<Length> unit) throws ConversionException {
808        if (unit.equals(getUnit()))
809            return this;
810
811        //  Convert the curves.
812        TFISurface srf = FACTORY.object();
813        copyState(srf);
814
815        srf.s0crv = s0crv.to(unit);
816        srf.s1crv = s1crv.to(unit);
817        srf.t0crv = t0crv.to(unit);
818        srf.t1crv = t1crv.to(unit);
819
820        return srf;
821    }
822
823    /**
824     * Return a NURBS surface representation of this surface to within the specified
825     * tolerance. If the curves making up this surface are NURBS curves, then the
826     * resulting NURBS surface is an exact representation of this surface (and tol is
827     * ignored). However, if any of the member curves are not NURBS curves, then tol is
828     * used to make a NURBS approximation of that curve (and this surface).
829     *
830     * @param tol The greatest possible difference between this surface and the NURBS
831     *            representation returned. May not be null.
832     * @return A NURBS surface that represents this surface to within the specified
833     *         tolerance.
834     */
835    @Override
836    public NurbsSurface toNurbs(Parameter<Length> tol) {
837
838        //  Go off and create a skinned NURBS surface.
839        BasicNurbsSurface srf = SurfaceFactory.createTFISurface(s0crv, t0crv, s1crv, t1crv, requireNonNull(tol));
840        copyState(srf); //  Copy over the super-class state for this surface to the new one.
841
842        return srf;
843    }
844
845    /**
846     * Return <code>true</code> if this TFISurface contains valid and finite numerical
847     * components. A value of <code>false</code> will be returned if any of the member
848     * curves are not valid.
849     *
850     * @return true if this TFISurface contains valid and finite numerical components.
851     */
852    @Override
853    public boolean isValid() {
854        return s0crv.isValid() && s1crv.isValid() && t0crv.isValid() && t1crv.isValid();
855    }
856
857    /**
858     * Return <code>true</code> if this surface is degenerate (i.e.: has area less than
859     * the specified tolerance squared).
860     *
861     * @param tol The tolerance for determining if this surface is degenerate. May not be
862     *            null.
863     * @return true if this surface is degenerate.
864     */
865    @Override
866    public boolean isDegenerate(Parameter<Length> tol) {
867        requireNonNull(tol);
868        //  A TFI is degenerate if all of it's bounding curves are degenerate.
869        return s0crv.isDegenerate(tol) && s1crv.isDegenerate(tol)
870                && t0crv.isDegenerate(tol) && t1crv.isDegenerate(tol);
871    }
872
873    /**
874     * Compares the specified object with this <code>TIFSurface</code> for equality.
875     * Returns true if and only if both surfaces are of the same type and both contain the
876     * same boundary curves in the same order.
877     *
878     * @param obj the object to compare with.
879     * @return <code>true</code> if this surface is identical to that surface;
880     *         <code>false</code> otherwise.
881     */
882    @Override
883    public boolean equals(Object obj) {
884        if (this == obj)
885            return true;
886        if ((obj == null) || (obj.getClass() != this.getClass()))
887            return false;
888
889        TFISurface that = (TFISurface)obj;
890        return this.s0crv.equals(that.s0crv)
891                && this.s1crv.equals(that.s1crv)
892                && this.t0crv.equals(that.t0crv)
893                && this.t1crv.equals(that.t1crv)
894                && super.equals(obj);
895    }
896
897    /**
898     * Returns the hash code for this <code>TFISurface</code>.
899     *
900     * @return the hash code value.
901     */
902    @Override
903    public int hashCode() {
904        return 31*super.hashCode() + Objects.hash(s0crv, s1crv, t0crv, t1crv);
905    }
906
907    /**
908     * Return a copy of this object with any transformations or subranges removed
909     * (applied).
910     *
911     * @return A copy of this object with any transformations or subranges removed.
912     */
913    @Override
914    public TFISurface copyToReal() {
915        TFISurface newSrf = FACTORY.object();
916        copyState(newSrf);
917
918        newSrf.s0crv = s0crv.copyToReal();
919        newSrf.s1crv = s1crv.copyToReal();
920        newSrf.t0crv = t0crv.copyToReal();
921        newSrf.t1crv = t1crv.copyToReal();
922
923        return newSrf;
924    }
925
926    /**
927     * Returns a copy of this <code>TFISurface</code> instance
928     * {@link javolution.context.AllocatorContext allocated} by the calling thread
929     * (possibly on the stack).
930     *
931     * @return an identical and independent copy of this object.
932     */
933    @Override
934    public TFISurface copy() {
935        return copyOf(this);
936    }
937
938    /**
939     * Returns the text representation of this geometry element.
940     *
941     * @return The text representation of this geometry element.
942     */
943    @Override
944    public Text toText() {
945        TextBuilder tmp = TextBuilder.newInstance();
946        String className = this.getClass().getName();
947        tmp.append(className.substring(className.lastIndexOf(".") + 1));
948        tmp.append(": {\n");
949        tmp.append(s0crv.toText());
950        tmp.append(",\n");
951        tmp.append(s1crv.toText());
952        tmp.append(",\n");
953        tmp.append(t0crv.toText());
954        tmp.append(",\n");
955        tmp.append(t1crv.toText());
956        tmp.append("\n");
957
958        tmp.append("}");
959        Text txt = tmp.toText();
960        TextBuilder.recycle(tmp);
961        return txt;
962    }
963
964    /**
965     * Holds the default XML representation for this object.
966     */
967    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
968    protected static final XMLFormat<TFISurface> XML = new XMLFormat<TFISurface>(TFISurface.class) {
969
970        @Override
971        public TFISurface newInstance(Class<TFISurface> cls, XMLFormat.InputElement xml) throws XMLStreamException {
972            TFISurface obj = FACTORY.object();
973            return obj;
974        }
975
976        @Override
977        public void read(XMLFormat.InputElement xml, TFISurface obj) throws XMLStreamException {
978            AbstractSurface.XML.read(xml, obj);     // Call parent read.
979
980            obj.s0crv = xml.get("S0");
981            if (isNull(obj.s0crv))
982                throw new XMLStreamException(
983                        MessageFormat.format(RESOURCES.getString("crvMissingXML"), "S0"));
984            obj.s1crv = xml.get("S1");
985            if (isNull(obj.s1crv))
986                throw new XMLStreamException(
987                        MessageFormat.format(RESOURCES.getString("crvMissingXML"), "S1"));
988            obj.t0crv = xml.get("T0");
989            if (isNull(obj.t0crv))
990                throw new XMLStreamException(
991                        MessageFormat.format(RESOURCES.getString("crvMissingXML"), "T0"));
992            obj.t1crv = xml.get("T1");
993            if (isNull(obj.t1crv))
994                throw new XMLStreamException(
995                        MessageFormat.format(RESOURCES.getString("crvMissingXML"), "T1"));
996
997            if (!(obj.s0crv instanceof Immutable))
998                obj.s0crv.addChangeListener(obj._childChangeListener);
999            if (!(obj.s1crv instanceof Immutable))
1000                obj.s1crv.addChangeListener(obj._childChangeListener);
1001            if (!(obj.t0crv instanceof Immutable))
1002                obj.t0crv.addChangeListener(obj._childChangeListener);
1003            if (!(obj.t1crv instanceof Immutable))
1004                obj.t1crv.addChangeListener(obj._childChangeListener);
1005
1006        }
1007
1008        @Override
1009        public void write(TFISurface obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1010            AbstractSurface.XML.write(obj, xml);    // Call parent write.
1011
1012            xml.add(obj.s0crv, "S0");
1013            xml.add(obj.s1crv, "S1");
1014            xml.add(obj.t0crv, "T0");
1015            xml.add(obj.t1crv, "T1");
1016        }
1017    };
1018
1019    //////////////////////
1020    // Factory Creation //
1021    //////////////////////
1022    /**
1023     * Do not allow the default constructor to be used except by subclasses.
1024     */
1025    protected TFISurface() { }
1026
1027    private static final ObjectFactory<TFISurface> FACTORY = new ObjectFactory<TFISurface>() {
1028        @Override
1029        protected TFISurface create() {
1030            return new TFISurface();
1031        }
1032
1033        @Override
1034        protected void cleanup(TFISurface obj) {
1035            obj.reset();
1036            if (!(obj.s0crv instanceof Immutable))
1037                obj.s0crv.removeChangeListener(obj._childChangeListener);
1038            if (!(obj.s1crv instanceof Immutable))
1039                obj.s1crv.removeChangeListener(obj._childChangeListener);
1040            if (!(obj.t0crv instanceof Immutable))
1041                obj.t0crv.removeChangeListener(obj._childChangeListener);
1042            if (!(obj.t1crv instanceof Immutable))
1043                obj.t1crv.removeChangeListener(obj._childChangeListener);
1044            obj.s0crv = null;
1045            obj.s1crv = null;
1046            obj.t0crv = null;
1047            obj.t1crv = null;
1048        }
1049    };
1050
1051    /**
1052     * Recycles a case instance immediately (on the stack when executing in a
1053     * StackContext).
1054     *
1055     * @param instance The instance to be recycled.
1056     */
1057    public static void recycle(TFISurface instance) {
1058        FACTORY.recycle(instance);
1059    }
1060
1061    @SuppressWarnings("unchecked")
1062    private static TFISurface copyOf(TFISurface original) {
1063        TFISurface obj = FACTORY.object();
1064        obj.s0crv = original.s0crv.copy();
1065        obj.s1crv = original.s1crv.copy();
1066        obj.t0crv = original.t0crv.copy();
1067        obj.t1crv = original.t1crv.copy();
1068
1069        if (!(obj.s0crv instanceof Immutable))
1070            obj.s0crv.addChangeListener(obj._childChangeListener);
1071        if (!(obj.s1crv instanceof Immutable))
1072            obj.s1crv.addChangeListener(obj._childChangeListener);
1073        if (!(obj.t0crv instanceof Immutable))
1074            obj.t0crv.addChangeListener(obj._childChangeListener);
1075        if (!(obj.t1crv instanceof Immutable))
1076            obj.t1crv.addChangeListener(obj._childChangeListener);
1077
1078        original.copyState(obj);
1079        return obj;
1080    }
1081}