001/**
002 * NurbsSurfaceTrans -- A GeomTransform that has a NurbsSurface for a child.
003 *
004 * Copyright (C) 2010-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 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.nurbs;
019
020import geomss.geom.*;
021import java.text.MessageFormat;
022import java.util.List;
023import java.util.Objects;
024import static java.util.Objects.nonNull;
025import static java.util.Objects.requireNonNull;
026import javax.measure.converter.ConversionException;
027import javax.measure.quantity.Length;
028import javax.measure.unit.Unit;
029import javax.swing.event.ChangeEvent;
030import javax.swing.event.ChangeListener;
031import javolution.context.ObjectFactory;
032import javolution.context.StackContext;
033import javolution.lang.Immutable;
034import javolution.util.FastTable;
035import javolution.xml.XMLFormat;
036import javolution.xml.stream.XMLStreamException;
037
038/**
039 * A {@link GeomTransform} object that refers to a {@link NurbsSurface} object and
040 * masquerades as a NurbsSurface object itself.
041 *
042 * <p> Modified by: Joseph A. Huwaldt </p>
043 *
044 * @author Joseph A. Huwaldt, Date: June 14, 2010
045 * @version November 28, 2015
046 */
047@SuppressWarnings({"serial", "CloneableImplementsClone"})
048public final class NurbsSurfaceTrans extends NurbsSurface implements GeomTransform<NurbsSurface> {
049
050    /**
051     * The transformation represented by this transformation element.
052     */
053    private GTransform _TM;
054
055    /**
056     * The object that is the child of this transform.
057     */
058    private NurbsSurface _child;
059
060    /**
061     * Reference to a change listener for this object's child.
062     */
063    private ChangeListener _childChangeListener = new MyChangeListener(this);
064
065    //  The following supports an optimization that caches the "copyToReal" version of the child
066    //  in order to speed up many calculations.
067    private BasicNurbsSurface _realChild;
068
069    /**
070     * Returns a 3D {@link NurbsSurfaceTrans} instance holding the specified
071     * {@link NurbsSurface} and {@link GTransform}.
072     *
073     * @param child     The NurbsSurface that is the child of this transform element (may
074     *                  not be <code>null</code>).
075     * @param transform The transform held by this transform element (may not be
076     *                  <code>null</code>).
077     * @return the transform element having the specified values.
078     */
079    public static NurbsSurfaceTrans newInstance(NurbsSurface child, GTransform transform) {
080        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
081        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
082
083        if (child.getPhyDimension() != 3)
084            throw new DimensionException(
085                    MessageFormat.format(RESOURCES.getString("dimensionNot3"), "NURBS surface", child.getPhyDimension()));
086
087        NurbsSurfaceTrans obj = FACTORY.object();
088        obj._TM = transform;
089        obj._child = child;
090        obj._realChild = obj.privateCopyToReal();
091
092        //  Listen for changes to the child object and pass them on.
093        if (!(child instanceof Immutable))
094            child.addChangeListener(obj._childChangeListener);
095
096        return obj;
097    }
098
099    //  A change listener that recreates the realChild geometry as well as
100    //  passing the child's change event on to any listeners.
101    private static class MyChangeListener extends ForwardingChangeListener {
102
103        private final NurbsSurfaceTrans thisTrans;
104
105        public MyChangeListener(NurbsSurfaceTrans trans) {
106            super(trans);
107            this.thisTrans = trans;
108        }
109
110        @Override
111        public void stateChanged(ChangeEvent e) {
112            //  Re-create the internal real copy of the child geometry.
113            thisTrans.privateCopyToReal();
114            super.stateChanged(e);
115        }
116    }
117
118    /**
119     * Returns the transformation represented by this transformation element.
120     */
121    @Override
122    public GTransform getTransform() {
123        return _TM;
124    }
125
126    /**
127     * Returns the total transformation represented by an entire chain of GeomTransform
128     * objects below this one.
129     */
130    @Override
131    public GTransform getTotalTransform() {
132        return GeomUtil.getTotalTransform(this);
133    }
134
135    /**
136     * Sets the transformation represented by this transformation element.
137     *
138     * @param transform The transform to set this transform element to (may not be
139     *                  <code>null</code>).
140     */
141    @Override
142    public void setTransform(GTransform transform) {
143        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
144
145        if (!transform.equals(_TM)) {
146            //  Create a new "real" child surface.
147            _TM = transform;
148            _realChild = privateCopyToReal();
149            fireChangeEvent();
150        }
151
152    }
153
154    /**
155     * Returns the child object transformed by this transform element.
156     */
157    @Override
158    public NurbsSurface getChild() {
159        return _child;
160    }
161
162    /**
163     * Return a copy of the child object transformed by this transformation.
164     *
165     * @return A copy of the child object transformed by this transformation.
166     */
167    @Override
168    public NurbsSurface copyToReal() {
169        return _realChild;
170    }
171
172    /**
173     * Returns the number of child-elements that make up this geometry element. This
174     * implementation returns the number of control points in the child NURBS surface.
175     */
176    @Override
177    public int size() {
178        return _child.size();
179    }
180
181    /**
182     * Returns the number of physical dimensions of the geometry element.
183     */
184    @Override
185    public int getPhyDimension() {
186        return _child.getPhyDimension();
187    }
188
189    /**
190     * Return a matrix or network of control points for this surface.
191     *
192     * @return the ordered control points
193     */
194    @Override
195    public ControlPointNet getControlPoints() {
196        return _realChild.getControlPoints();
197    }
198
199    /**
200     * Return a copy of the child object transformed by this transformation.
201     */
202    private BasicNurbsSurface privateCopyToReal() {
203        StackContext.enter();
204        try {
205            //  Transform the child surface's control points.
206            ControlPointNet transCPNet = privateGetControlPoints();
207
208            //  Create a new surface from the transformed control point list.
209            BasicNurbsSurface srf = BasicNurbsSurface.newInstance(transCPNet,
210                    _child.getSKnotVector().copy(), _child.getTKnotVector().copy());
211            srf.setName(_child.getName());
212            srf.putAllUserData(_child.getAllUserData());
213
214            return StackContext.outerCopy(srf);
215
216        } finally {
217            StackContext.exit();
218        }
219    }
220
221    /**
222     * Return a matrix or network of control points for this surface.
223     *
224     * @return the ordered control points
225     */
226    private ControlPointNet privateGetControlPoints() {
227
228        //  Loop over all the control points in the child surface and transform each in turn.
229        ControlPointNet cpNet = _child.getControlPoints();
230
231        FastTable<FastTable<ControlPoint>> transCPNet = FastTable.newInstance();
232        int ncol = cpNet.getNumberOfColumns();
233        for (int i = 0; i < ncol; ++i) {
234            List<ControlPoint> tbl = cpNet.getColumn(i);
235            FastTable<ControlPoint> transCPList = FastTable.newInstance();
236            transCPNet.add(transCPList);
237
238            int nrow = tbl.size();
239            for (int j = 0; j < nrow; ++j) {
240                ControlPoint cp = tbl.get(j);
241
242                //  Transform the control point's geometric point by this transformation.
243                Point pnt = _TM.transform(cp.getPoint()).copyToReal();
244
245                //  Create a transformed control point.
246                ControlPoint newCP = ControlPoint.valueOf(pnt, cp.getWeight());
247                transCPList.add(newCP);
248            }
249        }
250
251        //  Create a new control point net from the transformed control point list.
252        ControlPointNet cpNet2 = ControlPointNet.valueOf(transCPNet);
253
254        return cpNet2;
255    }
256
257    /**
258     * Return the control point matrix size in the s-direction (down a column of control
259     * points).
260     *
261     * @return The control point matrix size in the s-direction.
262     */
263    @Override
264    public int getNumberOfRows() {
265        return _child.getNumberOfRows();
266    }
267
268    /**
269     * Return the control point matrix size in the t-direction (across the columns of
270     * control points).
271     *
272     * @return The control point matrix size in the t-direction.
273     */
274    @Override
275    public int getNumberOfColumns() {
276        return _child.getNumberOfColumns();
277    }
278
279    /**
280     * Return the s-direction knot vector of this surface.
281     *
282     * @return The s-knot vector.
283     */
284    @Override
285    public KnotVector getSKnotVector() {
286        return _child.getSKnotVector();
287    }
288
289    /**
290     * Return the t-direction knot vector of this surface.
291     *
292     * @return The t-knot vector.
293     */
294    @Override
295    public KnotVector getTKnotVector() {
296        return _child.getTKnotVector();
297    }
298
299    /**
300     * Return the s-degree of the NURBS surface.
301     *
302     * @return s-degree of surface
303     */
304    @Override
305    public int getSDegree() {
306        return _child.getSDegree();
307    }
308
309    /**
310     * Return the t-degree of the NURBS surface.
311     *
312     * @return t-degree of surface
313     */
314    @Override
315    public int getTDegree() {
316        return _child.getTDegree();
317    }
318
319    /**
320     * Calculate a point on the surface for the given parametric position on the surface.
321     *
322     * @param s 1st parametric dimension distance to get the point for (0.0 to 1.0
323     *          inclusive).
324     * @param t 2nd parametric dimension distance to get the point for (0.0 to 1.0
325     *          inclusive).
326     * @return The calculated point on the surface at the specified parameter values.
327     * @throws IllegalArgumentException if there is any problem with the parameter values.
328     */
329    @Override
330    public Point getRealPoint(double s, double t) {
331        return _realChild.getRealPoint(s, t);
332    }
333
334    /**
335     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
336     * respect to parametric s-position on the surface for the given parametric position
337     * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>.
338     * <p>
339     * Example:<br>
340     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br>
341     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc.
342     * </p>
343     *
344     * @param s     1st parametric dimension distance to calculate derivative for (0.0 to
345     *              1.0 inclusive).
346     * @param t     2nd parametric dimension distance to calculate derivative for (0.0 to
347     *              1.0 inclusive).
348     * @param grade The maximum grade to calculate the u-derivatives for (1=1st
349     *              derivative, 2=2nd derivative, etc)
350     * @return A list of s-derivatives up to the specified grade of the surface at the
351     *         specified parametric position.
352     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
353     * invalid.
354     */
355    @Override
356    public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) {
357        return _realChild.getSDerivatives(s, t, grade, scaled);
358    }
359
360    /**
361     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
362     * respect to parametric t-position on the surface for the given parametric position
363     * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>.
364     * <p>
365     * Example:<br>
366     * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br>
367     * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc.
368     * </p>
369     *
370     * @param s     1st parametric dimension distance to calculate derivative for (0.0 to
371     *              1.0 inclusive).
372     * @param t     2nd parametric dimension distance to calculate derivative for (0.0 to
373     *              1.0 inclusive).
374     * @param grade The maximum grade to calculate the v-derivatives for (1=1st
375     *              derivative, 2=2nd derivative, etc)
376     * @return A list of t-derivatives up to the specified grade of the surface at the
377     *         specified parametric position.
378     * @throws IllegalArgumentException if the grade is &lt; 0 or the parameter values are
379     * invalid.
380     */
381    @Override
382    public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) {
383        return _realChild.getTDerivatives(s, t, grade, scaled);
384    }
385
386    /**
387     * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the
388     * specified position on this surface.
389     *
390     * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to
391     *          1.0 inclusive).
392     * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to
393     *          1.0 inclusive).
394     * @return The twist vector of this surface at the specified parametric position.
395     * @throws IllegalArgumentException if the parameter values are invalid.
396     */
397    @Override
398    public Vector<Length> getTwistVector(double s, double t) {
399        return _realChild.getTwistVector(s, t);
400    }
401
402    /**
403     * Return the coordinate point representing the minimum bounding box corner of this
404     * geometry element (e.g.: min X, min Y, min Z).
405     *
406     * @return The minimum bounding box coordinate for this geometry element.
407     * @throws IndexOutOfBoundsException if this list contains no elements.
408     */
409    @Override
410    public Point getBoundsMin() {
411        Point minPoint = _realChild.getBoundsMin();
412        return minPoint;
413    }
414
415    /**
416     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
417     * X, max Y, max Z).
418     *
419     * @return The maximum bounding box coordinate for this geometry element.
420     * @throws IndexOutOfBoundsException if this list contains no elements.
421     */
422    @Override
423    public Point getBoundsMax() {
424        Point maxPoint = _realChild.getBoundsMax();
425        return maxPoint;
426    }
427
428    /**
429     * Returns transformed version of this element. The returned object implements
430     * {@link GeomTransform} and contains this element as a child.
431     */
432    @Override
433    public NurbsSurfaceTrans getTransformed(GTransform transform) {
434        return NurbsSurfaceTrans.newInstance(this, requireNonNull(transform));
435    }
436
437    /**
438     * Returns the unit in which the control points in this curve are stated.
439     */
440    @Override
441    public Unit<Length> getUnit() {
442        return _child.getUnit();
443    }
444
445    /**
446     * Returns the equivalent to this surface but stated in the specified unit.
447     *
448     * @param unit the length unit of the surface to be returned. May not be null.
449     * @return an equivalent to this surface but stated in the specified unit.
450     * @throws ConversionException if the the input unit is not a length unit.
451     */
452    @Override
453    public NurbsSurfaceTrans to(Unit<Length> unit) throws ConversionException {
454        if (unit.equals(getUnit()))
455            return this;
456
457        return NurbsSurfaceTrans.newInstance(_child.to(unit), _TM);
458    }
459
460    /**
461     * Return the equivalent of this surface converted to the specified number of physical
462     * dimensions. This implementation will throw an exception if the specified dimension
463     * is anything other than 3.
464     *
465     * @param newDim The dimension of the surface to return. MUST equal 3.
466     * @return The equivalent of this surface converted to the new dimensions.
467     * @throws IllegalArgumentException if the new dimension is anything other than 3.
468     */
469    @Override
470    public NurbsSurfaceTrans toDimension(int newDim) {
471        if (newDim == 3)
472            return this;
473
474        throw new IllegalArgumentException(
475                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
476    }
477
478    /**
479     * Returns a copy of this NurbsSurfaceTrans instance
480     * {@link javolution.context.AllocatorContext allocated} by the calling thread
481     * (possibly on the stack).
482     *
483     * @return an identical and independent copy of this point.
484     */
485    @Override
486    public NurbsSurfaceTrans copy() {
487        return NurbsSurfaceTrans.copyOf(this);
488    }
489
490    /**
491     * Compares this NurbsSurfaceTrans against the specified object for strict equality.
492     *
493     * @param obj the object to compare with.
494     * @return <code>true</code> if this transform is identical to that transform;
495     *         <code>false</code> otherwise.
496     */
497    @Override
498    public boolean equals(Object obj) {
499        if (this == obj)
500            return true;
501        if ((obj == null) || (obj.getClass() != this.getClass()))
502            return false;
503
504        NurbsSurfaceTrans that = (NurbsSurfaceTrans)obj;
505        return this._TM.equals(that._TM)
506                && this._child.equals(that._child)
507                && super.equals(obj);
508    }
509
510    /**
511     * Returns the hash code for this parameter.
512     *
513     * @return the hash code value.
514     */
515    @Override
516    public int hashCode() {
517        return 31*super.hashCode() + Objects.hash(_TM, _child);
518    }
519
520    /**
521     * Holds the default XML representation. For example:
522     */
523    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
524    protected static final XMLFormat<NurbsSurfaceTrans> XML = new XMLFormat<NurbsSurfaceTrans>(NurbsSurfaceTrans.class) {
525
526        @Override
527        public NurbsSurfaceTrans newInstance(Class<NurbsSurfaceTrans> cls, InputElement xml) throws XMLStreamException {
528            return FACTORY.object();
529        }
530
531        @Override
532        public void read(InputElement xml, NurbsSurfaceTrans obj) throws XMLStreamException {
533            NurbsSurface.XML.read(xml, obj);     // Call parent read.
534
535            obj._TM = xml.getNext();
536            NurbsSurface child = xml.getNext();
537            obj._child = child;
538            obj._realChild = obj.privateCopyToReal();
539
540            //  Listen for changes to the child object and pass them on.
541            if (!(child instanceof Immutable))
542                child.addChangeListener(obj._childChangeListener);
543        }
544
545        @Override
546        public void write(NurbsSurfaceTrans obj, OutputElement xml) throws XMLStreamException {
547            NurbsSurface.XML.write(obj, xml);    // Call parent write.
548
549            xml.add(obj._TM);
550            xml.add(obj._child);
551
552        }
553    };
554
555    //////////////////////
556    // Factory Creation //
557    //////////////////////
558    private static final ObjectFactory<NurbsSurfaceTrans> FACTORY = new ObjectFactory<NurbsSurfaceTrans>() {
559        @Override
560        protected NurbsSurfaceTrans create() {
561            return new NurbsSurfaceTrans();
562        }
563
564        @Override
565        protected void cleanup(NurbsSurfaceTrans obj) {
566            obj.reset();
567            obj._TM = null;
568            if (nonNull(obj._childChangeListener))
569                obj._child.removeChangeListener(obj._childChangeListener);
570            obj._child = null;
571            obj._realChild = null;
572        }
573    };
574
575    /**
576     * Recycles a <code>NurbsSurfaceTrans</code> instance immediately (on the stack when
577     * executing in a StackContext).
578     *
579     * @param instance The instance to be recycled.
580     */
581    public static void recycle(NurbsSurfaceTrans instance) {
582        FACTORY.recycle(instance);
583    }
584
585    @SuppressWarnings("unchecked")
586    private static NurbsSurfaceTrans copyOf(NurbsSurfaceTrans original) {
587        NurbsSurfaceTrans obj = FACTORY.object();
588        obj._TM = original._TM.copy();
589        obj._child = original._child.copy();
590        obj._realChild = original._realChild.copy();
591        original.copyState(obj);
592        if (!(obj._child instanceof Immutable))
593            obj._child.addChangeListener(obj._childChangeListener);
594        return obj;
595    }
596
597    /**
598     * Do not allow the default constructor to be used except by subclasses.
599     */
600    private NurbsSurfaceTrans() { }
601
602}