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