001/**
002 * TriangleList -- A concrete list of GeomTriangle objects.
003 *
004 * Copyright (C) 2015-2017, by 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 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 Library 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 static geomss.geom.AbstractGeomElement.RESOURCES;
021import jahuwaldt.js.param.Parameter;
022import java.text.MessageFormat;
023import java.util.Collection;
024import java.util.List;
025import static java.util.Objects.nonNull;
026import static java.util.Objects.requireNonNull;
027import javax.measure.converter.ConversionException;
028import javax.measure.quantity.Area;
029import javax.measure.quantity.Dimensionless;
030import javax.measure.quantity.Length;
031import javax.measure.unit.Unit;
032import javolution.context.ObjectFactory;
033import javolution.context.StackContext;
034import javolution.util.FastMap;
035import javolution.util.FastTable;
036import javolution.xml.XMLFormat;
037import javolution.xml.stream.XMLStreamException;
038import org.jscience.mathematics.number.Integer64;
039
040/**
041 * A concrete list of {@link GeomTriangle} objects.  This list is similar
042 * to a GeomList, but has methods specifically designed for working with large
043 * sets of triangles. All the triangles in a list must have the same physical dimensions.
044 * <p>
045 * WARNING: This list allows geometry to be stored in different units. If consistent units
046 * are required, then the user must specifically convert the list items.
047 * </p>
048 *
049 * <p> Modified by: Joseph A. Huwaldt   </p>
050 *
051 * @author Joseph A. Huwaldt, Date: August 28, 2015
052 * @version January 31, 2017
053 *
054 * @param <E> The type of triangle stored in this list.
055 */
056@SuppressWarnings({"serial", "CloneableImplementsClone"})
057public final class TriangleList<E extends GeomTriangle> extends AbstractGeomList<TriangleList,E> {
058
059    private FastTable<E> _list;
060
061    /**
062     * Return the list underlying this geometry list.
063     *
064     * @return The list underlying this geometry list.
065     */
066    @Override
067    protected FastTable<E> getList() {
068        return _list;
069    }
070
071    /**
072     * Returns a new, empty, preallocated or recycled <code>TriangleList</code>
073     * instance (on the stack when executing in a <code>StackContext</code>)
074     * that can store a list of {@link GeomTriangle} objects.
075     *
076     * @return A new, empty TriangleList.
077     */
078    public static TriangleList newInstance() {
079        TriangleList list = FACTORY.object();
080        list._list = FastTable.newInstance();
081        return list;
082    }
083
084    /**
085     * Returns a new, empty, preallocated or recycled <code>TriangleList</code>
086     * instance (on the stack when executing in a <code>StackContext</code>)
087     * with the specified name, that can store a list of {@link GeomTriangle}
088     * objects.
089     *
090     * @param name The name to be assigned to this list (may be <code>null</code>).
091     * @return A new, empty TriangleList with the specified name.
092     */
093    public static TriangleList newInstance(String name) {
094        TriangleList list = TriangleList.newInstance();
095        list.setName(name);
096        return list;
097    }
098
099    /**
100     * Return a TriangleList containing the {@link GeomTriangle} objects in the
101     * specified collection.
102     *
103     * @param <E> The type of triangle contained in the returned list of triangles.
104     * @param name The name to be assigned to this list (may be
105     *  <code>null</code>).
106     * @param elements A collection of triangle elements. May not be null.
107     * @return A new TriangleList containing the elements in the specified collection.
108     */
109    public static <E extends GeomTriangle> TriangleList<E> valueOf(String name, Collection<E> elements) {
110        for (Object element : elements) {
111            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
112            if (!(element instanceof GeomTriangle))
113            throw new ClassCastException(MessageFormat.format(
114                    RESOURCES.getString("listElementTypeErr"), "TriangleList", "GeomTriangle"));
115        }
116
117        TriangleList<E> list = TriangleList.newInstance(name);
118        list.addAll(elements);
119
120        return list;
121    }
122
123    /**
124     * Return a TriangleList containing the {@link GeomTriangle} objects in the
125     * specified list.
126     *
127     * @param <E> The type of triangle contained in the returned list of triangles.
128     * @param name The name to be assigned to this list (may be
129     *  <code>null</code>).
130     * @param elements A list of triangle elements. May not be null.
131     * @return A new TriangleList containing the elements in the specified list.
132     */
133    public static <E extends GeomTriangle> TriangleList<E> valueOf(String name, E... elements) {
134        requireNonNull(elements);
135        TriangleList<E> list = TriangleList.newInstance(name);
136        list.addAll(elements);
137
138        return list;
139    }
140
141    /**
142     * Return a TriangleList containing the {@link GeomTriangle} objects in the
143     * specified array.
144     *
145     * @param <E> The type of triangle contained in the returned list of triangles.
146     * @param elements An array of triangle elements. May not be null.
147     * @return A new TriangleList containing the elements in the specified array.
148     */
149    public static <E extends GeomTriangle> TriangleList<E> valueOf(E... elements) {
150        return TriangleList.valueOf(null, elements);
151    }
152
153    /**
154     * Return a TriangleList containing the triangles
155     *
156     * @param vertices A list of all the vertices in the list of triangles. May not be null.
157     * @param indexes An array of indexes into the vertices list.  Each set of 3 consecutive
158     * indexes into the vertices list defines a triangle. May not be null.
159     * @return A new TriangleList containing the elements in the specified collection.
160     */
161    public static TriangleList<Triangle> valueOf(List<? extends GeomPoint> vertices, int[] indexes) {
162        requireNonNull(vertices);
163        requireNonNull(indexes);
164
165        TriangleList<Triangle> list = TriangleList.newInstance();
166        int numTris = indexes.length/3;
167        int pos = 0;
168        for (int i=0; i < numTris; ++i) {
169            GeomPoint p1 = vertices.get(indexes[pos++]);
170            GeomPoint p2 = vertices.get(indexes[pos++]);
171            GeomPoint p3 = vertices.get(indexes[pos++]);
172            Triangle tri = Triangle.valueOf(p1, p2, p3);
173            list.add(tri);
174        }
175
176        return list;
177    }
178
179    /**
180     * Return a TriangleList containing a simple triangulation of the input
181     * array of quadrilateral panels. The input quads are triangulated by
182     * dividing each quad into two triangles. Any subranges or transforms on the
183     * input points is lost by this triangulation.
184     *
185     * @param quads An array of quadrilateral panels (defined by a topologically
186     * rectangular array of points). May not be null.
187     * @return A new TriangleList containing a triangulation of the input quads.
188     */
189    public static TriangleList<Triangle> triangulateQuads(PointArray<? extends GeomPoint> quads) {
190        requireNonNull(quads);
191
192        //  Create an empty triangle list.
193        TriangleList<Triangle> list = TriangleList.newInstance();
194
195        //  Loop over all the panels in the array and turn each into two triangles.
196        int numRows = quads.size();
197        int numCols = quads.get(0).size();
198
199        //  Loop over the strings.
200        for (int row = 1; row < numRows; ++row) {
201            PointString<?> str1 = quads.get(row - 1);
202            PointString<?> str2 = quads.get(row);
203
204            //  Loop over points in strings.
205            for (int col = 1; col < numCols; ++col) {
206                // Get quad points.
207                Point p1 = str1.get(col - 1).immutable();
208                Point p2 = str1.get(col).immutable();
209                Point p3 = str2.get(col).immutable();
210                Point p4 = str2.get(col - 1).immutable();
211
212                //  Form the 1st triangle: p1,p2,p4
213                Triangle tri = Triangle.valueOf(p1,p2,p4);
214                list.add(tri);
215
216                //  Form the 2nd triangle: p4,p2,p3
217                tri = Triangle.valueOf(p4,p2,p3);
218                list.add(tri);
219            }
220        }
221
222        return list;
223    }
224
225    /**
226     * Returns the number of physical dimensions of this geometry element. This
227     * implementation always returns the physical dimension of the underlying
228     * {@link Triangle} objects or 0 if this list has no Triangle objects in it.
229     *
230     * @return The number of physical dimensions of this geometry element.
231     */
232    @Override
233    public int getPhyDimension() {
234        if (isEmpty())
235            return 0;
236        return get(0).getPhyDimension();
237    }
238
239    /**
240     * Returns the range of elements in this list from the specified start and
241     * ending indexes.
242     *
243     * @param first index of the first element to return (0 returns the 1st
244     *  element, -1 returns the last, etc).
245     * @param last index of the last element to return (0 returns the 1st
246     *  element, -1 returns the last, etc).
247     * @return the list of elements in the given range from this list.
248     * @throws IndexOutOfBoundsException if the given index is out of range:
249     *  <code>index >= size()</code>
250     */
251    @Override
252    public TriangleList<E> getRange(int first, int last) {
253        first = normalizeIndex(first);
254        last = normalizeIndex(last);
255
256        TriangleList list = TriangleList.newInstance();
257        for (int i=first; i <= last; ++i)
258            list.add(get(i));
259        return list;
260    }
261
262    /**
263     * Replaces the {@link GeomTriangle} at the specified position in this list with the
264     * specified element. Null elements are ignored. The input element must have the same
265     * physical dimensions as the other items in this list, or an exception is thrown.
266     *
267     * @param index   The index of the element to replace (0 returns the 1st element, -1
268     *                returns the last, -2 returns the 2nd from last, etc).
269     * @param element The element to be stored at the specified position.
270     *                <code>null</code> elements are ignored.
271     * @return The element previously at the specified position in this list. May not be
272     *         null.
273     * @throws java.lang.IndexOutOfBoundsException - if <code>index > size()</code>
274     * @throws DimensionException if the input element's dimensions are different from
275     * this list's dimensions.
276     */
277    @Override
278    public E set(int index, E element) {
279        requireNonNull(element);
280        if (size() > 0 && element.getPhyDimension() != this.getPhyDimension())
281            throw new DimensionException(RESOURCES.getString("dimensionErr"));
282        return super.set(index, element);
283    }
284
285    /**
286     * Inserts the specified {@link GeomTriangle} at the specified position in this list.
287     * Shifts the element currently at that position (if any) and any subsequent elements
288     * to the right (adds one to their indices). Null values are ignored. The input
289     * element must have the same physical dimensions as the other items in this list, or
290     * an exception is thrown.
291     * <p>
292     * Note: If this method is used concurrent access must be synchronized (the list is
293     * not thread-safe).
294     * </p>
295     *
296     * @param index the index at which the specified element is to be inserted. (0 returns
297     *              the 1st element, -1 returns the last, -2 returns the 2nd from last,
298     *              etc).
299     * @param value The element to be inserted. May not be null.
300     * @throws IndexOutOfBoundsException if <code>index > size()</code>
301     * @throws DimensionException if the input element's dimensions are different from
302     * this list's dimensions.
303     */
304    @Override
305    public void add(int index, E value) {
306        requireNonNull(value);
307        if (size() > 0 && value.getPhyDimension() != this.getPhyDimension())
308            throw new DimensionException(RESOURCES.getString("dimensionErr"));
309        super.add(index, value);
310    }
311
312    /**
313     * Inserts all of the {@link GeomTriangle} objects in the specified collection into
314     * this list at the specified position. Shifts the element currently at that position
315     * (if any) and any subsequent elements to the right (increases their indices). The
316     * new elements will appear in this list in the order that they are returned by the
317     * specified collection's iterator. The behavior of this operation is unspecified if
318     * the specified collection is modified while the operation is in progress. Note that
319     * this will occur if the specified collection is this list, and it's nonempty.  The
320     * input elements must have the same physical dimensions as the other items in this
321     * list, or an exception is thrown.
322     *
323     * @param index index at which to insert first element from the specified collection.
324     * @param c     Elements to be inserted into this collection. May not be null.
325     * @return <code>true</code> if this collection changed as a result of the call.
326     * @throws DimensionException if the input element's dimensions are different from
327     * this list's dimensions.
328     */
329    @Override
330    public boolean addAll(int index, Collection<? extends E> c) {
331        int thisSize = this.size();
332        for (Object element : c) {
333            requireNonNull(element, RESOURCES.getString("collectionElementsNullErr"));
334            if (!(element instanceof GeomTriangle))
335                throw new ClassCastException(MessageFormat.format(
336                        RESOURCES.getString("listElementTypeErr"), "TriangleList", "GeomTriangle"));
337            if (thisSize != 0) {
338                if (((GeomElement)element).getPhyDimension() != this.getPhyDimension())
339                    throw new DimensionException(RESOURCES.getString("dimensionErr"));
340            }
341        }
342        return super.addAll(index,c);
343    }
344
345    /**
346     * Returns an new {@link TriangleList} with the elements in this list in reverse
347     * order.
348     *
349     * @return A new TriangleList with the elements in this list in reverse order.
350     */
351    @Override
352    public TriangleList<E> reverse() {
353        TriangleList newList = TriangleList.newInstance();
354        copyState(newList);
355        int size = this.size();
356        for (int i=size-1; i >= 0; --i) {
357            newList.add(get(i));
358        }
359        return newList;
360    }
361
362    /**
363     * Return a new triangle list that is identical to this one, but with any degenerate
364     * triangles removed.
365     *
366     * @param tol The tolerance for determining if a triangle is degenerate. A value of
367     *            <code>null</code> would indicate that exactly zero area is required to
368     *            be degenerate.
369     * @return A new triangle list that is identical to this one, but with any degenerate
370     *         triangles removed.
371     */
372    public TriangleList<E> removeDegenerate(Parameter<Length> tol) {
373        TriangleList<E> newList = TriangleList.newInstance();
374        copyState(newList);
375        int size = size();
376        for (int i=0; i < size; ++i) {
377            E tri = get(i);
378            if (!tri.isDegenerate(tol))
379                newList.add(tri);
380        }
381        return newList;
382    }
383
384    /**
385     * Return the total surface area of this list of triangles. This is the sum of the
386     * area of all the triangles in this list.
387     *
388     * @return The surface area of all the triangles in this list.
389     */
390    public Parameter<Area> getArea() {
391        StackContext.enter();
392        try {
393
394            Parameter<Area> area = Parameter.ZERO_AREA.to(getUnit().pow(2).asType(Area.class));
395            for (E tri : this)
396                area = area.plus(tri.getArea());
397
398            return StackContext.outerCopy(area);
399
400        } finally {
401            StackContext.exit();
402        }
403    }
404
405    /**
406     * Return a copy of this list converted to the specified number of physical
407     * dimensions. If the number of dimensions is greater than this element,
408     * then zeros are added to the additional dimensions. If the number of
409     * dimensions is less than this element, then the extra dimensions are
410     * simply dropped (truncated). If the new dimensions are the same as the
411     * dimension of this element, then this list is simply returned.
412     *
413     * @param newDim The dimension of the element to return.
414     * @return A copy of this list converted to the new dimensions.
415     */
416    @Override
417    public TriangleList<E> toDimension(int newDim) {
418        TriangleList newList = TriangleList.newInstance();
419        copyState(newList);
420        int size = this.size();
421        for (int i=0; i < size; ++i) {
422            E element = this.get(i);
423            newList.add(element.toDimension(newDim));
424        }
425        return newList;
426    }
427
428    /**
429     * Returns the equivalent to this list but with <I>all</I> the elements stated in the
430     * specified unit.
431     *
432     * @param unit the length unit of the list to be returned. May not be null.
433     * @return an equivalent to this list but stated in the specified unit.
434     * @throws ConversionException if the the input unit is not a length unit.
435     */
436    @Override
437    public TriangleList<E> to(Unit<Length> unit) {
438        requireNonNull(unit);
439        TriangleList newList = TriangleList.newInstance();
440        copyState(newList);
441        int size = this.size();
442        for (int i = 0; i < size; ++i) {
443            E element = this.get(i);
444            newList.add(element.to(unit));
445        }
446        return newList;
447    }
448
449    /**
450     * Returns a copy of this <code>TriangleList</code> instance
451     * {@link javolution.context.AllocatorContext allocated} by the calling
452     * thread (possibly on the stack).
453     *
454     * @return an identical and independent copy of this object.
455     */
456    @Override
457    public TriangleList<E> copy() {
458        return copyOf(this);
459    }
460
461    /**
462     * Return a copy of this object with any transformations or subranges
463     * removed (applied).
464     *
465     * @return A copy of this list with any transformations or subranges removed.
466     */
467    @Override
468    public TriangleList<Triangle> copyToReal() {
469        TriangleList newList = TriangleList.newInstance();
470        copyState(newList);
471        int size = this.size();
472        for (int i=0; i < size; ++i) {
473            E element = this.get(i);
474            newList.add(element.copyToReal());
475        }
476        return newList;
477    }
478
479    /**
480     * Returns transformed version of this element. The returned object
481     * implements {@link GeomTransform} and contains transformed versions of the
482     * contents of this list as children. Any list elements that are not
483     * transformable will simply be added to the output list without
484     * transformation.
485     *
486     * @param transform The transform to apply to this geometry element. May not be null.
487     * @return A transformed version of this geometry element.
488     * @throws DimensionException if this element is not 3D.
489     */
490    @Override
491    public TriangleList<TriangleTrans> getTransformed(GTransform transform) {
492        requireNonNull(transform);
493        TriangleList list = TriangleList.newInstance();
494        copyState(list);    //  Copy over the super-class state for this object to the new one.
495        int size = this.size();
496        for (int i=0; i < size; ++i) {
497            E element = this.get(i);
498            if (element instanceof Transformable)
499                list.add(((Transformable)element).getTransformed(transform));
500            else
501                list.add(element);
502        }
503        return list;
504    }
505
506    /**
507     * Convert this list of Triangle objects into a TriangleVertData structure with a
508     * non-repeating (unique) list of vertices, non-degenerate triangles defined by
509     * indices into the list of vertices, areas of all the triangles, and normals for all
510     * the triangles. This is handy for converting between this list of triangles and
511     * various triangulated file formats.
512     *
513     * @param tol The position tolerance used to determine if two vertices are coincident.
514     *            Pass <code>null</code> or 0 if all vertices are to be retained.
515     * @return A TriangleVertData structure with information about the triangles in this
516     *         list.
517     */
518    public TriangleVertData toVertData(Parameter<Length> tol) {
519
520        //  Create lists to contain per-triangle data.
521        FastTable<Parameter<Area>> areas = FastTable.newInstance(); //  The area of each triangle.
522        FastTable<GeomVector<Dimensionless>> normals = FastTable.newInstance(); //  The normal vector of each triangle.
523
524        //  Remove degenerate triangles.
525        TriangleList<E> triLst = this;
526        if (nonNull(tol) && !tol.isApproxZero())
527            triLst = triLst.removeDegenerate(tol);
528
529        //  Create an index into the vertex list for the vertices of each triangle.
530        int numTris = triLst.size();
531        int[] triIndexes = new int[numTris * 3];
532
533        //  Identify all the unique vertices in the list of triangles.
534        PointString<GeomPoint> verts = uniqueVerts(triLst, triIndexes, tol);
535
536        //  Fill in the lists of areas and normal vectors.
537        for (int i = 0; i < numTris; ++i) {
538            E tri = triLst.get(i);
539            areas.add(tri.getArea());
540            normals.add(tri.getNormal());
541        }
542
543        //  Create the output data structure and fill it in.
544        TriangleVertData output = TriangleVertData.newInstance();
545        output.vertices = verts;
546        output.numTris = numTris;
547        output.tris = triIndexes;
548        output.areas = areas;
549        output.normals = normals;
550
551        return output;
552    }
553
554    /**
555     * Return a map of unique vertices for each triangle in this list. Coincident vertices
556     * are determined using the supplied tolerance on vertex position.
557     *
558     * @param triLst     A list of non-degenerate Triangle objects.
559     * @param triIndexes An array of indexes into "verts" for each triangle as consecutive
560     *                   sets of 3 indexes.
561     * @return A new vertex list with a unique set of vertices. The input triIndexes array
562     *         is also modified to use indexes into the output list of vertices.
563     * @param tol        The position tolerance used to determine if two vertices are
564     *                   coincident.
565     */
566    private <E extends GeomTriangle> PointString<GeomPoint> uniqueVerts(TriangleList<E> triLst,
567            int[] triIndexes, Parameter<Length> tol) {
568
569        /*  Algorithm is similar to the one for edges in "admesh": https://github.com/admesh/admesh
570         1. Each vertex is turned into a TriVert object.
571         2. Use a hash map to store the vertices.  The hash code for TriVert is such that
572         any vertex with vertices within "tol" of each other should return the same
573         hash code.
574         3. Repeat for all triangles.  If a vertex with the same hash code already exists
575         in the hash map, then a duplicate vertex has been found.
576         */
577        FastMap<TriVert, TriVert> vertMap = FastMap.newInstance();
578        PointString<GeomPoint> vertLst = PointString.newInstance();
579        GeomPoint[] triVerts = new GeomPoint[3];
580
581        //  Loop over all the triangles in this list.
582        int vert_idx = 0;
583        int numTris = triLst.size();
584        for (int triIdx = 0; triIdx < numTris; ++triIdx) {
585            GeomTriangle tri = triLst.get(triIdx);
586            if (!tri.isDegenerate(tol)) {
587                tri.getPoints(triVerts);
588
589                //  Loop over the three vertices of the triangle.
590                for (int i = 0; i < 3; ++i) {
591                    //  Get the vertex point for this triangle.
592                    GeomPoint vertex = triVerts[i];
593
594                    //  Form a vertex object using this vertex point.
595                    TriVert vThis = TriVert.newInstance(vert_idx, vertex, tol);
596
597                    //  Insert this vertex into the hash map and look for a duplicate.
598                    boolean exists = vertMap.containsKey(vThis);
599                    if (exists) {
600                        //  Found a duplicate vertex; get the original from the map.
601                        TriVert vOther = vertMap.get(vThis);
602
603                        //  Index this triangles vertex to the original one.
604                        triIndexes[triIdx * 3 + i] = vOther.vert_index;
605
606                        //  Recycle the redudant vertex.
607                        TriVert.recycle(vThis);
608
609                    } else {
610                        // If the vertex isn't in the map yet, add it.
611                        vertMap.put(vThis, vThis);
612
613                        //  Add the vertex to the output list as well (as position "vert_idx").
614                        vertLst.add(vertex);
615
616                        //  Index this triangle's vertex to the correct position in the list.
617                        triIndexes[triIdx * 3 + i] = vert_idx;
618
619                        //  Prepare for next vertex.
620                        ++vert_idx;
621                    }
622                }
623            }
624        }
625
626        //  Recycle the TriVert objects now that we are done with them.
627        for (FastMap.Entry<TriVert, TriVert> e = vertMap.head(), end = vertMap.tail(); (e = e.getNext()) != end;) {
628            TriVert v = e.getKey();
629            TriVert.recycle(v);
630        }
631        FastMap.recycle(vertMap);
632
633        return vertLst;
634    }
635
636    /**
637     * Holds the default XML representation for this object.
638     */
639    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
640    protected static final XMLFormat<TriangleList> XML = new XMLFormat<TriangleList>(TriangleList.class) {
641
642        @Override
643        public TriangleList newInstance(Class<TriangleList> cls, XMLFormat.InputElement xml) throws XMLStreamException {
644            TriangleList obj = TriangleList.newInstance();
645            return obj;
646        }
647
648        @Override
649        public void read(XMLFormat.InputElement xml, TriangleList obj) throws XMLStreamException {
650            AbstractGeomList.XML.read(xml, obj);     // Call parent read.
651        }
652
653        @Override
654        public void write(TriangleList obj, XMLFormat.OutputElement xml) throws XMLStreamException {
655            AbstractGeomList.XML.write(obj, xml);    // Call parent write.
656        }
657    };
658
659    //////////////////////
660    // Factory Creation //
661    //////////////////////
662    private static final ObjectFactory<TriangleList> FACTORY = new ObjectFactory<TriangleList>() {
663        @Override
664        protected TriangleList create() {
665            return new TriangleList();
666        }
667
668        @Override
669        protected void cleanup(TriangleList obj) {
670            obj.reset();
671        }
672    };
673
674    /**
675     * Recycles a TriangleList instance immediately (on the stack when executing in a
676     * StackContext).
677     *
678     * @param instance The instance to be recycled.
679     */
680    public static void recycle(TriangleList instance) {
681        FACTORY.recycle(instance);
682    }
683
684    /**
685     * Do not allow the default constructor to be used except by subclasses.
686     */
687    protected TriangleList() {}
688
689    private static TriangleList copyOf(TriangleList<? extends GeomElement> original) {
690        TriangleList o = TriangleList.newInstance();
691        original.copyState(o);
692        int size = original.size();
693        for (int i=0; i < size; ++i) {
694            GeomElement element = original.get(i);
695            o.add(element.copy());
696        }
697        return o;
698    }
699
700    /**
701     * An object that represents a vertex of a triangle. This is used to sort out a unique
702     * set of vertices.
703     */
704    private static class TriVert {
705
706        private static final int ONE = 1 << 16;
707
708        public int vert_index;  //  The index to this vertex in the full vertex list.
709        public int hashCode;    //  The hash code for this vertex.
710        private FastTable<Integer64> vertFP;  //  The vertex coordinates in fixed point.
711
712        /**
713         * Return a new TriVert object using the supplied vertex index, vertex and
714         * tolerance for use in determining if this vertex is coincident with another.
715         *
716         * @param vert_index The index in the full vertex list of this vertex.
717         * @param vert       The actual vertex point represented by this TriVert object.
718         * @param tol        The tolerance for determining if two vertices are coincident.
719         * @return A new TriVert object.
720         */
721        public static TriVert newInstance(int vert_index, GeomPoint vert, Parameter<Length> tol) {
722            TriVert o = FACTORY.object();
723            o.vert_index = vert_index;
724
725            //  Round off the input vertex coordinates and convert them to fixed-point integers.
726            double tolv = 0;
727            if (nonNull(tol) && !tol.isApproxZero())
728                tolv = 2.0 * tol.doubleValue(vert.getUnit());
729
730            o.vertFP = FastTable.newInstance();
731            int numDims = vert.getPhyDimension();
732            for (int i = 0; i < numDims; ++i) {
733                double value = vert.getValue(i);
734                value = approxValue(value, tolv);
735                int fp = toFix(value);
736                o.vertFP.add(Integer64.valueOf(fp));
737            }
738
739            //  Pre-compute the hash code.
740            o.hashCode = o.vertFP.hashCode();
741
742            return o;
743        }
744
745        @Override
746        public boolean equals(Object obj) {
747            if (this == obj)
748                return true;
749            if ((obj == null) || (obj.getClass() != this.getClass()))
750                return false;
751
752            TriVert that = (TriVert)obj;
753            return this.vertFP.equals(that.vertFP);
754        }
755
756        @Override
757        public int hashCode() {
758            return hashCode;
759        }
760
761        //  Convert a double to fixed-point integer.
762        public static int toFix(double val) {
763            return (int)(val * ONE);
764        }
765
766        //  Round a float to the specified tolerance.
767        private static double approxValue(double value, double tolv) {
768            if (tolv > 0.0)
769                //  Round to tolerance (approximately).
770                return tolv * Math.round(value / tolv);
771            return value;
772        }
773
774        public static void recycle(TriVert instance) {
775            FACTORY.recycle(instance);
776        }
777
778        private TriVert() { }
779
780        private static final ObjectFactory<TriVert> FACTORY = new ObjectFactory<TriVert>() {
781            @Override
782            protected TriVert create() {
783                return new TriVert();
784            }
785
786            @Override
787            protected void cleanup(TriVert obj) {
788                FastTable.recycle(obj.vertFP);
789                obj.vertFP = null;
790            }
791        };
792    }
793}