001/**
002 * VectorTrans -- A GeomTransform that has an GeomVector for a child.
003 *
004 * Copyright (C) 2009-2018, 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 Lesser 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 jahuwaldt.js.param.Parameter;
021import jahuwaldt.js.param.ParameterVector;
022import java.text.MessageFormat;
023import java.util.Objects;
024import static java.util.Objects.requireNonNull;
025import javax.measure.converter.ConversionException;
026import javax.measure.quantity.Dimensionless;
027import javax.measure.quantity.Quantity;
028import javax.measure.unit.Unit;
029import javax.swing.event.ChangeListener;
030import javolution.context.ObjectFactory;
031import javolution.context.StackContext;
032import javolution.lang.Immutable;
033import javolution.util.FastTable;
034import javolution.xml.XMLFormat;
035import javolution.xml.stream.XMLStreamException;
036import org.jscience.mathematics.number.Float64;
037import org.jscience.mathematics.vector.Float64Vector;
038
039/**
040 * A {@link GeomTransform} element that refers to a {@link GeomVector} object and
041 * masquerades as a GeomVector object itself.
042 *
043 * <p> Modified by: Joseph A. Huwaldt </p>
044 *
045 * @author Joseph A. Huwaldt, Date: June 13, 2009
046 * @version April 10, 2018
047 *
048 * @param <Q> The Quantity (unit type) of the elements of this vector.
049 */
050@SuppressWarnings({"serial", "CloneableImplementsClone"})
051public final class VectorTrans<Q extends Quantity> extends GeomVector<Q> implements GeomTransform<GeomVector> {
052
053    /**
054     * The transformation represented by this transformation element.
055     */
056    private GTransform _TM;
057
058    /**
059     * Just the rotational and scale portion of the transformation.
060     */
061    private GTransform _TM_RotScale;
062
063    /**
064     * The object that is the child of this transform.
065     */
066    private GeomVector<Q> _child;
067
068    /**
069     * Reference to a change listener for this object's child.
070     */
071    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
072
073    /**
074     * Returns a 3D {@link VectorTrans} instance holding the specified {@link GeomVector}
075     * and {@link GTransform}.
076     *
077     * @param <Q>       The Quantity (unit type) of this vector.
078     * @param child     The vector that is the child of this transform element (may not be
079     *                  <code>null</code>).
080     * @param transform The transform held by this transform element (may not be
081     *                  <code>null</code>).
082     * @return the transform element having the specified values.
083     * @throws DimensionException if the input element is not 3D.
084     */
085    public static <Q extends Quantity> VectorTrans<Q> newInstance(GeomVector<Q> child, GTransform transform) {
086        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
087        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
088
089        if (child.getPhyDimension() != 3)
090            throw new DimensionException(
091                    MessageFormat.format(RESOURCES.getString("dimensionNot3"), "vector", child.getPhyDimension()));
092
093        VectorTrans<Q> obj = FACTORY.object();
094        obj._TM = transform;
095        obj._TM_RotScale = GTransform.valueOf(transform.getRotationScale());
096        obj._child = child;
097        child.copyState(obj);
098
099        //  Listen for changes to the child object and pass them on.
100        if (!(child instanceof Immutable))
101            child.addChangeListener(obj._childChangeListener);
102
103        return obj;
104    }
105
106    /**
107     * Returns the transformation represented by this transformation element.
108     */
109    @Override
110    public GTransform getTransform() {
111        return _TM;
112    }
113
114    /**
115     * Returns the total transformation represented by an entire chain of GeomTransform
116     * objects below this one.
117     */
118    @Override
119    public GTransform getTotalTransform() {
120        return GeomUtil.getTotalTransform(this);
121    }
122
123    /**
124     * Sets the transformation represented by this transformation element.
125     *
126     * @param transform The transform to set this transform element to (may not be
127     *                  <code>null</code>).
128     */
129    @Override
130    public void setTransform(GTransform transform) {
131        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
132        _TM = transform;
133        _TM_RotScale = GTransform.valueOf(transform.getRotationScale());
134        fireChangeEvent();
135    }
136
137    /**
138     * Returns the child object transformed by this transform element.
139     */
140    @Override
141    public GeomVector<Q> getChild() {
142        return _child;
143    }
144
145    /**
146     * Return a copy of the child object transformed by this transformation.
147     *
148     * @return A copy of the child object transformed by this transformation.
149     */
150    @Override
151    public Vector<Q> copyToReal() {
152        Unit<Q> unit = (Unit<Q>)_child.getUnit();
153        Float64Vector dirV;
154        StackContext.enter();
155        try {
156
157            //  The direction of the vector is transformed by rotation and scaling only, not by translation!
158            Point pt2 = _TM_RotScale.transform(Point.valueOf(_child));
159
160            FastTable<Float64> values = FastTable.newInstance();
161            int dim = pt2.getPhyDimension();
162            for (int i = 0; i < dim; ++i) {
163                values.add(Float64.valueOf(pt2.getValue(i)));
164            }
165            dirV = Float64Vector.valueOf(values);
166            dirV = StackContext.outerCopy(dirV);
167
168        } finally {
169            StackContext.exit();
170        }
171
172        Vector<Q> V = Vector.valueOf(dirV, unit);
173        this.copyState(V);
174
175        //  The origin is transformed by rotation, scale, AND translation.
176        V.setOrigin(_TM.transform(_child.getOrigin()));
177
178        return V;
179    }
180
181    /**
182     * Recycles a <code>VectorTrans</code> instance immediately (on the stack when
183     * executing in a <code>StackContext</code>).
184     *
185     * @param instance The instance to be recycled.
186     */
187    public static void recycle(VectorTrans instance) {
188        FACTORY.recycle(instance);
189    }
190
191    /**
192     * Returns the number of physical dimensions of the geometry element. This
193     * implementation always returns 3.
194     */
195    @Override
196    public int getPhyDimension() {
197        return 3;
198    }
199
200    /**
201     * Returns the value of the Parameter in this vector as a <code>double</code>, stated
202     * in this vector's {@link #getUnit unit}.
203     *
204     * @param i the dimension index.
205     * @return the value of the Parameter at <code>i</code>.
206     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
207     */
208    @Override
209    public double getValue(int i) {
210        Vector pTrans = copyToReal();
211        double value = pTrans.getValue(i);
212        Vector.recycle(pTrans);
213        return value;
214    }
215
216    /**
217     * Returns the value of the Parameter in this vector as a <code>double</code>, stated
218     * in the specified units.
219     *
220     * @param i    the dimension index.
221     * @param unit the unit to return the value in. May not be null.
222     * @return the value of the Parameter at <code>i</code> in the specified unit.
223     * @throws IndexOutOfBoundsException <code>(i &lt; 0) || (i &ge; getPhyDimension())</code>
224     */
225    @Override
226    public double getValue(int i, Unit<Q> unit) {
227        requireNonNull(unit);
228        Vector pTrans = copyToReal();
229        double value = pTrans.getValue(i, unit);
230        Vector.recycle(pTrans);
231        return value;
232    }
233
234    /**
235     * Set the origin point for this vector. The origin is used as a reference for drawing
236     * the vector and is <i>not</i> a part of the state of this vector and is not used in
237     * any calculations with this vector.
238     * 
239     * @param origin The new origin point for the vector.  May not be null.
240     */
241    @Override
242    public void setOrigin(Point origin) {
243        requireNonNull(origin, MessageFormat.format(RESOURCES.getString("paramNullErr"), "origin"));
244        _child.setOrigin(_TM.transpose().transform(origin));
245    }
246
247    /**
248     * Return the origin point for this vector. If no origin has been explicitly set, then
249     * the coordinate system origin is returned.
250     *
251     * @return The origin point for this vector.
252     */
253    @Override
254    public Point getOrigin() {
255        return _TM.transform(_child.getOrigin());
256    }
257
258    /**
259     * Returns the {@link #norm}, magnitude, or length value of the vector from the origin
260     * to this point (square root of the dot product of the origin-to-this-point vector
261     * and itself).
262     *
263     * @return <code>this.norm().doubleValue()</code>.
264     */
265    @Override
266    public double normValue() {
267        Vector pTrans = copyToReal();
268        double normV = pTrans.normValue();
269        Vector.recycle(pTrans);
270        return normV;
271    }
272
273    /**
274     * Returns the negation of this vector.
275     *
276     * @return <code>-this</code>.
277     */
278    @Override
279    public Vector<Q> opposite() {
280        Vector<Q> pTrans = copyToReal();
281        Vector<Q> V = pTrans.opposite();
282        Vector.recycle(pTrans);
283        return V;
284    }
285
286    /**
287     * Returns the sum of this vector with the one specified. The unit of the output
288     * vector will be the units of this vector.
289     *
290     * @param that the vector to be added. May not be null.
291     * @return <code>this + that</code>.
292     * @throws DimensionException if vector dimensions are different.
293     */
294    @Override
295    public Vector<Q> plus(GeomVector<Q> that) {
296        requireNonNull(that);
297        Vector<Q> pTrans = copyToReal();
298        Vector<Q> V = pTrans.plus(that);
299        Vector.recycle(pTrans);
300        return V;
301    }
302
303    /**
304     * Returns the sum of this vector with the parameter specified. The input parameter is
305     * added to each component of this vector. The unit of the output vector will be the
306     * units of this vector.
307     *
308     * @param that the parameter to be added to each element of this vector. May not be null.
309     * @return <code>this + that</code>.
310     */
311    @Override
312    public Vector<Q> plus(Parameter<Q> that) {
313        requireNonNull(that);
314        Vector<Q> pTrans = copyToReal();
315        Vector<Q> V = pTrans.plus(that);
316        Vector.recycle(pTrans);
317        return V;
318    }
319
320    /**
321     * Returns the difference between this vector and the one specified. The unit of the
322     * output vector will be the units of this vector.
323     *
324     * @param that the vector to be subtracted from this vector. May not be null.
325     * @return <code>this - that</code>.
326     * @throws DimensionException if vector dimensions are different.
327     */
328    @Override
329    public Vector<Q> minus(GeomVector<Q> that) {
330        requireNonNull(that);
331        Vector<Q> pTrans = copyToReal();
332        Vector<Q> V = pTrans.minus(that);
333        Vector.recycle(pTrans);
334        return V;
335    }
336
337    /**
338     * Subtracts the supplied Parameter from each element of this vector and returns the
339     * result. The unit of the output vector will be the units of this vector.
340     *
341     * @param that the Parameter to be subtracted from each element of this vector. May
342     *             not be null.
343     * @return <code>this - that</code>.
344     */
345    @Override
346    public Vector<Q> minus(Parameter<Q> that) {
347        requireNonNull(that);
348        Vector<Q> pTrans = copyToReal();
349        Vector<Q> V = pTrans.minus(that);
350        Vector.recycle(pTrans);
351        return V;
352    }
353
354    /**
355     * Returns the product of this vector with the specified coefficient (dimensionless).
356     *
357     * @param k the coefficient multiplier.
358     * @return <code>this · k</code>
359     */
360    @Override
361    public Vector<Q> times(double k) {
362        Vector<Q> pTrans = copyToReal();
363        Vector<Q> V = pTrans.times(k);
364        Vector.recycle(pTrans);
365        return V;
366    }
367
368    /**
369     * Returns the product of this vector with the specified coefficient.
370     *
371     * @param k the coefficient multiplier. May not be null.
372     * @return <code>this · k</code>
373     */
374    @Override
375    public Vector<? extends Quantity> times(Parameter<?> k) {
376        requireNonNull(k);
377        Vector pTrans = copyToReal();
378        Vector V = pTrans.times(k);
379        Vector.recycle(pTrans);
380        return V;
381    }
382
383    /**
384     * Returns the dot product (scalar product) of this vector with the one specified.
385     *
386     * @param that the vector multiplier. May not be null.
387     * @return <code>this · that</code>
388     * @throws DimensionException if <code>this.dimension() != that.dimension()</code>
389     * @see <a href="http://en.wikipedia.org/wiki/Dot_product">
390     * Wikipedia: Dot Product</a>
391     */
392    @Override
393    public Parameter<? extends Quantity> times(GeomVector<?> that) {
394        requireNonNull(that);
395        Vector pTrans = copyToReal();
396        Parameter P = pTrans.times(that);
397        Vector.recycle(pTrans);
398        return P;
399    }
400
401    /**
402     * Returns the element-by-element product of this vector with the one specified.
403     *
404     * @param that the vector multiplier. May not be null.
405     * @return <code>this .* that</code>
406     * @throws DimensionException if <code>this.dimension() != that.dimension()</code>
407     */
408    @Override
409    public Vector<? extends Quantity> timesEBE(GeomVector<?> that) {
410        requireNonNull(that);
411        Vector<Q> pTrans = copyToReal();
412        Vector V = pTrans.timesEBE(that);
413        Vector.recycle(pTrans);
414        return V;
415    }
416
417    /**
418     * Returns the cross product of two vectors.
419     *
420     * @param that the vector multiplier. May not be null.
421     * @return <code>this x that</code>
422     * @throws DimensionException if
423     * <code>(that.getDimension() != this.getDimension())</code>
424     * @see <a href="http://en.wikipedia.org/wiki/Cross_product">
425     * Wikipedia: Cross Product</a>
426     */
427    @Override
428    public Vector<? extends Quantity> cross(GeomVector<?> that) {
429        requireNonNull(that);
430        Vector<Q> pTrans = copyToReal();
431        Vector V = pTrans.cross(that);
432        Vector.recycle(pTrans);
433        return V;
434    }
435
436    /**
437     * Returns this vector with each element divided by the specified divisor
438     * (dimensionless).
439     *
440     * @param divisor the divisor.
441     * @return <code>this / divisor</code>.
442     */
443    @Override
444    public Vector<Q> divide(double divisor) {
445        Vector<Q> pTrans = copyToReal();
446        Vector<Q> V = pTrans.divide(divisor);
447        Vector.recycle(pTrans);
448        return V;
449    }
450
451    /**
452     * Returns this vector with each element divided by the specified divisor.
453     *
454     * @param that the divisor. May not be null.
455     * @return <code>this / that</code>.
456     */
457    @Override
458    public Vector<? extends Quantity> divide(Parameter<?> that) {
459        requireNonNull(that);
460        Vector<Q> pTrans = copyToReal();
461        Vector V = pTrans.divide(that);
462        Vector.recycle(pTrans);
463        return V;
464    }
465
466    /**
467     * Return an immutable version of this vector.
468     *
469     * @return An immutable version of this vector.
470     */
471    @Override
472    public Vector<Q> immutable() {
473        return copyToReal();
474    }
475
476    /**
477     * Returns this vector converted to a unit vector by dividing all the vector's
478     * elements by the length ({@link #norm}) of this vector.
479     *
480     * @return This vector converted to a unit vector.
481     */
482    @Override
483    public Vector<Dimensionless> toUnitVector() {
484        Vector<Q> pTrans = copyToReal();
485        Vector<Dimensionless> V = pTrans.toUnitVector();
486        Vector.recycle(pTrans);
487        return V;
488    }
489
490    /**
491     * Returns the unit in which the {@link #getValue values} in this vector are stated
492     * in.
493     */
494    @Override
495    public Unit getUnit() {
496        return _child.getUnit();
497    }
498
499    /**
500     * Returns the equivalent to this vector but stated in the specified unit.
501     *
502     * @param unit the unit of the vector to be returned. May not be null.
503     * @return an equivalent to this vector but stated in the specified unit.
504     * @throws ConversionException if the the input unit is not compatible with this unit.
505     */
506    @Override
507    public GeomVector to(Unit unit) throws ConversionException {
508        if (unit.equals(getUnit()))
509            return this;
510
511        GeomVector newV = _child.to(unit);
512        VectorTrans result = VectorTrans.newInstance(newV, _TM);
513        this.copyState(result);
514
515        return result;
516    }
517
518    /**
519     * Returns a <code>ParameterVector</code> representation of this vector.
520     *
521     * @return A ParameterVector that is equivalent to this vector.
522     */
523    @Override
524    public ParameterVector<Q> toParameterVector() {
525        Vector<Q> pTrans = copyToReal();
526        ParameterVector<Q> V = pTrans.toParameterVector();
527        Vector.recycle(pTrans);
528        return V;
529    }
530
531    /**
532     * Returns a <code>Float64Vector</code> containing the elements of this vector stated
533     * in the current units.
534     *
535     * @return A Float64Vector that contains the elements of this vector in the current
536     *         units.
537     */
538    @Override
539    public Float64Vector toFloat64Vector() {
540        Vector<Q> pTrans = copyToReal();
541        Float64Vector V = pTrans.toFloat64Vector();
542        Vector.recycle(pTrans);
543        return V;
544    }
545
546    /**
547     * Returns transformed version of this element. The returned object implements
548     * {@link GeomTransform} and contains this element as a child.
549     * 
550     * @param transform The transform to apply to this vector. May not be null.
551     */
552    @Override
553    public VectorTrans<Q> getTransformed(GTransform transform) {
554        return VectorTrans.newInstance(this, requireNonNull(transform));
555    }
556
557    /**
558     * Return the equivalent of this vector converted to the specified number of physical
559     * dimensions. This implementation will throw an exception if the specified dimension
560     * is anything other than 3.
561     *
562     * @param newDim The dimension of the vector to return. MUST equal 3.
563     * @return The equivalent of this vector converted to the new dimensions.
564     * @throws IllegalArgumentException if the new dimension is anything other than 3.
565     */
566    @Override
567    public VectorTrans<Q> toDimension(int newDim) {
568        if (newDim == 3)
569            return this;
570
571        throw new IllegalArgumentException(
572                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
573    }
574
575    /**
576     * Compares this VectorTrans against the specified object for strict equality (same
577     * values and same units).
578     *
579     * @param obj the object to compare with.
580     * @return <code>true</code> if this point is identical to that point;
581     *         <code>false</code> otherwise.
582     */
583    @Override
584    public boolean equals(Object obj) {
585        if (this == obj)
586            return true;
587        if ((obj == null) || (obj.getClass() != this.getClass()))
588            return false;
589
590        VectorTrans that = (VectorTrans)obj;
591        return this._TM.equals(that._TM)
592                && this._child.equals(that._child)
593                && super.equals(obj);
594    }
595
596    /**
597     * Returns the hash code for this parameter.
598     *
599     * @return the hash code value.
600     */
601    @Override
602    public int hashCode() {
603        return 31*super.hashCode() + Objects.hash(_TM, _child);
604    }
605
606    /**
607     * Returns a copy of this VectorTrans instance
608     * {@link javolution.context.AllocatorContext allocated} by the calling thread
609     * (possibly on the stack).
610     *
611     * @return an identical and independent copy of this vector.
612     */
613    @Override
614    public VectorTrans<Q> copy() {
615        return copyOf(this);
616    }
617
618    /**
619     * Holds the default XML representation for this object.
620     */
621    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
622    protected static final XMLFormat<VectorTrans> XML = new XMLFormat<VectorTrans>(VectorTrans.class) {
623
624        @Override
625        public VectorTrans newInstance(Class<VectorTrans> cls, InputElement xml) throws XMLStreamException {
626            return FACTORY.object();
627        }
628
629        @Override
630        public void read(InputElement xml, VectorTrans obj) throws XMLStreamException {
631            GeomVector.XML.read(xml, obj);     // Call parent read.
632
633            obj._TM = xml.getNext();
634            obj._TM_RotScale = GTransform.valueOf(obj._TM.getRotationScale());
635            GeomVector child = xml.getNext();
636            obj._child = child;
637
638            //  Listen for changes to the child object and pass them on.
639            if (!(child instanceof Immutable))
640                child.addChangeListener(obj._childChangeListener);
641        }
642
643        @Override
644        public void write(VectorTrans obj, OutputElement xml) throws XMLStreamException {
645            GeomVector.XML.write(obj, xml);    // Call parent write.
646
647            xml.add(obj._TM);
648            xml.add(obj._child);
649
650        }
651    };
652
653    ///////////////////////
654    // Factory creation. //
655    ///////////////////////
656    private VectorTrans() { }
657
658    @SuppressWarnings("unchecked")
659    private static final ObjectFactory<VectorTrans> FACTORY = new ObjectFactory<VectorTrans>() {
660        @Override
661        protected VectorTrans create() {
662            return new VectorTrans();
663        }
664
665        @Override
666        protected void cleanup(VectorTrans obj) {
667            obj.reset();
668            if (!(obj._child instanceof Immutable))
669                obj._child.removeChangeListener(obj._childChangeListener);
670        }
671    };
672
673    @SuppressWarnings("unchecked")
674    private static VectorTrans copyOf(VectorTrans original) {
675        VectorTrans obj = FACTORY.object();
676        obj._TM = original._TM.copy();
677        obj._TM_RotScale = original._TM_RotScale.copy();
678        obj._child = original._child.copy();
679        original.copyState(obj);
680        if (!(obj._child instanceof Immutable))
681            obj._child.addChangeListener(obj._childChangeListener);
682        return obj;
683    }
684
685}