001/**
002 * SubrangePoint -- A GeomPoint that is a subrange on a parametric object such as a curve
003 * or surface.
004 *
005 * Copyright (C) 2009-2017, 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 jahuwaldt.js.param.Parameter;
022import jahuwaldt.js.param.Vector3D;
023import java.text.MessageFormat;
024import java.util.Objects;
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.ChangeListener;
030import javolution.context.ObjectFactory;
031import javolution.context.StackContext;
032import javolution.lang.Immutable;
033import javolution.xml.XMLFormat;
034import javolution.xml.stream.XMLStreamException;
035import org.jscience.mathematics.vector.Float64Vector;
036
037/**
038 * A {@link GeomPoint} element that refers to a {@link ParametricGeometry} object such as
039 * a {@link Curve} or {@link Surface} and a parametric position on that parametric object.
040 *
041 * <p> Modified by: Joseph A. Huwaldt </p>
042 *
043 * @author Joseph A. Huwaldt, Date: May 28, 2009
044 * @version October 14, 2017
045 */
046@SuppressWarnings({"serial", "CloneableImplementsClone"})
047public final class SubrangePoint extends GeomPoint implements Subrange<GeomPoint> {
048
049    /**
050     * Tolerance allowed on parametric spacing being == 0 or == 1.
051     */
052    protected static final double TOL_ST = Parameter.EPS;
053
054    /**
055     * The parametric distance along the parametric geometry where this point is located.
056     */
057    private GeomPoint _u;
058
059    /**
060     * The parametric geometry object that this point is located on.
061     */
062    private ParametricGeometry _child;
063
064    /**
065     * Reference to a change listener for this object's child geometry.
066     */
067    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
068
069    /**
070     * Returns a {@link SubrangePoint} instance referring to the specified {@link Curve}
071     * and parametric location.
072     *
073     * @param child The {@link Curve} object that this point is subranged onto (may not be
074     *              <code>null</code>).
075     * @param s     The parametric s-position (between 0 and 1) along curve where the
076     *              point is located (may not be <code>null</code>).
077     * @return the subrange point having the specified values.
078     */
079    public static SubrangePoint newInstance(Curve child, double s) {
080        if (s < 0 || s > 1)
081            throw new DimensionException(
082                    MessageFormat.format(RESOURCES.getString("crvInvalidSValue"), s));
083        return newInstance(child, Point.valueOf(s));
084    }
085
086    /**
087     * Returns a {@link SubrangePoint} instance referring to the specified {@link Surface}
088     * and parametric location.
089     *
090     * @param child The {@link Surface} object that this point is subranged onto (may not
091     *              be <code>null</code>).
092     * @param s     The parametric s-position (between 0 and 1) along surface where the
093     *              point is located.
094     * @param t     The parametric t-position (between 0 and 1) along surface where the
095     *              point is located.
096     * @return the subrange point having the specified values.
097     */
098    public static SubrangePoint newInstance(Surface child, double s, double t) {
099        if (s < -TOL_ST || s > 1 + TOL_ST)
100            throw new IllegalArgumentException(
101                    MessageFormat.format(RESOURCES.getString("srfInvalidSValue"), s));
102        if (t < -TOL_ST || t > 1 + TOL_ST)
103            throw new IllegalArgumentException(
104                    MessageFormat.format(RESOURCES.getString("srfInvalidTValue"), t));
105        return newInstance(child, Point.valueOf(s, t));
106    }
107
108    /**
109     * Returns a {@link SubrangePoint} instance referring to the specified
110     * {@link ParametricGeometry} and parametric location.
111     *
112     * @param child The {@link ParametricGeometry} object that this point is subranged
113     *              onto (may not be <code>null</code>).
114     * @param par   The parametric position (between 0 and 1) along each parametric
115     *              dimension where the point is located (may not be <code>null</code>).
116     * @return the subrange point having the specified values.
117     * @throws DimensionException if the input child object's parametric dimension is not
118     * compatible with the input parametric distance point.
119     */
120    public static SubrangePoint newInstance(ParametricGeometry child, GeomPoint par) {
121        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
122        requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par"));
123        if (par.getPhyDimension() != child.getParDimension())
124            throw new DimensionException(RESOURCES.getString("scIncParDim"));
125
126        SubrangePoint obj = FACTORY.object();
127        obj._u = par;
128        obj._child = child;
129
130        if (!(par instanceof Immutable))
131            par.addChangeListener(obj._childChangeListener);
132        if (!(child instanceof Immutable))
133            child.addChangeListener(obj._childChangeListener);
134
135        return obj;
136    }
137
138    /**
139     * Returns the parametric position on the child object that this point refers to.
140     */
141    @Override
142    public GeomPoint getParPosition() {
143        return _u;
144    }
145
146    /**
147     * Sets the parametric position on the child object that this point refers to.
148     *
149     * @param par The parametric position (between 0 and 1) along each parametric
150     *            dimension where the point is located (may not be <code>null</code>).
151     */
152    @Override
153    public void setParPosition(GeomPoint par) {
154        requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"),"par"));
155        if (par.getPhyDimension() != _child.getParDimension())
156            throw new DimensionException(RESOURCES.getString("scIncParDim"));
157
158        if (!(_u instanceof Immutable))
159            _u.removeChangeListener(_childChangeListener);
160
161        _u = par;
162
163        if (!(par instanceof Immutable))
164            par.addChangeListener(_childChangeListener);
165
166        fireChangeEvent();
167    }
168
169    /**
170     * Returns the child object this point is subranged onto.
171     */
172    @Override
173    public ParametricGeometry getChild() {
174        return _child;
175    }
176
177    /**
178     * Return a copy of the child object transformed into a concrete {@link GeomPoint}
179     * object (information on the parametric position on the child parametric geometry is
180     * removed).
181     *
182     * @return A copy of this object with the subrange removed (applied).
183     */
184    @Override
185    public Point copyToReal() {
186        return _child.getRealPoint(_u);
187    }
188
189    /**
190     * Recycles a <code>SubrangePoint</code> instance immediately (on the stack when
191     * executing in a <code>StackContext</code>).
192     *
193     * @param instance The instance to be recycled.
194     */
195    public static void recycle(SubrangePoint instance) {
196        FACTORY.recycle(instance);
197    }
198
199    /**
200     * Return an immutable version of this point.
201     *
202     * @return An immutable version of this point.
203     */
204    @Override
205    public Point immutable() {
206        return Point.valueOf(this);
207    }
208
209    /**
210     * Returns the number of physical dimensions of the geometry element.
211     *
212     * @return The number of physical dimensions of the geometry element.
213     */
214    @Override
215    public int getPhyDimension() {
216        return _child.getPhyDimension();
217    }
218
219    /**
220     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
221     * this point's {@link #getUnit unit}.
222     *
223     * @param i the dimension index.
224     * @return the value of the Parameter at <code>i</code>.
225     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
226     */
227    @Override
228    public double getValue(int i) {
229        StackContext.enter();
230        try {
231            Point pTrans = copyToReal();
232            double value = pTrans.getValue(i);
233            return value;
234        } finally {
235            StackContext.exit();
236        }
237    }
238
239    /**
240     * Returns the value of a coordinate in this point as a <code>double</code>, stated in
241     * the specified unit.
242     *
243     * @param i    the dimension index.
244     * @param unit the unit to return the value in. May not be null.
245     * @return the value of the Parameter at <code>i</code> in the specified unit.
246     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
247     */
248    @Override
249    public double getValue(int i, Unit<Length> unit) {
250        StackContext.enter();
251        try {
252            Point pTrans = copyToReal();
253            double value = pTrans.getValue(i, requireNonNull(unit));
254            return value;
255        } finally {
256            StackContext.exit();
257        }
258    }
259
260    /**
261     * Returns the square of the Euclidean norm, magnitude, or length value of the vector
262     * from the origin to this point (the dot product of the origin-to-this-point vector
263     * and itself). This is slightly faster than calling <code>normValue</code> if the
264     * squared value is all that is needed.
265     *
266     * @return <code>this.normSq().getValue()</code>.
267     * @see #normValue() 
268     */
269    @Override
270    public double normSqValue() {
271        StackContext.enter();
272        try {
273            Point pTrans = copyToReal();
274            double norm2V = pTrans.normSqValue();
275            return norm2V;
276        } finally {
277            StackContext.exit();
278        }
279    }
280    
281    /**
282     * Returns the sum of this point with the one specified. The unit of the output point
283     * will be the units of this point.
284     *
285     * @param that the point to be added. May not be null.
286     * @return <code>this + that</code>.
287     * @throws DimensionException if point dimensions are different.
288     */
289    @Override
290    public Point plus(GeomPoint that) {
291        Point pTrans = copyToReal();
292        Point point = pTrans.plus(that);
293        return point;
294    }
295
296    /**
297     * Adds the specified parameter to each component of this point. The unit of the
298     * output point will be the units of this point.
299     *
300     * @param that the parameter to be added to each component of this point. May not be
301     *             null.
302     * @return <code>this + that</code>.
303     */
304    @Override
305    public Point plus(Parameter<Length> that) {
306        Point pTrans = copyToReal();
307        Point point = pTrans.plus(that);
308        return point;
309    }
310
311    /**
312     * Returns the difference between this point and the one specified. The unit of the
313     * output point will be the units of this point.
314     *
315     * @param that the point to be subtracted from this point. May not be null.
316     * @return <code>this - that</code>.
317     * @throws DimensionException if point dimensions are different.
318     */
319    @Override
320    public Point minus(GeomPoint that) {
321        Point pTrans = copyToReal();
322        Point point = pTrans.minus(that);
323        return point;
324    }
325
326    /**
327     * Subtracts the specified parameter from each component of this point. The unit of
328     * the output point will be the units of this point.
329     *
330     * @param that the parameter to be subtracted from each component of this point. May
331     *             not be null.
332     * @return <code>this - that</code>.
333     */
334    @Override
335    public Point minus(Parameter<Length> that) {
336        Point pTrans = copyToReal();
337        Point point = pTrans.minus(that);
338        return point;
339    }
340
341    /**
342     * Returns the negation of this point (all the values of each dimension negated).
343     *
344     * @return <code>-this</code>
345     */
346    @Override
347    public Point opposite() {
348        Point pTrans = copyToReal();
349        Point point = pTrans.opposite();
350        return point;
351    }
352
353    /**
354     * Returns the product of this point with the specified coefficient.
355     *
356     * @param k the coefficient multiplier.
357     * @return <code>this ยท k</code>
358     */
359    @Override
360    public Point times(double k) {
361        Point pTrans = copyToReal();
362        Point point = pTrans.times(k);
363        return point;
364    }
365
366    /**
367     * Return <code>true</code> if this point contains valid and finite numerical
368     * components. A value of <code>false</code> will be returned if any of the coordinate
369     * values are NaN or Inf.
370     *
371     * @return true if this point contains valid and finite numerical components.
372     */
373    @Override
374    public boolean isValid() {
375        return _u.isValid() && _child.isValid();
376    }
377
378    /**
379     * Returns a copy of this SubrangePoint instance
380     * {@link javolution.context.AllocatorContext allocated} by the calling thread
381     * (possibly on the stack).
382     *
383     * @return an identical and independent copy of this point.
384     */
385    @Override
386    public SubrangePoint copy() {
387        return SubrangePoint.copyOf(this);
388    }
389
390    /**
391     * Returns the unit in which the {@link #getValue values} in this point are stated in.
392     *
393     * @return The unit in which the coordinate values in this point are stated in.
394     */
395    @Override
396    public Unit<Length> getUnit() {
397        return _child.getUnit();
398    }
399
400    /**
401     * Returns the equivalent to this point but stated in the specified unit.
402     *
403     * @param unit the length unit of the point to be returned. May not be null.
404     * @return an equivalent to this point but stated in the specified unit.
405     * @throws ConversionException if the the input unit is not a length unit.
406     */
407    @Override
408    public GeomPoint to(Unit<Length> unit) throws ConversionException {
409        if (unit.equals(getUnit()))
410            return this;
411        return copyToReal().to(unit);
412    }
413
414    /**
415     * Returns a Vector3D representation of this transformed point if possible.
416     *
417     * @return A Vector3D that is equivalent to this transformed point
418     * @throws DimensionException if this point has any number of dimensions other than 3.
419     */
420    @Override
421    public Vector3D<Length> toVector3D() {
422        Point pTrans = copyToReal();
423        Vector3D<Length> V = pTrans.toVector3D();
424        return V;
425    }
426
427    /**
428     * Returns the values stored in this transformed point, stated in this point's
429     * {@link #getUnit unit}, as a Float64Vector.
430     *
431     * @return A Float64Vector containing the coordinate values in this point.
432     */
433    @Override
434    public Float64Vector toFloat64Vector() {
435        StackContext.enter();
436        try {
437            Point pTrans = copyToReal();
438            Float64Vector V = pTrans.toFloat64Vector();
439            return V;
440        } finally {
441            StackContext.exit();
442        }
443    }
444
445    /**
446     * Return the equivalent of this point converted to the specified number of physical
447     * dimensions. If the number of dimensions is greater than this element, then zeros
448     * are added to the additional dimensions. If the number of dimensions is less than
449     * this element, then the extra dimensions are simply dropped (truncated). If the new
450     * dimensions are the same as the dimension of this element, then this element is
451     * simply returned. NOTE: The returned point is no longer a SubrangePoint.
452     *
453     * @param newDim The dimension of the point to return.
454     * @return The equivalent of this point converted to the new dimensions.
455     */
456    @Override
457    public GeomPoint toDimension(int newDim) {
458        if (newDim == this.getPhyDimension())
459            return this;
460
461        GeomPoint newP = copyToReal().toDimension(newDim);
462        return newP;
463    }
464
465    /**
466     * Returns transformed version of this element. The returned object implements
467     * {@link GeomTransform} and contains this element as a child.
468     *
469     * @param transform The transformation to apply to this geometry. May not be null.
470     * @return A new triangle that is identical to this one with the specified
471     *         transformation applied.
472     * @throws DimensionException if this point is not 3D.
473     */
474    @Override
475    public GeomPointTrans getTransformed(GTransform transform) {
476        return GeomPointTrans.newInstance(this, requireNonNull(transform));
477    }
478
479    /**
480     * Compares this SubrangePoint against the specified object for strict equality (same
481     * values and same units).
482     *
483     * @param obj the object to compare with.
484     * @return <code>true</code> if this point is identical to that point;
485     *         <code>false</code> otherwise.
486     */
487    @Override
488    public boolean equals(Object obj) {
489        if (this == obj)
490            return true;
491        if ((obj == null) || (obj.getClass() != this.getClass()))
492            return false;
493
494        SubrangePoint that = (SubrangePoint)obj;
495        return this._u.equals(that._u)
496                && this._child.equals(that._child)
497                && super.equals(obj);
498    }
499
500    /**
501     * Returns the hash code for this parameter.
502     *
503     * @return the hash code value.
504     */
505    @Override
506    public int hashCode() {
507        return 31*super.hashCode() + Objects.hash(_u, _child);
508    }
509
510    /**
511     * Holds the default XML representation for this object.
512     */
513    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
514    protected static final XMLFormat<SubrangePoint> XML = new XMLFormat<SubrangePoint>(SubrangePoint.class) {
515
516        @Override
517        public SubrangePoint newInstance(Class<SubrangePoint> cls, InputElement xml) throws XMLStreamException {
518            return FACTORY.object();
519        }
520
521        @Override
522        public void read(InputElement xml, SubrangePoint obj) throws XMLStreamException {
523            GeomPoint.XML.read(xml, obj);     // Call parent read.
524
525            GeomPoint par = xml.get("ParPos");
526            obj._u = par;
527            ParametricGeometry child = xml.get("Child");
528            obj._child = child;
529
530            if (!(par instanceof Immutable))
531                par.addChangeListener(obj._childChangeListener);
532            if (!(child instanceof Immutable))
533                child.addChangeListener(obj._childChangeListener);
534        }
535
536        @Override
537        public void write(SubrangePoint obj, OutputElement xml) throws XMLStreamException {
538            GeomPoint.XML.write(obj, xml);    // Call parent write.
539
540            xml.add(obj._u, "ParPos");
541            xml.add(obj._child, "Child");
542
543        }
544    };
545
546    ///////////////////////
547    // Factory creation. //
548    ///////////////////////
549    private SubrangePoint() { }
550
551    @SuppressWarnings("unchecked")
552    private static final ObjectFactory<SubrangePoint> FACTORY = new ObjectFactory<SubrangePoint>() {
553        @Override
554        protected SubrangePoint create() {
555            return new SubrangePoint();
556        }
557
558        @Override
559        protected void cleanup(SubrangePoint obj) {
560            obj.reset();
561            if (!(obj._u instanceof Immutable))
562                obj._u.removeChangeListener(obj._childChangeListener);
563            obj._u = null;
564            if (!(obj._child instanceof Immutable))
565                obj._child.removeChangeListener(obj._childChangeListener);
566            obj._child = null;
567        }
568    };
569
570    @SuppressWarnings("unchecked")
571    private static SubrangePoint copyOf(SubrangePoint original) {
572        SubrangePoint obj = FACTORY.object();
573        obj._u = original._u.copy();
574        obj._child = original._child.copy();
575        if (!(obj._u instanceof Immutable))
576            obj._u.addChangeListener(obj._childChangeListener);
577        if (!(obj._child instanceof Immutable))
578            obj._child.addChangeListener(obj._childChangeListener);
579        original.copyState(obj);
580        return obj;
581    }
582
583}