001/**
002 * GeomPlaneTrans -- A GeomTransform that has an GeomPlane for a child.
003 *
004 * Copyright (C) 2009-2016, 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 java.text.MessageFormat;
022import java.util.Objects;
023import static java.util.Objects.requireNonNull;
024import javax.measure.converter.ConversionException;
025import javax.measure.quantity.Dimensionless;
026import javax.measure.quantity.Length;
027import javax.measure.unit.Unit;
028import javax.swing.event.ChangeListener;
029import javolution.context.ObjectFactory;
030import javolution.xml.XMLFormat;
031import javolution.xml.stream.XMLStreamException;
032
033/**
034 * A {@link GeomTransform} element that refers to a {@link GeomPlane} object and
035 * masquerades as a GeomPlane object itself.
036 *
037 * <p> Modified by: Joseph A. Huwaldt </p>
038 *
039 * @author Joseph A. Huwaldt, Date: June 14, 2009
040 * @version September 13, 2016
041 */
042@SuppressWarnings({"serial", "CloneableImplementsClone"})
043public final class GeomPlaneTrans extends GeomPlane implements GeomTransform<GeomPlane> {
044
045    /**
046     * The transformation represented by this transformation element.
047     */
048    private GTransform _TM;
049
050    /**
051     * The object that is the child of this transform.
052     */
053    private GeomPlane _child;
054
055    /**
056     * Reference to a change listener for this object's child.
057     */
058    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
059
060    /**
061     * Returns a 3D {@link GeomPlaneTrans} instance holding the specified
062     * {@link GeomPlane} and {@link GTransform}.
063     *
064     * @param child     The plane that is the child of this transform element (may not be
065     *                  <code>null</code>).
066     * @param transform The transform held by this transform element (may not be
067     *                  <code>null</code>).
068     * @return the transform element having the specified values.
069     * @throws DimensionException if the input element is not 3D.
070     */
071    public static GeomPlaneTrans newInstance(GeomPlane child, GTransform transform) {
072        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
073        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
074
075        if (child.getPhyDimension() != 3)
076            throw new DimensionException(
077                    MessageFormat.format(RESOURCES.getString("dimensionNot3"), "plane", child.getPhyDimension()));
078
079        GeomPlaneTrans obj = FACTORY.object();
080        obj._TM = transform;
081        obj._child = child;
082
083        //  Listen for changes to the child object and pass them on.
084        child.addChangeListener(obj._childChangeListener);
085
086        return obj;
087    }
088
089    /**
090     * Returns the transformation represented by this transformation element.
091     *
092     * @return The transformation represented by this transformation element.
093     */
094    @Override
095    public GTransform getTransform() {
096        return _TM;
097    }
098
099    /**
100     * Returns the total transformation represented by an entire chain of GeomTransform
101     * objects below this one.
102     *
103     * @return The total transformation represented by an entire chain of GeomTransform
104     * objects below this one.
105     */
106    @Override
107    public GTransform getTotalTransform() {
108        return GeomUtil.getTotalTransform(this);
109    }
110
111    /**
112     * Sets the transformation represented by this transformation element.
113     *
114     * @param transform The transform to set this transform element to (may not be
115     *                  <code>null</code>).
116     */
117    @Override
118    public void setTransform(GTransform transform) {
119        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
120        _TM = transform;
121        fireChangeEvent();
122    }
123
124    /**
125     * Returns the child object transformed by this transform element.
126     *
127     * @return The child object transformed by this transform element.
128     */
129    @Override
130    public GeomPlane getChild() {
131        return _child;
132    }
133
134    /**
135     * Return a copy of the child object transformed by this transformation.
136     *
137     * @return A copy of the child object transformed by this transformation.
138     */
139    @Override
140    public Plane copyToReal() {
141        GeomVector n = _child.getNormal();
142        VectorTrans nt = n.getTransformed(_TM);
143        n = nt.copyToReal();
144
145        Point refPoint = _TM.transform(_child.getRefPoint()).copyToReal();
146        Plane P = Plane.valueOf(n, refPoint);
147
148        _child.copyState(P);
149
150        return P;
151    }
152
153    /**
154     * Return an immutable version of this plane.
155     *
156     * @return An immutable version of this plane.
157     */
158    @Override
159    public Plane immutable() {
160        return copyToReal();
161    }
162
163    /**
164     * Recycles a <code>GeomPlaneTrans</code> instance immediately (on the stack when
165     * executing in a <code>StackContext</code>).
166     *
167     * @param instance The instance to be recycled.
168     */
169    public static void recycle(GeomPlaneTrans instance) {
170        requireNonNull(instance, MessageFormat.format(RESOURCES.getString("paramNullErr"), "instance"));
171        FACTORY.recycle(instance);
172    }
173
174    /**
175     * Returns the number of physical dimensions of the geometry element. This
176     * implementation always returns 3.
177     */
178    @Override
179    public int getPhyDimension() {
180        return 3;
181    }
182
183    /**
184     * Return the normal vector for the plane. The normal vector is a unit vector that is
185     * perpendicular to the plane.
186     *
187     * @return The normal vector for the plane.
188     */
189    @Override
190    public GeomVector<Dimensionless> getNormal() {
191        return _child.getNormal().getTransformed(_TM);
192    }
193
194    /**
195     * Return the constant term of the plane equation (e.g.: "D" for a 3D plane:
196     * <code>A*x + B*y + C*z = D</code>).
197     *
198     * @return The constant term of the plane equation for this plane.
199     */
200    @Override
201    public Parameter<Length> getConstant() {
202        GeomPointTrans pt = getRefPoint();
203        Vector<Length> v = Vector.valueOf(pt);
204        GeomVector<Dimensionless> n = getNormal();
205
206        Parameter<Length> c = (Parameter<Length>)n.dot(v);
207
208        return c;
209    }
210
211    /**
212     * Return the reference point for this plane. The reference point is an arbitrary
213     * point that is contained in the plane and is used as a reference when drawing the
214     * plane.
215     *
216     * @return The reference point for this plane.
217     */
218    @Override
219    public GeomPointTrans getRefPoint() {
220        return _child.getRefPoint().getTransformed(_TM);
221    }
222
223    /**
224     * Return the equivalent of this plane converted to the specified number of physical
225     * dimensions. This implementation will throw an exception if the specified dimension
226     * is anything other than 3.
227     *
228     * @param newDim The dimension of the plane to return. MUST equal 3.
229     * @return The equivalent of this plane converted to the new dimensions.
230     * @throws IllegalArgumentException if the new dimension is anything other than 3.
231     */
232    @Override
233    public GeomPlaneTrans toDimension(int newDim) {
234        if (newDim == 3)
235            return this;
236
237        throw new IllegalArgumentException(
238                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
239    }
240
241    /**
242     * Returns the unit in which the geometry in this element are stated.
243     */
244    @Override
245    public Unit<Length> getUnit() {
246        return _child.getUnit();
247    }
248
249    /**
250     * Returns the equivalent to this element but stated in the specified unit.
251     * <p>
252     * WARNING: If the unit changes, then the returned transform element DOES NOT refer
253     * back to the original plane (the link with the original plane is broken).
254     * </p>
255     *
256     * @param unit the length unit of the element to be returned. May not be null.
257     * @return an equivalent to this element but stated in the specified unit.
258     * @throws ConversionException if the the input unit is not a length unit.
259     */
260    @Override
261    public GeomPlane to(Unit<Length> unit) throws ConversionException {
262        if (unit.equals(getUnit()))
263            return this;
264        GeomPlaneTrans newPlane = GeomPlaneTrans.newInstance(_child.to(unit), _TM);
265        return newPlane;
266    }
267
268    /**
269     * Compares this GeomPlaneTrans against the specified object for strict equality (same
270     * values and same units).
271     *
272     * @param obj the object to compare with.
273     * @return <code>true</code> if this point is identical to that point;
274     *         <code>false</code> otherwise.
275     */
276    @Override
277    public boolean equals(Object obj) {
278        if (this == obj)
279            return true;
280        if ((obj == null) || (obj.getClass() != this.getClass()))
281            return false;
282
283        GeomPlaneTrans that = (GeomPlaneTrans)obj;
284        return this._TM.equals(that._TM)
285                && this._child.equals(that._child)
286                && super.equals(obj);
287    }
288
289    /**
290     * Returns the hash code for this object.
291     *
292     * @return the hash code value.
293     */
294    @Override
295    public int hashCode() {
296        return 31*super.hashCode() + Objects.hash(_TM, _child);
297    }
298
299    /**
300     * Returns a copy of this GeomPlaneTrans instance
301     * {@link javolution.context.AllocatorContext allocated} by the calling thread
302     * (possibly on the stack).
303     *
304     * @return an identical and independent copy of this object.
305     */
306    @Override
307    public GeomPlaneTrans copy() {
308        return copyOf(this);
309    }
310
311    /**
312     * Holds the default XML representation for this object.
313     */
314    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
315    protected static final XMLFormat<GeomPlaneTrans> XML = new XMLFormat<GeomPlaneTrans>(GeomPlaneTrans.class) {
316
317        @Override
318        public GeomPlaneTrans newInstance(Class<GeomPlaneTrans> cls, InputElement xml) throws XMLStreamException {
319            return FACTORY.object();
320        }
321
322        @Override
323        public void read(InputElement xml, GeomPlaneTrans obj) throws XMLStreamException {
324            GeomPlane.XML.read(xml, obj);     // Call parent read.
325
326            obj._TM = xml.getNext();
327            GeomPlane child = xml.getNext();
328            obj._child = child;
329
330            //  Listen for changes to the child object and pass them on.
331            child.addChangeListener(obj._childChangeListener);
332        }
333
334        @Override
335        public void write(GeomPlaneTrans obj, OutputElement xml) throws XMLStreamException {
336            GeomPlane.XML.write(obj, xml);    // Call parent write.
337
338            xml.add(obj._TM);
339            xml.add(obj._child);
340
341        }
342    };
343
344    ///////////////////////
345    // Factory creation. //
346    ///////////////////////
347    private GeomPlaneTrans() { }
348
349    @SuppressWarnings("unchecked")
350    private static final ObjectFactory<GeomPlaneTrans> FACTORY = new ObjectFactory<GeomPlaneTrans>() {
351        @Override
352        protected GeomPlaneTrans create() {
353            return new GeomPlaneTrans();
354        }
355
356        @Override
357        protected void cleanup(GeomPlaneTrans obj) {
358            obj.reset();
359            obj._TM = null;
360            obj._child.removeChangeListener(obj._childChangeListener);
361            obj._child = null;
362        }
363    };
364
365    @SuppressWarnings("unchecked")
366    private static GeomPlaneTrans copyOf(GeomPlaneTrans original) {
367        GeomPlaneTrans obj = FACTORY.object();
368        obj._TM = original._TM.copy();
369        obj._child = original._child.copy();
370        original.copyState(obj);
371        obj._child.addChangeListener(obj._childChangeListener);
372        return obj;
373    }
374
375}