001/**
002 * NurbsCurveTrans -- A GeomTransform that has a NurbsCurve for a child.
003 *
004 * Copyright (C) 2009-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.requireNonNull;
025import javax.measure.converter.ConversionException;
026import javax.measure.quantity.Length;
027import javax.measure.unit.Unit;
028import javax.swing.event.ChangeListener;
029import javolution.context.ObjectFactory;
030import javolution.context.StackContext;
031import javolution.lang.Immutable;
032import javolution.util.FastTable;
033import javolution.xml.XMLFormat;
034import javolution.xml.stream.XMLStreamException;
035
036/**
037 * A {@link GeomTransform} object that refers to a {@link NurbsCurve} object and
038 * masquerades as a NurbsCurve object itself.
039 *
040 * <p> Modified by: Joseph A. Huwaldt </p>
041 *
042 * @author Joseph A. Huwaldt, Date: April 30, 2009
043 * @version November 28, 2015
044 */
045@SuppressWarnings({"serial", "CloneableImplementsClone"})
046public final class NurbsCurveTrans extends NurbsCurve implements GeomTransform<NurbsCurve> {
047
048    /**
049     * The transformation represented by this transformation element.
050     */
051    private GTransform _TM;
052
053    /**
054     * The object that is the child of this transform.
055     */
056    private NurbsCurve _child;
057
058    /**
059     * Reference to a change listener for this object's child.
060     */
061    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
062
063    /**
064     * Returns a 3D {@link NurbsCurveTrans} instance holding the specified
065     * {@link NurbsCurve} and {@link GTransform}.
066     *
067     * @param child The NurbsCurve that is the child of this transform element (may not be
068     * <code>null</code>).
069     * @param transform The transform held by this transform element (may not be
070     * <code>null</code>).
071     * @return the transform element having the specified values.
072     */
073    public static NurbsCurveTrans newInstance(NurbsCurve child, GTransform transform) {
074        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
075        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
076
077        if (child.getPhyDimension() != 3)
078            throw new DimensionException(
079                    MessageFormat.format(RESOURCES.getString("dimensionNot3"), "NURBS curve", child.getPhyDimension()));
080
081        NurbsCurveTrans obj = FACTORY.object();
082        obj._TM = transform;
083        obj._child = child;
084
085        //  Listen for changes to the child object and pass them on.
086        if (!(child instanceof Immutable))
087            child.addChangeListener(obj._childChangeListener);
088
089        return obj;
090    }
091
092    /**
093     * Returns the transformation represented by this transformation element.
094     *
095     * @return The transformation represented by this element.
096     */
097    @Override
098    public GTransform getTransform() {
099        return _TM;
100    }
101
102    /**
103     * Returns the total transformation represented by an entire chain of GeomTransform
104     * objects below this one.
105     *
106     * @return The total transformation represented by the entire chain of transform
107     * objects.
108     */
109    @Override
110    public GTransform getTotalTransform() {
111        return GeomUtil.getTotalTransform(this);
112    }
113
114    /**
115     * Sets the transformation represented by this transformation element.
116     *
117     * @param transform The transform to set this transform element to (may not be
118     * <code>null</code>).
119     */
120    @Override
121    public void setTransform(GTransform transform) {
122        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
123        _TM = transform;
124        fireChangeEvent();
125    }
126
127    /**
128     * Returns the child object transformed by this transform element.
129     *
130     * @return The child object transformed by this element.
131     */
132    @Override
133    public NurbsCurve getChild() {
134        return _child;
135    }
136
137    /**
138     * Return a copy of the child object transformed by this transformation.
139     *
140     * @return A copy of this object with any transformations or subranges removed
141     * (applied).
142     */
143    @Override
144    public BasicNurbsCurve copyToReal() {
145
146        //  Transform the child curve's control points.
147        FastTable<ControlPoint> transCPList = (FastTable)getControlPoints();
148
149        //  Create a new curve from the transformed control point list.
150        BasicNurbsCurve crv = BasicNurbsCurve.newInstance(transCPList, _child.getKnotVector());
151        crv.setName(_child.getName());
152        crv.putAllUserData(_child.getAllUserData());
153
154        FastTable.recycle(transCPList);
155        return crv;
156    }
157
158    /**
159     * Return an immutable version of this NURBS curve.
160     *
161     * @return an immutable version of this curve.
162     */
163    @Override
164    public BasicNurbsCurve immutable() {
165        return copyToReal();
166    }
167    
168    /**
169     * Returns the number of child-elements that make up this geometry element. This
170     * implementation returns the number of control points in the child NURBS curve.
171     */
172    @Override
173    public int size() {
174        return _child.size();
175    }
176
177    /**
178     * Returns the number of physical dimensions of the geometry element.
179     */
180    @Override
181    public int getPhyDimension() {
182        return _child.getPhyDimension();
183    }
184
185    /**
186     * Return a list of transformed control points for this curve.
187     *
188     * @return the ordered control points
189     */
190    @Override
191    public List<ControlPoint> getControlPoints() {
192
193        //  Loop over all the control points in the child curve and transform each in turn.
194        FastTable<ControlPoint> transCPList = FastTable.newInstance();
195
196        StackContext.enter();
197        try {
198            FastTable<ControlPoint> cPoly = (FastTable<ControlPoint>)_child.getControlPoints();
199            int size = cPoly.size();
200            for (int i = 0; i < size; ++i) {
201                ControlPoint cp = cPoly.get(i);
202
203                //  Transform the control point's geomssic point by this transformation.
204                Point pnt = cp.getPoint().getTransformed(_TM).copyToReal();
205
206                //  Create a transformed control point.
207                ControlPoint newCP = ControlPoint.valueOf(pnt, cp.getWeight());
208                transCPList.add(StackContext.outerCopy(newCP));
209            }
210
211        } finally {
212            StackContext.exit();
213        }
214
215        return transCPList;
216    }
217
218    /**
219     * Return the knot vector of this curve.
220     *
221     * @return The knot vector.
222     */
223    @Override
224    public KnotVector getKnotVector() {
225        return _child.getKnotVector();
226    }
227
228    /**
229     * Return the degree of the NURBS curve.
230     *
231     * @return degree of curve
232     */
233    @Override
234    public int getDegree() {
235        return _child.getDegree();
236    }
237
238    /**
239     * Calculate a point on the curve for the given parametric distance along the curve,
240     * <code>p(s)</code>.
241     *
242     * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive).
243     * @return the calculated point
244     */
245    @Override
246    public Point getRealPoint(double s) {
247        return _TM.transform(_child.getRealPoint(s));
248    }
249
250    /**
251     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
252     * respect to parametric distance on the curve for the given parametric distance along
253     * the curve, <code>d^{grade}p(s)/d^{grade}s</code>.
254     * <p>
255     * Example:<br>
256     * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
257     * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
258     * </p>
259     *
260     * @param s Parametric distance to calculate derivatives for (0.0 to 1.0 inclusive).
261     * @param grade The maximum grade to calculate the derivatives for (1=1st derivative,
262     * 2=2nd derivative, etc)
263     * @return A list of derivatives up to the specified grade of the curve at the
264     * specified parametric position.
265     * @throws IllegalArgumentException if the grade is &lt; 0.
266     */
267    @Override
268    public List<Vector<Length>> getSDerivatives(double s, int grade) {
269        BasicNurbsCurve trans = copyToReal();
270        List<Vector<Length>> ders = trans.getSDerivatives(s, grade);
271        BasicNurbsCurve.recycle(trans);
272        return ders;
273    }
274
275    /**
276     * Return the coordinate point representing the minimum bounding box corner of this
277     * geometry element (e.g.: min X, min Y, min Z).
278     *
279     * @return The minimum bounding box coordinate for this geometry element.
280     * @throws IndexOutOfBoundsException if this list contains no elements.
281     */
282    @Override
283    public Point getBoundsMin() {
284        BasicNurbsCurve trans = copyToReal();
285        Point minPoint = trans.getBoundsMin();
286        BasicNurbsCurve.recycle(trans);
287        return minPoint;
288    }
289
290    /**
291     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
292     * X, max Y, max Z).
293     *
294     * @return The maximum bounding box coordinate for this geometry element.
295     * @throws IndexOutOfBoundsException if this list contains no elements.
296     */
297    @Override
298    public Point getBoundsMax() {
299        BasicNurbsCurve trans = copyToReal();
300        Point maxPoint = trans.getBoundsMax();
301        BasicNurbsCurve.recycle(trans);
302        return maxPoint;
303    }
304
305    /**
306     * Returns the unit in which the control points in this curve are stated.
307     */
308    @Override
309    public Unit<Length> getUnit() {
310        return _child.getUnit();
311    }
312
313    /**
314     * Returns the equivalent to this curve but stated in the specified unit.
315     *
316     * @param unit The length unit of the curve to be returned. May not be null.
317     * @return An equivalent to this curve but stated in the specified unit.
318     * @throws ConversionException if the the input unit is not a length unit.
319     */
320    @Override
321    public NurbsCurveTrans to(Unit<Length> unit) throws ConversionException {
322        if (unit.equals(getUnit()))
323            return this;
324
325        return NurbsCurveTrans.newInstance(_child.to(unit), _TM);
326    }
327
328    /**
329     * Return the equivalent of this curve converted to the specified number of physical
330     * dimensions. This implementation will throw an exception if the specified dimension
331     * is anything other than 3.
332     *
333     * @param newDim The dimension of the curve to return. MUST equal 3.
334     * @return The equivalent of this curve converted to the new dimensions.
335     * @throws IllegalArgumentException if the new dimension is anything other than 3.
336     */
337    @Override
338    public NurbsCurveTrans toDimension(int newDim) {
339        if (newDim == 3)
340            return this;
341
342        throw new IllegalArgumentException(
343                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
344    }
345
346    /**
347     * Returns a copy of this NurbsCurveTrans instance
348     * {@link javolution.context.AllocatorContext allocated} by the calling thread
349     * (possibly on the stack).
350     *
351     * @return an identical and independent copy of this point.
352     */
353    @Override
354    public NurbsCurveTrans copy() {
355        return copyOf(this);
356    }
357
358    /**
359     * Compares this NurbsCurveTrans against the specified object for strict equality.
360     *
361     * @param obj the object to compare with.
362     * @return <code>true</code> if this transform is identical to that transform;
363     * <code>false</code> otherwise.
364     */
365    @Override
366    public boolean equals(Object obj) {
367        if (this == obj)
368            return true;
369        if ((obj == null) || (obj.getClass() != this.getClass()))
370            return false;
371
372        NurbsCurveTrans that = (NurbsCurveTrans)obj;
373        return this._TM.equals(that._TM)
374                && this._child.equals(that._child)
375                && super.equals(obj);
376    }
377
378    /**
379     * Returns the hash code for this parameter.
380     *
381     * @return the hash code value.
382     */
383    @Override
384    public int hashCode() {
385        return 31*super.hashCode() + Objects.hash(_TM, _child);
386    }
387
388    /**
389     * Holds the default XML representation for this object.
390     */
391    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
392    protected static final XMLFormat<NurbsCurveTrans> XML = new XMLFormat<NurbsCurveTrans>(NurbsCurveTrans.class) {
393
394        @Override
395        public NurbsCurveTrans newInstance(Class<NurbsCurveTrans> cls, InputElement xml) throws XMLStreamException {
396            return FACTORY.object();
397        }
398
399        @Override
400        public void read(InputElement xml, NurbsCurveTrans obj) throws XMLStreamException {
401            NurbsCurve.XML.read(xml, obj);     // Call parent read.
402
403            obj._TM = xml.getNext();
404            NurbsCurve child = xml.getNext();
405            obj._child = child;
406
407            //  Listen for changes to the child object and pass them on.
408            if (!(child instanceof Immutable))
409                child.addChangeListener(obj._childChangeListener);
410        }
411
412        @Override
413        public void write(NurbsCurveTrans obj, OutputElement xml) throws XMLStreamException {
414            NurbsCurve.XML.write(obj, xml);    // Call parent write.
415
416            xml.add(obj._TM);
417            xml.add(obj._child);
418
419        }
420    };
421
422    //////////////////////
423    // Factory Creation //
424    //////////////////////
425    private static final ObjectFactory<NurbsCurveTrans> FACTORY = new ObjectFactory<NurbsCurveTrans>() {
426        @Override
427        protected NurbsCurveTrans create() {
428            return new NurbsCurveTrans();
429        }
430
431        @Override
432        protected void cleanup(NurbsCurveTrans obj) {
433            obj.reset();
434            obj._TM = null;
435            if (Objects.nonNull(obj._childChangeListener))
436                obj._child.removeChangeListener(obj._childChangeListener);
437            obj._child = null;
438        }
439    };
440
441    /**
442     * Recycles a <code>NurbsCurveTrans</code> instance immediately (on the stack when
443     * executing in a StackContext).
444     * 
445     * @param instance The instance to be recycled.
446     */
447    public static void recycle(NurbsCurveTrans instance) {
448        FACTORY.recycle(instance);
449    }
450
451    @SuppressWarnings("unchecked")
452    private static NurbsCurveTrans copyOf(NurbsCurveTrans original) {
453        NurbsCurveTrans obj = FACTORY.object();
454        obj._TM = original._TM.copy();
455        obj._child = original._child.copy();
456        original.copyState(obj);
457        if (!(obj._child instanceof Immutable))
458            obj._child.addChangeListener(obj._childChangeListener);
459        return obj;
460    }
461
462    /**
463     * Do not allow the default constructor to be used.
464     */
465    private NurbsCurveTrans() { }
466
467}