001/**
002 * SubrangeCurve -- A Curve that is a subrange on a parametric object such as a curve or
003 * surface.
004 *
005 * Copyright (C) 2009-2015, Joseph A. Huwaldt. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or modify it under the terms
008 * of the GNU Lesser General Public License as published by the Free Software Foundation;
009 * either version 2.1 of the License, or (at your option) any later version.
010 *
011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along with
016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
018 */
019package geomss.geom;
020
021import geomss.geom.nurbs.BasicNurbsCurve;
022import geomss.geom.nurbs.CurveFactory;
023import geomss.geom.nurbs.NurbsCurve;
024import jahuwaldt.js.param.*;
025import java.text.MessageFormat;
026import java.util.List;
027import java.util.Objects;
028import static java.util.Objects.requireNonNull;
029import javax.measure.converter.ConversionException;
030import javax.measure.quantity.Dimensionless;
031import javax.measure.quantity.Length;
032import javax.measure.unit.Unit;
033import javax.swing.event.ChangeEvent;
034import javax.swing.event.ChangeListener;
035import javolution.context.ObjectFactory;
036import javolution.context.StackContext;
037import javolution.lang.Immutable;
038import javolution.text.Text;
039import javolution.text.TextBuilder;
040import javolution.xml.XMLFormat;
041import javolution.xml.stream.XMLStreamException;
042
043/**
044 * A {@link Curve} element that refers to a {@link ParametricGeometry} object such as a
045 * {@link Curve} or {@link Surface} and a parametric curve on that parametric object.
046 *
047 * <p> Modified by: Joseph A. Huwaldt </p>
048 *
049 * @author Joseph A. Huwaldt, Date: May 28, 2009
050 * @version November 27, 2015
051 */
052@SuppressWarnings({"serial", "CloneableImplementsClone"})
053public final class SubrangeCurve extends AbstractCurve<SubrangeCurve> implements Subrange<Curve> {
054
055    /**
056     * The parametric distance along the parametric geometry where this curve is located.
057     */
058    private Curve _u;
059
060    /**
061     * The parametric geometry object that this curve is located on.
062     */
063    private ParametricGeometry _child;
064
065    /**
066     * Reference to a change listener for this object's child geometry.
067     */
068    private ChangeListener _childChangeListener = new MyChangeListener(this);
069
070    /**
071     * The minimum bounding point for this subrange curve.
072     */
073    private Point _boundsMin;
074
075    /**
076     * The maximum bounding point for this subrange curve.
077     */
078    private Point _boundsMax;
079
080    /**
081     * The arc-length for the parametric curve.
082     */
083    private double uL;
084
085    /**
086     * Returns a {@link SubrangeCurve} instance referring to the specified
087     * {@link ParametricGeometry} and parametric location.
088     *
089     * @param child The {@link ParametricGeometry} object that this curve is subranged
090     *              onto (may not be <code>null</code>).
091     * @param par   A curve of the parametric position (between 0 and 1) along each
092     *              parametric dimension where the curve is located (may not be
093     *              <code>null</code>).
094     * @return the subrange curve having the specified values.
095     * @throws DimensionException if the input child object's parametric dimension is not
096     * compatible with the input parametric distance point.
097     */
098    public static SubrangeCurve newInstance(ParametricGeometry child, Curve par) {
099        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
100        requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par"));
101        int pDim = child.getParDimension();
102        if (par.getPhyDimension() != pDim)
103            throw new DimensionException(RESOURCES.getString("scIncParDim"));
104
105        SubrangeCurve obj = FACTORY.object();
106        obj._u = par;
107        obj._child = child;
108        if (!(par instanceof Immutable))
109            par.addChangeListener(obj._childChangeListener);
110        if (!(child instanceof Immutable))
111            child.addChangeListener(obj._childChangeListener);
112        obj.calcBoundsMinMax();
113
114        return obj;
115    }
116
117    //  A change listener that re-calculates the subrange bounds as well as
118    //  passing the child's change event on to any listeners.
119    private static class MyChangeListener extends ForwardingChangeListener {
120
121        private final SubrangeCurve target;
122
123        public MyChangeListener(SubrangeCurve geom) {
124            super(geom);
125            this.target = geom;
126        }
127
128        @Override
129        public void stateChanged(ChangeEvent e) {
130            //  Re-calculate the bounds of this subrange curve.
131            target.calcBoundsMinMax();
132            super.stateChanged(e);
133        }
134    }
135
136    /**
137     * Returns the parametric position on the child object that this curve refers to.
138     *
139     * @return The parametric position on the child object that this curve refers to.
140     */
141    @Override
142    public Curve getParPosition() {
143        return _u;
144    }
145
146    /**
147     * Sets the parametric position on the child object that this curve refers to.
148     *
149     * @param par The parametric position (between 0 and 1) along each parametric
150     *            dimension where the curve is located (may not be <code>null</code>).
151     */
152    @Override
153    public void setParPosition(Curve par) {
154        requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par"));
155        int pDim = _child.getParDimension();
156        if (par.getPhyDimension() != pDim)
157            throw new DimensionException(RESOURCES.getString("scIncParDim"));
158
159        if (!(_u instanceof Immutable))
160            _u.removeChangeListener(_childChangeListener);
161
162        _u = par;
163
164        if (!(par instanceof Immutable))
165            par.addChangeListener(_childChangeListener);
166
167        calcBoundsMinMax();
168        fireChangeEvent();
169    }
170
171    /**
172     * Returns the child object this point is subranged onto.
173     */
174    @Override
175    public ParametricGeometry getChild() {
176        return _child;
177    }
178
179    /**
180     * Recycles a <code>SubrangeCurve</code> instance immediately (on the stack when
181     * executing in a <code>StackContext</code>).
182     *
183     * @param instance The instance to be recycled.
184     */
185    public static void recycle(SubrangeCurve instance) {
186        FACTORY.recycle(instance);
187    }
188
189    /**
190     * Returns the number of child-elements that make up this geometry element. This
191     * implementation returns the size of the child object.
192     *
193     * @return The number of child-elements that make up this geometry element.
194     */
195    @Override
196    public int size() {
197        return _child.size();
198    }
199
200    /**
201     * Returns the number of physical dimensions of the geometry element.
202     *
203     * @return The number of physical dimensions of this element.
204     */
205    @Override
206    public int getPhyDimension() {
207        return _child.getPhyDimension();
208    }
209
210    /**
211     * Calculate a point on the curve for the given parametric distance along the curve,
212     * <code>p(s)</code>.
213     *
214     * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive).
215     * @return the calculated point
216     */
217    @Override
218    public Point getRealPoint(double s) {
219        Point sT = _u.getRealPoint(s);  //  Convert from 0-1 to parametric position range of _u curve.
220        Point p = _child.getRealPoint(sT);
221        return p;
222    }
223
224    /**
225     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
226     * respect to parametric distance on the curve for the given parametric distance along
227     * the curve, <code>d^{grade}p(s)/d^{grade}s</code>.
228     * <p>
229     * Example:<br>
230     * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
231     * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
232     * </p>
233     *
234     * @param s     Parametric distance to calculate derivatives for (0.0 to 1.0
235     *              inclusive).
236     * @param grade The maximum grade to calculate the derivatives for (1=1st derivative,
237     *              2=2nd derivative, etc)
238     * @return A list of derivatives up to the specified grade of the curve at the
239     *         specified parametric position.
240     * @throws IllegalArgumentException if the grade is &lt; 0.
241     */
242    @Override
243    public List<Vector<Length>> getSDerivatives(double s, int grade) {
244        Point sT = _u.getRealPoint(s);  //  Convert from 0-1 to parametric position range of _u curve.
245
246        //  Calculate the derivatives of the child object.
247        List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade);
248        Point origin = Point.valueOf(dersLst.get(0).get(0));
249
250        //  Sort out what to output.
251        List<Vector<Length>> ders = null;
252        if (_child.getParDimension() == 1)
253            ders = dersLst.get(0);
254
255        else if (_child.getParDimension() == 2) {
256            //  Have to account for derivatives in 2-dimensions.
257
258            //  Get the direction cosines of the parametric curve tangent vector.
259            Vector<Dimensionless> utangent = _u.getTangent(s);
260            double ks = utangent.get(Vector.X).getValue();
261            double kt = utangent.get(Vector.Y).getValue();
262
263            //  Get the surface derivatives in the s and t directions.
264            List<Vector<Length>> dP_dss = dersLst.get(0);
265            List<Vector<Length>> dP_dts = dersLst.get(1);
266
267            //  Create the output array and put in the surface/curve point.
268            ders = dP_dss;
269
270            //  Use the direction cosines to combine the surface s & t direction
271            //  derivatives into the curve's s direction: dP_dsc = ks*dP_dss + kt*dP_dts
272            for (int i = 1; i <= grade; ++i)
273                ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt)));
274
275        } else
276            throw new UnsupportedOperationException(
277                    MessageFormat.format(RESOURCES.getString("scUnsupportedParDim"),
278                            _child.getParDimension()));
279
280        //  Scale all the derivatives by the ratio of parameter arc length to the
281        //  normal parameter range (uL/1).
282        double f = 1;
283        for (int i = 1; i <= grade; ++i) {
284            f *= uL;
285            ders.set(i, ders.get(i).times(f));
286        }
287
288        //  Set the derivative vector origin points.
289        for (int i = 0; i <= grade; ++i)
290            ders.get(i).setOrigin(origin);
291
292        return ders;
293    }
294
295    /**
296     * Return the coordinate point representing the minimum bounding box corner of this
297     * geometry element (e.g.: min X, min Y, min Z).
298     *
299     * @return The minimum bounding box coordinate for this geometry element.
300     * @throws IndexOutOfBoundsException if this list contains no elements.
301     */
302    @Override
303    public Point getBoundsMin() {
304        return _boundsMin;
305    }
306
307    /**
308     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
309     * X, max Y, max Z).
310     *
311     * @return The maximum bounding box coordinate for this geometry element.
312     * @throws IndexOutOfBoundsException if this list contains no elements.
313     */
314    @Override
315    public Point getBoundsMax() {
316        return _boundsMax;
317    }
318
319    private static final int NPTS = 21;
320
321    /**
322     * Calculate the minimum & maximum bounding box corner points of this geometry
323     * element.
324     */
325    private void calcBoundsMinMax() {
326        StackContext.enter();
327        try {
328            //  Space out some points on the subrange curve.
329            List<Double> spacing = GridSpacing.linear(NPTS);
330            PointString<SubrangePoint> pstr = _u.extractGrid(GridRule.PAR, spacing);
331            PointString<Point> str = PointString.newInstance();
332            for (int i = 0; i < NPTS; ++i) {
333                Point ppnt = pstr.get(i).copyToReal();
334                str.add(_child.getRealPoint(ppnt));
335            }
336
337            //  Get the bounds of the grid of points.
338            _boundsMin = StackContext.outerCopy(str.getBoundsMin());
339            _boundsMax = StackContext.outerCopy(str.getBoundsMax());
340
341            //  Compute the arc length of the parametric curve and store it for later use.
342            uL = _u.getArcLength(GTOL * 100).getValue();
343
344        } finally {
345            StackContext.exit();
346        }
347
348    }
349
350    /**
351     * Return <code>true</code> if this SubrangeCurve contains valid and finite numerical
352     * components. A value of <code>false</code> will be returned if any of the coordinate
353     * values are NaN or Inf.
354     *
355     * @return true if this element contains valid and finite numerical values.
356     */
357    @Override
358    public boolean isValid() {
359        return _u.isValid() && _child.isValid();
360    }
361
362    /**
363     * Return <code>true</code> if this curve is degenerate (i.e.: has length less than
364     * the specified tolerance).
365     *
366     * @param tol The tolerance for determining if this curve is degenerate. May not be
367     *            null.
368     * @return true if this curve is degenerate.
369     */
370    @Override
371    public boolean isDegenerate(Parameter<Length> tol) {
372        if (_u.isDegenerate(requireNonNull(tol)))
373            return true;
374        return _child.isDegenerate(tol);
375    }
376
377    /**
378     * Returns the unit in which this curves values are stated.
379     *
380     * @return The unit that this object is stated in.
381     */
382    @Override
383    public Unit<Length> getUnit() {
384        return _child.getUnit();
385    }
386
387    /**
388     * Returns the equivalent to this curve but stated in the specified unit.
389     *
390     * @param unit the length unit of the curve to be returned. May not be null.
391     * @return an equivalent to this curve but stated in the specified unit.
392     * @throws ConversionException if the the input unit is not a length unit.
393     */
394    @Override
395    public SubrangeCurve to(Unit<Length> unit) throws ConversionException {
396        if (unit.equals(getUnit()))
397            return this;
398
399        SubrangeCurve crv = SubrangeCurve.newInstance(_child.to(unit), _u);
400        return copyState(crv);
401    }
402
403    /**
404     * Return the equivalent of this curve converted to the specified number of physical
405     * dimensions. If the number of dimensions is greater than this element, then zeros
406     * are added to the additional dimensions. If the number of dimensions is less than
407     * this element, then the extra dimensions are simply dropped (truncated). If the new
408     * dimensions are the same as the dimension of this element, then this element is
409     * simply returned.
410     *
411     * @param newDim The dimension of the curve to return.
412     * @return The equivalent of this curve converted to the new dimensions.
413     */
414    @Override
415    public SubrangeCurve toDimension(int newDim) {
416        if (getPhyDimension() == newDim)
417            return this;
418
419        SubrangeCurve crv = SubrangeCurve.newInstance(_child.toDimension(newDim), _u);
420        return copyState(crv);
421    }
422
423    /**
424     * Return a NURBS curve representation of this curve to within the specified
425     * tolerance.
426     *
427     * @param tol The greatest possible difference between this curve and the NURBS
428     *            representation returned. May not be null.
429     * @return A NURBS curve that represents this curve to within the specified tolerance.
430     */
431    @Override
432    public NurbsCurve toNurbs(Parameter<Length> tol) {
433        requireNonNull(tol);
434        StackContext.enter();
435        try {
436
437            //  Grid some points onto the curve.
438            PointString<SubrangePoint> str = this.gridToTolerance(tol);
439
440            //  Fit a cubic NURBS curve to the points.
441            int deg = 3;
442            if (str.size() <= 3)
443                deg = str.size() - 1;
444            BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str);
445            copyState(curve);   //  Copy over the super-class state for this curve to the new one.
446
447            return StackContext.outerCopy(curve);
448
449        } finally {
450            StackContext.exit();
451        }
452    }
453
454    /**
455     * Return a new curve that is identical to this one, but with the parameterization
456     * reversed.
457     *
458     * @return A new curve identical to this one, but with the parameterization reversed.
459     */
460    @Override
461    public SubrangeCurve reverse() {
462        SubrangeCurve crv = SubrangeCurve.newInstance(_child, _u.reverse());
463        return copyState(crv);
464    }
465
466    /**
467     * Split this {@link SubrangeCurve} at the specified parametric position returning a
468     * list containing two curves (a lower curve with smaller parametric positions than
469     * "s" and an upper curve with larger parametric positions).
470     *
471     * @param s The parametric position where this curve should be split (must not be 0 or
472     *          1!).
473     * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve.
474     */
475    @Override
476    public GeomList<SubrangeCurve> splitAt(double s) {
477
478        //  Split the parametric position curve.
479        GeomList<Curve> pCrvs = _u.splitAt(s);
480
481        //  Create the output list.
482        GeomList<SubrangeCurve> output = GeomList.newInstance();
483
484        //  Create the lower curve.
485        output.add(SubrangeCurve.newInstance(_child, pCrvs.get(0)));
486        output.add(SubrangeCurve.newInstance(_child, pCrvs.get(1)));
487
488        GeomList.recycle(pCrvs);
489
490        return output;
491    }
492
493    /**
494     * Returns a copy of this {@link SubrangeCurve} instance
495     * {@link javolution.context.AllocatorContext allocated} by the calling thread
496     * (possibly on the stack).
497     *
498     * @return an identical and independent copy of this curve.
499     */
500    @Override
501    public SubrangeCurve copy() {
502        return SubrangeCurve.copyOf(this);
503    }
504
505    /**
506     * Return a copy of this object with any transformations or subranges removed
507     * (applied). This method is not yet implemented and current returns a new
508     * SubrangeCurve with the child and subrange curve geometry copied to real.
509     *
510     * @return A copy of this object with any transformations or subranges removed
511     *         (applied).
512     */
513    @Override
514    public SubrangeCurve copyToReal() {
515        //  TODO: Add support for this.
516        //throw new UnsupportedOperationException( RESOURCES.getString("scCopy2RealNotSupported") );
517        return SubrangeCurve.newInstance((ParametricGeometry)_child.copyToReal(), _u.copyToReal());
518    }
519
520    /**
521     * Returns transformed version of this element. The returned object implements is a
522     * new SubrangeCurve instance with the transformed version of this object's child as
523     * it's child.
524     *
525     * @param transform The transformation to apply to this geometry. May not be null.
526     * @return A new triangle that is identical to this one with the specified
527     *         transformation applied.
528     * @throws DimensionException if this point is not 3D.
529     */
530    @Override
531    public SubrangeCurve getTransformed(GTransform transform) {
532        requireNonNull(transform);
533        return SubrangeCurve.newInstance((ParametricGeometry)_child.getTransformed(transform), _u);
534    }
535
536    /**
537     * Compares this SubrangeCurve against the specified object for strict equality (same
538     * values and same units).
539     *
540     * @param obj the object to compare with.
541     * @return <code>true</code> if this point is identical to that point;
542     *         <code>false</code> otherwise.
543     */
544    @Override
545    public boolean equals(Object obj) {
546        if (this == obj)
547            return true;
548        if ((obj == null) || (obj.getClass() != this.getClass()))
549            return false;
550
551        SubrangeCurve that = (SubrangeCurve)obj;
552        return this._u.equals(that._u)
553                && this._child.equals(that._child)
554                && super.equals(obj);
555    }
556
557    /**
558     * Returns the hash code for this parameter.
559     *
560     * @return the hash code value.
561     */
562    @Override
563    public int hashCode() {
564        return  31*super.hashCode() + Objects.hash(_u, _child);
565    }
566
567    /**
568     * Returns the text representation of this geometry element.
569     *
570     * @return A text representation of this geometry element.
571     */
572    @Override
573    public Text toText() {
574        TextBuilder tmp = TextBuilder.newInstance();
575        String className = this.getClass().getName();
576        tmp.append(className.substring(className.lastIndexOf(".") + 1));
577
578        tmp.append(": {child = {\n");
579        tmp.append(_child.toText());
580        tmp.append("}\n");
581        tmp.append(", u = {\n");
582        tmp.append(_u.toText());
583        tmp.append("}\n");
584
585        tmp.append("}");
586        Text txt = tmp.toText();
587        TextBuilder.recycle(tmp);
588        return txt;
589    }
590
591    /**
592     * Holds the default XML representation for this object.
593     */
594    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
595    protected static final XMLFormat<SubrangeCurve> XML = new XMLFormat<SubrangeCurve>(SubrangeCurve.class) {
596
597        @Override
598        public SubrangeCurve newInstance(Class<SubrangeCurve> cls, InputElement xml) throws XMLStreamException {
599            return FACTORY.object();
600        }
601
602        @Override
603        public void read(InputElement xml, SubrangeCurve obj) throws XMLStreamException {
604            AbstractCurve.XML.read(xml, obj);     // Call parent read.
605
606            Curve par = xml.get("ParPos");
607            obj._u = par;
608            ParametricGeometry child = xml.get("Child");
609            obj._child = child;
610            if (!(par instanceof Immutable))
611                par.addChangeListener(obj._childChangeListener);
612            if (!(child instanceof Immutable))
613                child.addChangeListener(obj._childChangeListener);
614            obj.calcBoundsMinMax();
615
616        }
617
618        @Override
619        public void write(SubrangeCurve obj, OutputElement xml) throws XMLStreamException {
620            AbstractCurve.XML.write(obj, xml);    // Call parent write.
621
622            xml.add(obj._u, "ParPos");
623            xml.add(obj._child, "Child");
624
625        }
626    };
627
628    ///////////////////////
629    // Factory creation. //
630    ///////////////////////
631    private SubrangeCurve() { }
632
633    @SuppressWarnings("unchecked")
634    private static final ObjectFactory<SubrangeCurve> FACTORY = new ObjectFactory<SubrangeCurve>() {
635        @Override
636        protected SubrangeCurve create() {
637            return new SubrangeCurve();
638        }
639
640        @Override
641        protected void cleanup(SubrangeCurve obj) {
642            obj.reset();
643            if (!(obj._u instanceof Immutable))
644                obj._u.removeChangeListener(obj._childChangeListener);
645            obj._u = null;
646            if (!(obj._child instanceof Immutable))
647                obj._child.removeChangeListener(obj._childChangeListener);
648            obj._child = null;
649            obj._boundsMax = null;
650            obj._boundsMin = null;
651        }
652    };
653
654    @SuppressWarnings("unchecked")
655    private static SubrangeCurve copyOf(SubrangeCurve original) {
656        SubrangeCurve obj = FACTORY.object();
657        obj._u = original._u.copy();
658        obj._child = original._child.copy();
659        obj._boundsMax = original._boundsMax.copy();
660        obj._boundsMin = original._boundsMin.copy();
661        if (!(obj._u instanceof Immutable))
662            obj._u.addChangeListener(obj._childChangeListener);
663        if (!(obj._child instanceof Immutable))
664            obj._child.addChangeListener(obj._childChangeListener);
665        original.copyState(obj);
666        return obj;
667    }
668
669}