001/*
002 *   Triangle  -- A concrete triangle in nD space.
003 *
004 *   Copyright (C) 2015, Joseph A. Huwaldt
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom;
023
024import static geomss.geom.AbstractGeomElement.RESOURCES;
025import jahuwaldt.js.param.Parameter;
026import java.text.MessageFormat;
027import java.util.List;
028import java.util.Objects;
029import static java.util.Objects.requireNonNull;
030import javax.measure.converter.ConversionException;
031import javax.measure.quantity.Area;
032import javax.measure.quantity.Dimensionless;
033import javax.measure.quantity.Length;
034import javax.measure.unit.Unit;
035import javolution.context.ObjectFactory;
036import javolution.context.StackContext;
037import javolution.lang.ValueType;
038import javolution.xml.XMLFormat;
039import javolution.xml.stream.XMLStreamException;
040
041/**
042 * A concrete triangle in n-dimensional space. A triangle is represented by
043 * exactly three vertex points arranged in a counter-clockwise direction (or
044 * winding) when viewed from the "outside" (the direction the normal vector 
045 * points).
046 *
047 * <p> Modified by: Joseph A. Huwaldt   </p>
048 *
049 * @author Joseph A. Huwaldt, Date: August 26, 2015
050 * @version November 28, 2015
051 */
052@SuppressWarnings({"serial", "CloneableImplementsClone"})
053public final class Triangle extends GeomTriangle implements ValueType {
054
055    /**
056     * The corners of the triangle.
057     */
058    private Point _p1, _p2, _p3;
059    
060    /**
061     * The surface area of this triangle.
062     */
063    private Parameter<Area> _area;
064    
065    /**
066     * The surface normal vector for this triangle.
067     */
068    private Vector<Dimensionless> _n;
069    
070
071    /**
072     * Returns a <code>Triangle</code> instance with the specified corner
073     * vertices in counter-clockwise order/winding when looking down the surface normal
074     * vector. The units of the triangle will be the units of the start point.
075     *
076     * @param p1 The start (beginning) of the triangle. May not be null.
077     * @param p2 The second point in the triangle. May not be null.
078     * @param p3 The third and last point in the triangle. May not be null.
079     * @return A <code>Triangle</code> instance defined by the input points.
080     */
081    public static Triangle valueOf(GeomPoint p1, GeomPoint p2, GeomPoint p3) {
082
083        //  Check the dimensionality.
084        int numDims = GeomUtil.maxPhyDimension(p1,p2,p3);
085        if (numDims < 2)
086            throw new DimensionException(MessageFormat.format(
087                    RESOURCES.getString("dimensionNotAtLeast2"), "triangle",numDims) );
088        Unit<Length> unit = p1.getUnit();
089
090        Triangle T = newInstance();
091        T._p1 = p1.immutable().toDimension(numDims);
092        T._p2 = p2.immutable().to(unit).toDimension(numDims);
093        T._p3 = p3.immutable().to(unit).toDimension(numDims);
094        
095        computeTriData(T);
096        
097        return T;
098    }
099
100    /**
101     * Returns a <code>Triangle</code> instance with the specified corner
102     * vertices in counter-clockwise order/winding when looking down the surface normal
103     * vector. The units of the triangle will be the units of the start point.
104     *
105     * @param points A list of 3 points that form the triangle. May not be null.
106     * @return A <code>Triangle</code> instance defined by the input points.
107     */
108    public static Triangle valueOf(List<? extends GeomPoint> points) {
109        if (points.size() < 3)
110            throw new IllegalArgumentException(RESOURCES.getString("triThreePointsOnly"));
111        
112        return valueOf(points.get(0), points.get(1), points.get(2));
113    }
114    
115    /**
116     * Returns a <code>Triangle</code> instance with the specified corner
117     * vertices in counter-clockwise order/winding when looking down the surface normal
118     * vector. The units of the triangle will be the units of the start point.
119     *
120     * @param points An array of 3 points that form the triangle. May not be null.
121     * @return A <code>Triangle</code> instance defined by the input points.
122     */
123    public static Triangle valueOf(GeomPoint[] points) {
124        if (points.length < 3)
125            throw new IllegalArgumentException(RESOURCES.getString("triThreePointsOnly"));
126        
127        return valueOf(points[0], points[1], points[2]);
128    }
129    
130    /**
131     * Compute some values for a triangle defined by 3 points. It is assumed that the
132     * points have been set for the Triangle "T" before calling this method.
133     */
134    private static void computeTriData(Triangle T) {
135        Point p1 = T._p1;
136        Point p2 = T._p2;
137        Point p3 = T._p3;
138        
139        StackContext.enter();
140        try {
141            //  Compute the triangle surface area and normal vector.
142            Vector<Length> v13 = p3.minus(p1).toGeomVector();
143            Vector<Length> v12 = p2.minus(p1).toGeomVector();
144            Vector n;
145            Parameter<Area> area;
146            int numDims = T.getPhyDimension();
147            if (numDims > 2) {
148                //  area = |v12 X v13|/2 = |n|/2
149                n = v12.cross(v13);
150                area = n.norm().times(0.5);
151            } else {
152                //  Triangle area = (v1.X*v2.Y - v1.Y*v2.X)/2
153                area = GeomUtil.crossArea(v12, v13).times(0.5);
154                Parameter<Area> ZERO = Parameter.ZERO_AREA.to(area.getUnit());
155                n = Vector.valueOf(ZERO, ZERO, area);
156                area = area.abs();
157            }
158
159            if (area.isApproxZero())
160                n = Vector.newInstance(p1.getPhyDimension());
161            n = n.toUnitVector();
162            
163            //  Calculate the mean vertex location and set normal vector to that location.
164            Point mp = p1.plus(p2).plus(p3).divide(3);
165            n.setOrigin(mp);
166            
167            //  Copy out the results.
168            T._area = StackContext.outerCopy(area);
169            T._n = StackContext.outerCopy(n);
170            
171        } finally {
172            StackContext.exit();
173        }
174    }
175    
176    /**
177     * Recycles a <code>Triangle</code> instance immediately (on the stack when
178     * executing in a <code>StackContext</code>).
179     *
180     * @param instance The instance to recycle immediately.
181     */
182    public static void recycle(Triangle instance) {
183        FACTORY.recycle(instance);
184    }
185
186    /**
187     * Return the first vertex in this triangle.
188     *
189     * @return The first vertex in this triangle.
190     */
191    @Override
192    public Point getP1() {
193        return _p1;
194    }
195
196    /**
197     * Return the second vertex in this triangle.
198     *
199     * @return The second vertex in this triangle.
200     */
201    @Override
202    public Point getP2() {
203        return _p2;
204    }
205
206    /**
207     * Return the third and last vertex in this triangle.
208     *
209     * @return The third and last vertex in this triangle.
210     */
211    @Override
212    public Point getP3() {
213        return _p3;
214    }
215
216    /**
217     * Return the surface unit normal vector for this triangle.
218     * If the triangle is degenerate (zero area), then the
219     * normal vector will have zero length.
220     * 
221     * @return The surface normal vector for this triangle.
222     */
223    @Override
224    public Vector<Dimensionless> getNormal() {
225        return _n.copy();
226    }
227    
228    /**
229     * Return the surface area of one side of this triangle.
230     * The returned area is always positive, but can be zero.
231     * 
232     * @return The surface area of one side of this triangle.
233     */
234    @Override
235    public Parameter<Area> getArea() {
236        return _area;
237    }
238    
239    /**
240     * Return a new triangle that is identical to this one, but with the order
241     * of the points (and the surface normal direction) reversed.
242     *
243     * @return A new Triangle that is identical to this one, but with the order
244     * of the points reversed.
245     */
246    @Override
247    public Triangle reverse() {
248
249        //  Create the reversed curve.
250        Triangle T = Triangle.valueOf(_p3, _p2, _p1);
251
252        return copyState(T);    //  Copy over the super-class state for this object to the new one.
253    }
254
255    /**
256     * Return the coordinate point representing the minimum bounding box corner
257     * of this geometry element (e.g.: min X, min Y, min Z).
258     *
259     * @return The minimum bounding box coordinate for this geometry element.
260     */
261    @Override
262    public Point getBoundsMin() {
263        return _p1.min(_p2.min(_p3));
264    }
265
266    /**
267     * Return the coordinate point representing the maximum bounding box corner
268     * (e.g.: max X, max Y, max Z).
269     *
270     * @return The maximum bounding box coordinate for this geometry element.
271     */
272    @Override
273    public Point getBoundsMax() {
274        return _p1.max(_p2.max(_p3));
275    }
276
277    /**
278     * Returns transformed version of this element. The returned object
279     * implements {@link geomss.geom.GeomTransform} and contains this element as
280     * a child.
281     *
282     * @param transform The transformation to apply to this geometry. May not be null.
283     * @return A new triangle that is identical to this one with the
284     * specified transformation applied.
285     * @throws DimensionException if this point is not 3D.
286     */
287    @Override
288    public TriangleTrans getTransformed(GTransform transform) {
289        return TriangleTrans.newInstance(this, requireNonNull(transform));
290    }
291    /**
292     * Return a copy of this Triangle converted to the specified number of
293     * physical dimensions. If the number of dimensions is greater than this
294     * element, then zeros are added to the additional dimensions. If the number
295     * of dimensions is less than this element, then the extra dimensions are
296     * simply dropped (truncated). If the new dimensions are the same as the
297     * dimension of this element, then this element is simply returned.
298     *
299     * @param newDim The dimension of the Triangle to return.
300     * @return This Triangle converted to the new dimensions.
301     */
302    @Override
303    public Triangle toDimension(int newDim) {
304        if (getPhyDimension() == newDim)
305            return this;
306
307        Triangle T = Triangle.valueOf(_p1.toDimension(newDim), _p2.toDimension(newDim), _p3.toDimension(newDim));
308
309        return copyState(T);    //  Copy over the super-class state for this object to the new one.
310    }
311
312    /**
313     * Returns the equivalent to this element but stated in the specified unit.
314     *
315     * @param unit the length unit of the element to be returned. May not be null.
316     * @return an equivalent to this element but stated in the specified unit.
317     * @throws ConversionException if the the input unit is not a length unit.
318     */
319    @Override
320    public Triangle to(Unit<Length> unit) throws ConversionException {
321        if (unit.equals(getUnit()))
322            return this;
323        Triangle T = Triangle.valueOf(_p1.to(unit), _p2.to(unit), _p3.to(unit));
324        return copyState(T);    //  Copy over the super-class state for this object to the new one.
325    }
326
327    /**
328     * Returns a copy of this Triangle instance allocated by the calling
329     * thread (possibly on the stack).
330     *
331     * @return an identical and independent copy of this Triangle.
332     */
333    @Override
334    public Triangle copy() {
335        return copyOf(this);
336    }
337
338    /**
339     * Return a copy of this object with any transformations or subranges
340     * removed (applied).
341     *
342     * @return A copy of this object with any transformations or subranges
343     *      removed (applied).
344     */
345    @Override
346    public Triangle copyToReal() {
347        return copy();
348    }
349
350    /**
351     * Compares this Triangle against the specified object for strict equality.
352     *
353     * @param obj the object to compare with.
354     * @return <code>true</code> if this Triangle is identical to that
355     * Triangle; <code>false</code> otherwise.
356     */
357    @Override
358    public boolean equals(Object obj) {
359        if (this == obj)
360            return true;
361        if ((obj == null) || (obj.getClass() != this.getClass()))
362            return false;
363
364        Triangle that = (Triangle)obj;
365        return this._p1.equals(that._p1)
366                && this._p2.equals(that._p2)
367                && this._p3.equals(that._p3)
368                && super.equals(obj);
369    }
370
371    /**
372     * Returns the hash code for this parameter.
373     *
374     * @return the hash code value.
375     */
376    @Override
377    public int hashCode() {
378        return 31*super.hashCode() + Objects.hash(_p1, _p2, _p3);
379    }
380
381    /**
382     * Holds the default XML representation for this object.
383     */
384    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
385    protected static final XMLFormat<Triangle> XML = new XMLFormat<Triangle>(Triangle.class) {
386
387        @Override
388        public Triangle newInstance(Class<Triangle> cls, InputElement xml) throws XMLStreamException {
389            return FACTORY.object();
390        }
391
392        @Override
393        public void read(InputElement xml, Triangle obj) throws XMLStreamException {
394            GeomTriangle.XML.read(xml, obj);     // Call parent read.
395
396            obj._p1 = xml.getNext();
397            obj._p2 = xml.getNext();
398            obj._p3 = xml.getNext();
399
400            computeTriData(obj);
401        }
402
403        @Override
404        public void write(Triangle obj, OutputElement xml) throws XMLStreamException {
405            GeomTriangle.XML.write(obj, xml);    // Call parent write.
406
407            xml.add(obj._p1);
408            xml.add(obj._p2);
409            xml.add(obj._p3);
410
411        }
412    };
413
414    ///////////////////////
415    // Factory creation. //
416    ///////////////////////
417    private Triangle() {
418    }
419
420    private static final ObjectFactory<Triangle> FACTORY = new ObjectFactory<Triangle>() {
421        @Override
422        protected Triangle create() {
423            return new Triangle();
424        }
425
426        @Override
427        protected void cleanup(Triangle obj) {
428            obj.reset();
429            obj._p1 = null;
430            obj._p2 = null;
431            obj._p3 = null;
432        }
433    };
434
435    private static Triangle copyOf(Triangle original) {
436        Triangle o = FACTORY.object();
437        o._p1 = original._p1.copy();
438        o._p2 = original._p2.copy();
439        o._p3 = original._p3.copy();
440        computeTriData(o);
441        original.copyState(o);
442        return o;
443    }
444
445    private static Triangle newInstance() {
446        return FACTORY.object();
447    }
448
449}