001/**
002 * Copyright (c) 2008-2014 Ardor Labs, Inc.
003 *
004 * This file is part of Ardor3D.
005 *
006 * Ardor3D is free software: you can redistribute it and/or modify it
007 * under the terms of its license which may be found in the accompanying
008 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
009 */
010
011package com.ardor3d.extension.model.util.nvtristrip;
012
013import java.util.ArrayList;
014import java.util.HashSet;
015import java.util.List;
016import java.util.Set;
017import java.util.logging.Logger;
018
019/**
020 * Ported from <a href="http://developer.nvidia.com/object/nvtristrip_library.html">NVIDIA's NvTriStrip Library</a>
021 */
022final class NvStripifier {
023    private static final Logger logger = Logger.getLogger(NvStripifier.class.getName());
024
025    public static int CACHE_INEFFICIENCY = 6;
026
027    protected List<Integer> _indices = new ArrayList<>();
028    protected int _cacheSize;
029    protected int _minStripLength;
030    protected float _meshJump;
031    protected boolean _firstTimeResetPoint;
032
033    /**
034     *
035     * @param in_indices
036     *            the input indices of the mesh to stripify
037     * @param in_cacheSize
038     *            the target cache size
039     * @param in_minStripLength
040     *            the minimum strip length
041     * @param maxIndex
042     *            the maximum index
043     * @param outStrips
044     *            the strips
045     * @param outFaceList
046     *            the face list
047     */
048    void stripify(final List<Integer> in_indices, final int in_cacheSize, final int in_minStripLength,
049            final int maxIndex, final List<NvStripInfo> outStrips, final List<NvFaceInfo> outFaceList) {
050        _meshJump = 0.0f;
051        _firstTimeResetPoint = true; // used in FindGoodResetPoint()
052
053        // the number of times to run the experiments
054        final int numSamples = 10;
055
056        // the cache size, clamped to one
057        _cacheSize = Math.max(1, in_cacheSize - NvStripifier.CACHE_INEFFICIENCY);
058
059        // this is the strip size threshold below which we dump the strip into a list
060        _minStripLength = in_minStripLength;
061
062        _indices = in_indices;
063
064        // build the stripification info
065        final List<NvFaceInfo> allFaceInfos = new ArrayList<>();
066        final List<NvEdgeInfo> allEdgeInfos = new ArrayList<>();
067
068        buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex);
069
070        final List<NvStripInfo> allStrips = new ArrayList<>();
071
072        // stripify
073        findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples);
074
075        // split up the strips into cache friendly pieces, optimize them, then dump these into outStrips
076        splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos, outFaceList);
077    }
078
079    /**
080     * Generates actual strips from the list-in-strip-order.
081     *
082     * @param allStrips
083     *            all strips
084     * @param stripIndices
085     *            the strip indices
086     * @param bStitchStrips
087     *            <code>true</code> if the strips are stitched
088     * @param bRestart
089     *            <code>true</code> if it restarts
090     * @param restartVal
091     *            restart value
092     * @return the number of separate strips
093     */
094    int createStrips(final List<NvStripInfo> allStrips, final List<Integer> stripIndices, final boolean bStitchStrips,
095            final boolean bRestart, final int restartVal) {
096        int numSeparateStrips = 0;
097
098        NvFaceInfo tLastFace = new NvFaceInfo(0, 0, 0);
099        final int nStripCount = allStrips.size();
100        assert nStripCount > 0;
101
102        // we infer the cw/ccw ordering depending on the number of indices
103        // this is screwed up by the fact that we insert -1s to denote changing strips
104        // this is to account for that
105        int accountForNegatives = 0;
106
107        for (int i = 0; i < nStripCount; i++) {
108            final NvStripInfo strip = allStrips.get(i);
109            final int nStripFaceCount = strip._faces.size();
110            assert nStripFaceCount > 0;
111
112            // Handle the first face in the strip
113            {
114                final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0)._v0, strip._faces.get(0)._v1,
115                        strip._faces.get(0)._v2);
116
117                // If there is a second face, reorder vertices such that the
118                // unique vertex is first
119                if (nStripFaceCount > 1) {
120                    final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace);
121                    if (nUnique == tFirstFace._v1) {
122                        final int store = tFirstFace._v1;
123                        tFirstFace._v1 = tFirstFace._v0;
124                        tFirstFace._v0 = store;
125                    } else if (nUnique == tFirstFace._v2) {
126                        final int store = tFirstFace._v2;
127                        tFirstFace._v2 = tFirstFace._v0;
128                        tFirstFace._v0 = store;
129                    }
130
131                    // If there is a third face, reorder vertices such that the
132                    // shared vertex is last
133                    if (nStripFaceCount > 2) {
134                        if (NvStripifier.isDegenerate(strip._faces.get(1))) {
135                            final int pivot = strip._faces.get(1)._v1;
136                            if (tFirstFace._v1 == pivot) {
137                                final int store = tFirstFace._v2;
138                                tFirstFace._v2 = tFirstFace._v1;
139                                tFirstFace._v1 = store;
140                            }
141                        } else {
142                            final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace);
143                            if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) {
144                                final int store = tFirstFace._v2;
145                                tFirstFace._v2 = tFirstFace._v1;
146                                tFirstFace._v1 = store;
147                            }
148                        }
149                    }
150                }
151
152                if (i == 0 || !bStitchStrips || bRestart) {
153                    if (!NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
154                        stripIndices.add(tFirstFace._v0);
155                    }
156                } else {
157                    // Double tap the first in the new strip
158                    stripIndices.add(tFirstFace._v0);
159
160                    // Check CW/CCW ordering
161                    if (NvStripifier.nextIsCW(stripIndices.size() - accountForNegatives) != NvStripifier
162                            .isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
163                        stripIndices.add(tFirstFace._v0);
164                    }
165                }
166
167                stripIndices.add(tFirstFace._v0);
168                stripIndices.add(tFirstFace._v1);
169                stripIndices.add(tFirstFace._v2);
170
171                // Update last face info
172                tLastFace = tFirstFace;
173            }
174
175            for (int j = 1; j < nStripFaceCount; j++) {
176                final int nUnique = NvStripifier.getUniqueVertexInB(tLastFace, strip._faces.get(j));
177                if (nUnique != -1) {
178                    stripIndices.add(nUnique);
179
180                    // Update last face info
181                    tLastFace._v0 = tLastFace._v1;
182                    tLastFace._v1 = tLastFace._v2;
183                    tLastFace._v2 = nUnique;
184                } else {
185                    // we've hit a degenerate
186                    stripIndices.add(strip._faces.get(j)._v2);
187                    tLastFace._v0 = strip._faces.get(j)._v0;// tLastFace.m_v1;
188                    tLastFace._v1 = strip._faces.get(j)._v1;// tLastFace.m_v2;
189                    tLastFace._v2 = strip._faces.get(j)._v2;// tLastFace.m_v1;
190
191                }
192            }
193
194            // Double tap between strips.
195            if (bStitchStrips && !bRestart) {
196                if (i != nStripCount - 1) {
197                    stripIndices.add(tLastFace._v2);
198                }
199            } else if (bRestart) {
200                stripIndices.add(restartVal);
201            } else {
202                // -1 index indicates next strip
203                stripIndices.add(-1);
204                accountForNegatives++;
205                numSeparateStrips++;
206            }
207
208            // Update last face info
209            tLastFace._v0 = tLastFace._v1;
210            tLastFace._v1 = tLastFace._v2;
211            // tLastFace._v2 = tLastFace._v2; // for info purposes.
212        }
213
214        if (bStitchStrips || bRestart) {
215            numSeparateStrips = 1;
216        }
217
218        return numSeparateStrips;
219    }
220
221    /**
222     * @param faceA
223     *            the face A
224     * @param faceB
225     *            the face B
226     * @return the first vertex unique to faceB
227     */
228    static int getUniqueVertexInB(final NvFaceInfo faceA, final NvFaceInfo faceB) {
229        final int facev0 = faceB._v0;
230        if (facev0 != faceA._v0 && facev0 != faceA._v1 && facev0 != faceA._v2) {
231            return facev0;
232        }
233
234        final int facev1 = faceB._v1;
235        if (facev1 != faceA._v0 && facev1 != faceA._v1 && facev1 != faceA._v2) {
236            return facev1;
237        }
238
239        final int facev2 = faceB._v2;
240        if (facev2 != faceA._v0 && facev2 != faceA._v1 && facev2 != faceA._v2) {
241            return facev2;
242        }
243
244        // nothing is different
245        return -1;
246    }
247
248    /**
249     * @param faceA
250     *            the face A
251     * @param faceB
252     *            the face B
253     * @return the (at most) two vertices shared between the two faces
254     */
255    static int[] getSharedVertices(final NvFaceInfo faceA, final NvFaceInfo faceB) {
256        final int[] vertexStore = new int[2];
257        vertexStore[0] = vertexStore[1] = -1;
258
259        final int facev0 = faceB._v0;
260        if (facev0 == faceA._v0 || facev0 == faceA._v1 || facev0 == faceA._v2) {
261            if (vertexStore[0] == -1) {
262                vertexStore[0] = facev0;
263            } else {
264                vertexStore[1] = facev0;
265                return vertexStore;
266            }
267        }
268
269        final int facev1 = faceB._v1;
270        if (facev1 == faceA._v0 || facev1 == faceA._v1 || facev1 == faceA._v2) {
271            if (vertexStore[0] == -1) {
272                vertexStore[0] = facev1;
273            } else {
274                vertexStore[1] = facev1;
275                return vertexStore;
276            }
277        }
278
279        final int facev2 = faceB._v2;
280        if (facev2 == faceA._v0 || facev2 == faceA._v1 || facev2 == faceA._v2) {
281            if (vertexStore[0] == -1) {
282                vertexStore[0] = facev2;
283            } else {
284                vertexStore[1] = facev2;
285                return vertexStore;
286            }
287        }
288
289        return vertexStore;
290    }
291
292    static boolean isDegenerate(final NvFaceInfo face) {
293        if (face._v0 == face._v1) {
294            return true;
295        } else if (face._v0 == face._v2) {
296            return true;
297        } else if (face._v1 == face._v2) {
298            return true;
299        } else {
300            return false;
301        }
302    }
303
304    static boolean isDegenerate(final int v0, final int v1, final int v2) {
305        if (v0 == v1) {
306            return true;
307        } else if (v0 == v2) {
308            return true;
309        } else if (v1 == v2) {
310            return true;
311        } else {
312            return false;
313        }
314    }
315
316    // ///////////////////////////////////////////////////////////////////////////////
317    //
318    // Big mess of functions called during stripification
319    // Note: I removed some that were orphans - JES
320    //
321    // ///////////////////////////////////////////////////////////////////////////////
322
323    /**
324     * @param faceInfo
325     *            the face info
326     * @param v0
327     *            the vertex 0
328     * @param v1
329     *            the vertex 1
330     * @return <code>true</code> if the face is ordered in CW fashion
331     */
332    static boolean isCW(final NvFaceInfo faceInfo, final int v0, final int v1) {
333        if (faceInfo._v0 == v0) {
334            return faceInfo._v1 == v1;
335        } else if (faceInfo._v1 == v0) {
336            return faceInfo._v2 == v1;
337        } else {
338            return faceInfo._v0 == v1;
339        }
340    }
341
342    /**
343     *
344     * @param numIndices
345     *            the number of indices
346     * @return <code>true</code> if the next face should be ordered in CW fashion
347     */
348    static boolean nextIsCW(final int numIndices) {
349        return numIndices % 2 == 0;
350    }
351
352    /**
353     * @param indices
354     *            the indices
355     * @param face
356     *            the face
357     * @return vertex of the input face which is "next" in the input index list
358     */
359    static int getNextIndex(final List<Integer> indices, final NvFaceInfo face) {
360        final int numIndices = indices.size();
361        assert numIndices >= 2;
362
363        final int v0 = indices.get(numIndices - 2);
364        final int v1 = indices.get(numIndices - 1);
365
366        final int fv0 = face._v0;
367        final int fv1 = face._v1;
368        final int fv2 = face._v2;
369
370        if (fv0 != v0 && fv0 != v1) {
371            if (fv1 != v0 && fv1 != v1 || fv2 != v0 && fv2 != v1) {
372                NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
373                NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
374            }
375            return fv0;
376        }
377        if (fv1 != v0 && fv1 != v1) {
378            if (fv0 != v0 && fv0 != v1 || fv2 != v0 && fv2 != v1) {
379                NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
380                NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
381            }
382            return fv1;
383        }
384        if (fv2 != v0 && fv2 != v1) {
385            if (fv0 != v0 && fv0 != v1 || fv1 != v0 && fv1 != v1) {
386                NvStripifier.logger.warning("getNextIndex: Triangle doesn't have all of its vertices\n");
387                NvStripifier.logger.warning("getNextIndex: Duplicate triangle probably got us derailed\n");
388            }
389            return fv2;
390        }
391
392        // shouldn't get here, but let's try and fail gracefully
393        if (fv0 == fv1 || fv0 == fv2) {
394            return fv0;
395        } else if (fv1 == fv0 || fv1 == fv2) {
396            return fv1;
397        } else if (fv2 == fv0 || fv2 == fv1) {
398            return fv2;
399        } else {
400            return -1;
401        }
402    }
403
404    /**
405     * find the edge info for these two indices
406     *
407     * @param edgeInfos
408     *            the edge infos
409     * @param v0
410     *            the vertex 0
411     * @param v1
412     *            the vertex 1
413     * @return the edge info
414     */
415    static NvEdgeInfo findEdgeInfo(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1) {
416        // we can get to it through either array because the edge infos have a v0 and v1 and there is no order except
417        // how it was first created.
418        NvEdgeInfo infoIter = edgeInfos.get(v0);
419        while (infoIter != null) {
420            if (infoIter._v0 == v0) {
421                if (infoIter._v1 == v1) {
422                    return infoIter;
423                } else {
424                    infoIter = infoIter._nextV0;
425                }
426            } else {
427                assert infoIter._v1 == v0;
428                if (infoIter._v0 == v1) {
429                    return infoIter;
430                } else {
431                    infoIter = infoIter._nextV1;
432                }
433            }
434        }
435        return null;
436    }
437
438    /**
439     * find the other face sharing these vertices
440     *
441     * @param edgeInfos
442     *            the edge infos
443     * @param v0
444     *            the vertex 0
445     * @param v1
446     *            the vertex 1
447     * @param faceInfo
448     *            the face info
449     * @return the other face sharing these vertices
450     */
451    @SuppressWarnings("null")
452    static NvFaceInfo findOtherFace(final List<NvEdgeInfo> edgeInfos, final int v0, final int v1,
453            final NvFaceInfo faceInfo) {
454        final NvEdgeInfo edgeInfo = NvStripifier.findEdgeInfo(edgeInfos, v0, v1);
455
456        if (edgeInfo == null && v0 == v1) {
457            // we've hit a degenerate
458            return null;
459        }
460
461        assert edgeInfo != null;
462        return edgeInfo._face0 == faceInfo ? edgeInfo._face1 : edgeInfo._face0;
463    }
464
465    /**
466     * A good reset point is one near other committed areas so that we know that when we've made the longest strips its
467     * because we're stripifying in the same general orientation.
468     *
469     * @param faceInfos
470     *            the face infos
471     * @param edgeInfos
472     *            the edge infos
473     * @return the good reset point
474     */
475    NvFaceInfo findGoodResetPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) {
476        // we hop into different areas of the mesh to try to get other large open spans done. Areas of small strips can
477        // just be left to triangle lists added at the end.
478        NvFaceInfo result = null;
479        final int numFaces = faceInfos.size();
480        int startPoint;
481        if (_firstTimeResetPoint) {
482            // first time, find a face with few neighbors (look for an edge of the mesh)
483            startPoint = findStartPoint(faceInfos, edgeInfos);
484            _firstTimeResetPoint = false;
485        } else {
486            startPoint = (int) (((float) numFaces - 1) * _meshJump);
487        }
488
489        if (startPoint == -1) {
490            startPoint = (int) (((float) numFaces - 1) * _meshJump);
491
492            // meshJump += 0.1f;
493            // if (meshJump > 1.0f)
494            // meshJump = .05f;
495        }
496
497        int i = startPoint;
498        do {
499
500            // if this guy isn't visited, try him
501            if (faceInfos.get(i)._stripId < 0) {
502                result = faceInfos.get(i);
503                break;
504            }
505
506            // update the index and clamp to 0-(numFaces-1)
507            if (++i >= numFaces) {
508                i = 0;
509            }
510
511        } while (i != startPoint);
512
513        // update the meshJump
514        _meshJump += 0.1f;
515        if (_meshJump > 1.0f) {
516            _meshJump = .05f;
517        }
518
519        // return the best face we found
520        return result;
521    }
522
523    /**
524     * Does the stripification, puts output strips into vector allStrips
525     *
526     * Works by setting running a number of experiments in different areas of the mesh, and accepting the one which
527     * results in the longest strips. It then accepts this, and moves on to a different area of the mesh. We try to jump
528     * around the mesh some, to ensure that large open spans of strips get generated.
529     *
530     * @param allStrips
531     *            all strips
532     * @param allFaceInfos
533     *            all face infos
534     * @param allEdgeInfos
535     *            all edge infos
536     * @param numSamples
537     *            the number of samples
538     */
539    @SuppressWarnings("unchecked")
540    void findAllStrips(final List<NvStripInfo> allStrips, final List<NvFaceInfo> allFaceInfos,
541            final List<NvEdgeInfo> allEdgeInfos, final int numSamples) {
542        // the experiments
543        int experimentId = 0;
544        int stripId = 0;
545        boolean done = false;
546
547        while (!done) {
548
549            //
550            // PHASE 1: Set up numSamples * numEdges experiments
551            //
552            final List<NvStripInfo>[] experiments = new List[numSamples * 6];
553            for (int i = 0; i < experiments.length; i++) {
554                experiments[i] = new ArrayList<>();
555            }
556
557            int experimentIndex = 0;
558            final Set<NvFaceInfo> resetPoints = new HashSet<>();
559            for (int i = 0; i < numSamples; i++) {
560
561                // Try to find another good reset point.
562                // If there are none to be found, we are done
563                final NvFaceInfo nextFace = findGoodResetPoint(allFaceInfos, allEdgeInfos);
564                if (nextFace == null) {
565                    done = true;
566                    break;
567                }
568                // If we have already evaluated starting at this face in this slew
569                // of experiments, then skip going any further
570                else if (resetPoints.contains(nextFace)) {
571                    continue;
572                }
573
574                // trying it now...
575                resetPoints.add(nextFace);
576
577                // otherwise, we shall now try experiments for starting on the 01,12, and 20 edges
578                assert nextFace._stripId < 0;
579
580                // build the strip off of this face's 0-1 edge
581                final NvEdgeInfo edge01 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1);
582                final NvStripInfo strip01 = new NvStripInfo(new NvStripStartInfo(nextFace, edge01, true), stripId++,
583                        experimentId++);
584                experiments[experimentIndex++].add(strip01);
585
586                // build the strip off of this face's 1-0 edge
587                final NvEdgeInfo edge10 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v0, nextFace._v1);
588                final NvStripInfo strip10 = new NvStripInfo(new NvStripStartInfo(nextFace, edge10, false), stripId++,
589                        experimentId++);
590                experiments[experimentIndex++].add(strip10);
591
592                // build the strip off of this face's 1-2 edge
593                final NvEdgeInfo edge12 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2);
594                final NvStripInfo strip12 = new NvStripInfo(new NvStripStartInfo(nextFace, edge12, true), stripId++,
595                        experimentId++);
596                experiments[experimentIndex++].add(strip12);
597
598                // build the strip off of this face's 2-1 edge
599                final NvEdgeInfo edge21 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v1, nextFace._v2);
600                final NvStripInfo strip21 = new NvStripInfo(new NvStripStartInfo(nextFace, edge21, false), stripId++,
601                        experimentId++);
602                experiments[experimentIndex++].add(strip21);
603
604                // build the strip off of this face's 2-0 edge
605                final NvEdgeInfo edge20 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0);
606                final NvStripInfo strip20 = new NvStripInfo(new NvStripStartInfo(nextFace, edge20, true), stripId++,
607                        experimentId++);
608                experiments[experimentIndex++].add(strip20);
609
610                // build the strip off of this face's 0-2 edge
611                final NvEdgeInfo edge02 = NvStripifier.findEdgeInfo(allEdgeInfos, nextFace._v2, nextFace._v0);
612                final NvStripInfo strip02 = new NvStripInfo(new NvStripStartInfo(nextFace, edge02, false), stripId++,
613                        experimentId++);
614                experiments[experimentIndex++].add(strip02);
615            }
616
617            //
618            // PHASE 2: Iterate through that we setup in the last phase
619            // and really build each of the strips and strips that follow to see how
620            // far we get
621            //
622            final int numExperiments = experimentIndex;
623            for (int i = 0; i < numExperiments; i++) {
624                // get the strip set
625                NvStripInfo stripIter = experiments[i].get(0);
626
627                // build the first strip of the list
628                stripIter.build(allEdgeInfos, allFaceInfos);
629                final int currExperimentId = stripIter._experimentId;
630
631                final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false);
632                while (findTraversal(allFaceInfos, allEdgeInfos, stripIter, startInfo)) {
633
634                    // create the new strip info
635                    stripIter = new NvStripInfo(startInfo, stripId++, currExperimentId);
636
637                    // build the next strip
638                    stripIter.build(allEdgeInfos, allFaceInfos);
639
640                    // add it to the list
641                    experiments[i].add(stripIter);
642                }
643            }
644
645            //
646            // Phase 3: Find the experiment that has the most promise
647            //
648            int bestIndex = 0;
649            double bestValue = 0;
650            for (int i = 0; i < numExperiments; i++) {
651                final float avgStripSizeWeight = 1.0f;
652                // final float numTrisWeight = 0.0f; // unused
653                final float numStripsWeight = 0.0f;
654                final float avgStripSize = avgStripSize(experiments[i]);
655                final float numStrips = experiments[i].size();
656                final float value = avgStripSize * avgStripSizeWeight + numStrips * numStripsWeight;
657                // float value = 1.f / numStrips;
658                // float value = numStrips * avgStripSize;
659
660                if (value > bestValue) {
661                    bestValue = value;
662                    bestIndex = i;
663                }
664            }
665
666            //
667            // Phase 4: commit the best experiment of the bunch
668            //
669            commitStrips(allStrips, experiments[bestIndex]);
670        }
671    }
672
673    /**
674     * Splits the input vector of strips (allBigStrips) into smaller, cache friendly pieces, then reorders these pieces
675     * to maximize cache hits. The final strips are stored in outStrips
676     *
677     * @param allStrips
678     *            all strips
679     * @param outStrips
680     *            the resulting strips
681     * @param edgeInfos
682     *            the edge infos
683     * @param outFaceList
684     *            the resulting face list
685     */
686    void splitUpStripsAndOptimize(final List<NvStripInfo> allStrips, final List<NvStripInfo> outStrips,
687            final List<NvEdgeInfo> edgeInfos, final List<NvFaceInfo> outFaceList) {
688        final int threshold = _cacheSize;
689        final List<NvStripInfo> tempStrips = new ArrayList<>();
690
691        // split up strips into threshold-sized pieces
692        for (int i = 0; i < allStrips.size(); i++) {
693            final NvStripInfo allStripI = allStrips.get(i);
694            NvStripInfo currentStrip;
695            final NvStripStartInfo startInfo = new NvStripStartInfo(null, null, false);
696
697            int actualStripSize = 0;
698            for (final NvFaceInfo face : allStripI._faces) {
699                if (!NvStripifier.isDegenerate(face)) {
700                    actualStripSize++;
701                }
702            }
703
704            if (actualStripSize > threshold) {
705
706                final int numTimes = actualStripSize / threshold;
707                int numLeftover = actualStripSize % threshold;
708
709                int degenerateCount = 0, j = 0;
710                for (; j < numTimes; j++) {
711                    currentStrip = new NvStripInfo(startInfo, 0, -1);
712
713                    int faceCtr = j * threshold + degenerateCount;
714                    boolean bFirstTime = true;
715                    while (faceCtr < threshold + j * threshold + degenerateCount) {
716                        if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) {
717                            degenerateCount++;
718
719                            // last time or first time through, no need for a degenerate
720                            if ((faceCtr + 1 != threshold + j * threshold + degenerateCount
721                                    || j == numTimes - 1 && numLeftover < 4 && numLeftover > 0) && !bFirstTime) {
722                                currentStrip._faces.add(allStripI._faces.get(faceCtr++));
723                            } else {
724                                ++faceCtr;
725                            }
726                        } else {
727                            currentStrip._faces.add(allStripI._faces.get(faceCtr++));
728                            bFirstTime = false;
729                        }
730                    }
731                    if (j == numTimes - 1) // last time through
732                    {
733                        if (numLeftover < 4 && numLeftover > 0) // way too small
734                        {
735                            // just add to last strip
736                            int ctr = 0;
737                            while (ctr < numLeftover) {
738                                if (NvStripifier.isDegenerate(allStripI._faces.get(faceCtr))) {
739                                    ++degenerateCount;
740                                } else {
741                                    ++ctr;
742                                }
743                                currentStrip._faces.add(allStripI._faces.get(faceCtr++));
744                            }
745                            numLeftover = 0;
746                        }
747                    }
748                    tempStrips.add(currentStrip);
749                }
750                int leftOff = j * threshold + degenerateCount;
751
752                if (numLeftover != 0) {
753                    currentStrip = new NvStripInfo(startInfo, 0, -1);
754
755                    int ctr = 0;
756                    boolean bFirstTime = true;
757                    while (ctr < numLeftover) {
758                        if (!NvStripifier.isDegenerate(allStripI._faces.get(leftOff))) {
759                            ctr++;
760                            bFirstTime = false;
761                            currentStrip._faces.add(allStripI._faces.get(leftOff++));
762                        } else if (!bFirstTime) {
763                            currentStrip._faces.add(allStripI._faces.get(leftOff++));
764                        } else {
765                            leftOff++;
766                        }
767                    }
768
769                    tempStrips.add(currentStrip);
770                }
771            } else {
772                // we're not just doing a tempStrips.add(allBigStrips.get(i)) because
773                // this way we can delete allBigStrips later to free the memory
774                currentStrip = new NvStripInfo(startInfo, 0, -1);
775
776                for (int j = 0; j < allStripI._faces.size(); j++) {
777                    currentStrip._faces.add(allStripI._faces.get(j));
778                }
779
780                tempStrips.add(currentStrip);
781            }
782        }
783
784        // add small strips to face list
785        final List<NvStripInfo> tempStrips2 = new ArrayList<>();
786        removeSmallStrips(tempStrips, tempStrips2, outFaceList);
787
788        outStrips.clear();
789        // screw optimization for now
790        // for(i = 0; i < tempStrips.size(); ++i)
791        // outStrips.add(tempStrips.get(i));
792
793        if (tempStrips2.size() != 0) {
794            // Optimize for the vertex cache
795            final VertexCache vcache = new VertexCache(_cacheSize);
796
797            float bestNumHits = -1.0f;
798            float numHits;
799            int bestIndex = 0;
800            int firstIndex = 0;
801            float minCost = 10000.0f;
802
803            for (int i = 0; i < tempStrips2.size(); i++) {
804                final NvStripInfo tempStrips2I = tempStrips2.get(i);
805                int numNeighbors = 0;
806
807                // find strip with least number of neighbors per face
808                for (int j = 0; j < tempStrips2I._faces.size(); j++) {
809                    numNeighbors += numNeighbors(tempStrips2I._faces.get(j), edgeInfos);
810                }
811
812                final float currCost = numNeighbors / (float) tempStrips2I._faces.size();
813                if (currCost < minCost) {
814                    minCost = currCost;
815                    firstIndex = i;
816                }
817            }
818
819            final NvStripInfo tempStrips2FirstIndex = tempStrips2.get(firstIndex);
820            updateCacheStrip(vcache, tempStrips2FirstIndex);
821            outStrips.add(tempStrips2FirstIndex);
822
823            tempStrips2FirstIndex._visited = true;
824
825            boolean bWantsCW = tempStrips2FirstIndex._faces.size() % 2 == 0;
826
827            // XXX: this n^2 algo is what slows down stripification so much.... needs to be improved
828            while (true) {
829                bestNumHits = -1.0f;
830
831                // find best strip to add next, given the current cache
832                for (int i = 0; i < tempStrips2.size(); i++) {
833                    final NvStripInfo tempStrips2I = tempStrips2.get(i);
834                    if (tempStrips2I._visited) {
835                        continue;
836                    }
837
838                    numHits = calcNumHitsStrip(vcache, tempStrips2I);
839                    if (numHits > bestNumHits) {
840                        bestNumHits = numHits;
841                        bestIndex = i;
842                    } else if (numHits >= bestNumHits) {
843                        // check previous strip to see if this one requires it to switch polarity
844                        final NvStripInfo strip = tempStrips2I;
845                        final int nStripFaceCount = strip._faces.size();
846
847                        final NvFaceInfo tFirstFace = new NvFaceInfo(strip._faces.get(0));
848
849                        // If there is a second face, reorder vertices such that the
850                        // unique vertex is first
851                        if (nStripFaceCount > 1) {
852                            final int nUnique = NvStripifier.getUniqueVertexInB(strip._faces.get(1), tFirstFace);
853                            if (nUnique == tFirstFace._v1) {
854                                final int store = tFirstFace._v1;
855                                tFirstFace._v1 = tFirstFace._v0;
856                                tFirstFace._v0 = store;
857                            } else if (nUnique == tFirstFace._v2) {
858                                final int store = tFirstFace._v2;
859                                tFirstFace._v2 = tFirstFace._v0;
860                                tFirstFace._v0 = store;
861                            }
862
863                            // If there is a third face, reorder vertices such that the
864                            // shared vertex is last
865                            if (nStripFaceCount > 2) {
866                                final int[] nShared = NvStripifier.getSharedVertices(strip._faces.get(2), tFirstFace);
867                                if (nShared[0] == tFirstFace._v1 && nShared[1] == -1) {
868                                    final int store = tFirstFace._v2;
869                                    tFirstFace._v2 = tFirstFace._v1;
870                                    tFirstFace._v1 = store;
871                                }
872                            }
873                        }
874
875                        // Check CW/CCW ordering
876                        if (bWantsCW == NvStripifier.isCW(strip._faces.get(0), tFirstFace._v0, tFirstFace._v1)) {
877                            // I like this one!
878                            bestIndex = i;
879                        }
880                    }
881                }
882
883                if (bestNumHits == -1.0f) {
884                    break;
885                }
886                tempStrips2.get(bestIndex)._visited = true;
887                updateCacheStrip(vcache, tempStrips2.get(bestIndex));
888                outStrips.add(tempStrips2.get(bestIndex));
889                bWantsCW = tempStrips2.get(bestIndex)._faces.size() % 2 == 0 ? bWantsCW : !bWantsCW;
890            }
891        }
892    }
893
894    /**
895     * @param allStrips
896     *            the whole strip vector...all small strips will be deleted from this list, to avoid leaking mem
897     * @param allBigStrips
898     *            an out parameter which will contain all strips above minStripLength
899     * @param faceList
900     *            an out parameter which will contain all faces which were removed from the striplist
901     */
902    void removeSmallStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> allBigStrips,
903            final List<NvFaceInfo> faceList) {
904        faceList.clear();
905        allBigStrips.clear(); // make sure these are empty
906        final List<NvFaceInfo> tempFaceList = new ArrayList<>();
907
908        for (int i = 0; i < allStrips.size(); i++) {
909            final NvStripInfo allStripI = allStrips.get(i);
910            if (allStripI._faces.size() < _minStripLength) {
911                // strip is too small, add faces to faceList
912                for (int j = 0; j < allStripI._faces.size(); j++) {
913                    tempFaceList.add(allStripI._faces.get(j));
914                }
915            } else {
916                allBigStrips.add(allStripI);
917            }
918        }
919
920        if (!tempFaceList.isEmpty()) {
921            final boolean[] bVisitedList = new boolean[tempFaceList.size()];
922            final VertexCache vcache = new VertexCache(_cacheSize);
923
924            int bestNumHits = -1;
925            int numHits = 0;
926            int bestIndex = 0;
927
928            while (true) {
929                bestNumHits = -1;
930
931                // find best face to add next, given the current cache
932                for (int i = 0; i < tempFaceList.size(); i++) {
933                    if (bVisitedList[i]) {
934                        continue;
935                    }
936
937                    numHits = calcNumHitsFace(vcache, tempFaceList.get(i));
938                    if (numHits > bestNumHits) {
939                        bestNumHits = numHits;
940                        bestIndex = i;
941                    }
942                }
943
944                if (bestNumHits == -1.0f) {
945                    break;
946                }
947                bVisitedList[bestIndex] = true;
948                updateCacheFace(vcache, tempFaceList.get(bestIndex));
949                faceList.add(tempFaceList.get(bestIndex));
950            }
951        }
952    }
953
954    /**
955     * Finds the next face to start the next strip on.
956     *
957     * @param faceInfos
958     *            the face infos
959     * @param edgeInfos
960     *            the edge infos
961     * @param strip
962     *            the strip
963     * @param startInfo
964     *            the start info
965     * @return <code>true</code> if the next face to start the next strip on is found
966     */
967    boolean findTraversal(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final NvStripInfo strip,
968            final NvStripStartInfo startInfo) {
969        // if the strip was v0.v1 on the edge, then v1 will be a vertex in the next edge.
970        final int v = strip._startInfo._toV1 ? strip._startInfo._startEdge._v1 : strip._startInfo._startEdge._v0;
971
972        NvFaceInfo untouchedFace = null;
973        NvEdgeInfo edgeIter = edgeInfos.get(v);
974        while (edgeIter != null) {
975            final NvFaceInfo face0 = edgeIter._face0;
976            final NvFaceInfo face1 = edgeIter._face1;
977            if (face0 != null && !strip.IsInStrip(face0) && face1 != null && !strip.isMarked(face1)) {
978                untouchedFace = face1;
979                break;
980            }
981            if (face1 != null && !strip.IsInStrip(face1) && face0 != null && !strip.isMarked(face0)) {
982                untouchedFace = face0;
983                break;
984            }
985
986            // find the next edgeIter
987            edgeIter = edgeIter._v0 == v ? edgeIter._nextV0 : edgeIter._nextV1;
988        }
989
990        startInfo._startFace = untouchedFace;
991        startInfo._startEdge = edgeIter;
992        if (edgeIter != null) {
993            if (strip.sharesEdge(startInfo._startFace, edgeInfos)) {
994                startInfo._toV1 = edgeIter._v0 == v; // note! used to be m_v1
995            } else {
996                startInfo._toV1 = edgeIter._v1 == v;
997            }
998        }
999        return startInfo._startFace != null;
1000    }
1001
1002    /**
1003     * "Commits" the input strips by setting their m_experimentId to -1 and adding to the allStrips vector
1004     *
1005     * @param allStrips
1006     *            all strips
1007     * @param strips
1008     *            the strips
1009     */
1010    void commitStrips(final List<NvStripInfo> allStrips, final List<NvStripInfo> strips) {
1011        // Iterate through strips
1012        for (final NvStripInfo strip : strips) {
1013            // Tell the strip that it is now real
1014            strip._experimentId = -1;
1015
1016            // add to the list of real strips
1017            allStrips.add(strip);
1018
1019            // Iterate through the faces of the strip
1020            // Tell the faces of the strip that they belong to a real strip now
1021            final List<NvFaceInfo> faces = strip._faces;
1022            for (final NvFaceInfo face : faces) {
1023                strip.markTriangle(face);
1024            }
1025        }
1026    }
1027
1028    /**
1029     *
1030     * @param strips
1031     *            the strips
1032     * @return the average strip size of the input vector of strips
1033     */
1034    float avgStripSize(final List<NvStripInfo> strips) {
1035        int sizeAccum = 0;
1036        for (final NvStripInfo strip : strips) {
1037            sizeAccum += strip._faces.size();
1038            sizeAccum -= strip._numDegenerates;
1039        }
1040        return (float) sizeAccum / (float) strips.size();
1041    }
1042
1043    /**
1044     * Finds a good starting point, namely one which has only one neighbor
1045     *
1046     * @param faceInfos
1047     *            the face infos
1048     * @param edgeInfos
1049     *            the edge infos
1050     * @return the good starting point or -1
1051     */
1052    int findStartPoint(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos) {
1053        int bestCtr = -1;
1054        int bestIndex = -1;
1055
1056        int i = 0;
1057        for (final NvFaceInfo faceInfo : faceInfos) {
1058            int ctr = 0;
1059
1060            if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v0, faceInfo._v1, faceInfo) == null) {
1061                ctr++;
1062            }
1063            if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v1, faceInfo._v2, faceInfo) == null) {
1064                ctr++;
1065            }
1066            if (NvStripifier.findOtherFace(edgeInfos, faceInfo._v2, faceInfo._v0, faceInfo) == null) {
1067                ctr++;
1068            }
1069            if (ctr > bestCtr) {
1070                bestCtr = ctr;
1071                bestIndex = i;
1072            }
1073            i++;
1074        }
1075
1076        if (bestCtr == 0) {
1077            return -1;
1078        } else {
1079            return bestIndex;
1080        }
1081    }
1082
1083    /**
1084     * Updates the input vertex cache with this strip's vertices
1085     *
1086     * @param vcache
1087     *            the vertex cache
1088     * @param strip
1089     *            the strip
1090     */
1091    void updateCacheStrip(final VertexCache vcache, final NvStripInfo strip) {
1092        for (final NvFaceInfo face : strip._faces) {
1093            updateCacheFace(vcache, face);
1094        }
1095    }
1096
1097    /**
1098     * Updates the input vertex cache with this face's vertices
1099     *
1100     * @param vcache
1101     *            the vertex cache
1102     * @param face
1103     *            the face
1104     */
1105    void updateCacheFace(final VertexCache vcache, final NvFaceInfo face) {
1106        if (!vcache.inCache(face._v0)) {
1107            vcache.addEntry(face._v0);
1108        }
1109
1110        if (!vcache.inCache(face._v1)) {
1111            vcache.addEntry(face._v1);
1112        }
1113
1114        if (!vcache.inCache(face._v2)) {
1115            vcache.addEntry(face._v2);
1116        }
1117    }
1118
1119    /**
1120     * @param vcache
1121     *            the vertex cache
1122     * @param strip
1123     *            the strip
1124     * @return the number of cache hits per face in the strip
1125     */
1126    float calcNumHitsStrip(final VertexCache vcache, final NvStripInfo strip) {
1127        int numHits = 0;
1128        int numFaces = 0;
1129
1130        for (final NvFaceInfo face : strip._faces) {
1131            if (vcache.inCache(face._v0)) {
1132                ++numHits;
1133            }
1134
1135            if (vcache.inCache(face._v1)) {
1136                ++numHits;
1137            }
1138
1139            if (vcache.inCache(face._v2)) {
1140                ++numHits;
1141            }
1142
1143            numFaces++;
1144        }
1145
1146        return (float) numHits / (float) numFaces;
1147    }
1148
1149    /**
1150     * @param vcache
1151     *            the vertex cache
1152     * @param face
1153     *            the face
1154     * @return the number of cache hits in the face
1155     */
1156    int calcNumHitsFace(final VertexCache vcache, final NvFaceInfo face) {
1157        int numHits = 0;
1158
1159        if (vcache.inCache(face._v0)) {
1160            numHits++;
1161        }
1162
1163        if (vcache.inCache(face._v1)) {
1164            numHits++;
1165        }
1166
1167        if (vcache.inCache(face._v2)) {
1168            numHits++;
1169        }
1170
1171        return numHits;
1172    }
1173
1174    /**
1175     *
1176     * @param face
1177     *            the face
1178     * @param edgeInfoVec
1179     *            the edge info list
1180     * @return the number of neighbors that this face has
1181     */
1182    int numNeighbors(final NvFaceInfo face, final List<NvEdgeInfo> edgeInfoVec) {
1183        int numNeighbors = 0;
1184
1185        if (NvStripifier.findOtherFace(edgeInfoVec, face._v0, face._v1, face) != null) {
1186            numNeighbors++;
1187        }
1188
1189        if (NvStripifier.findOtherFace(edgeInfoVec, face._v1, face._v2, face) != null) {
1190            numNeighbors++;
1191        }
1192
1193        if (NvStripifier.findOtherFace(edgeInfoVec, face._v2, face._v0, face) != null) {
1194            numNeighbors++;
1195        }
1196
1197        return numNeighbors;
1198    }
1199
1200    /**
1201     * Builds the list of all face and edge infos
1202     *
1203     * @param faceInfos
1204     *            the face infos
1205     * @param edgeInfos
1206     *            the edge infos
1207     * @param maxIndex
1208     *            the maximum index
1209     */
1210    void buildStripifyInfo(final List<NvFaceInfo> faceInfos, final List<NvEdgeInfo> edgeInfos, final int maxIndex) {
1211        // reserve space for the face infos, but do not resize them.
1212        final int numIndices = _indices.size();
1213
1214        // we actually resize the edge infos, so we must initialize to null
1215        for (int i = 0; i <= maxIndex; i++) {
1216            edgeInfos.add(null);
1217        }
1218
1219        // iterate through the triangles of the triangle list
1220        final int numTriangles = numIndices / 3;
1221        int index = 0;
1222        final boolean[] bFaceUpdated = new boolean[3];
1223
1224        for (int i = 0; i < numTriangles; i++) {
1225            boolean bMightAlreadyExist = true;
1226            bFaceUpdated[0] = false;
1227            bFaceUpdated[1] = false;
1228            bFaceUpdated[2] = false;
1229
1230            // grab the indices
1231            final int v0 = _indices.get(index++);
1232            final int v1 = _indices.get(index++);
1233            final int v2 = _indices.get(index++);
1234
1235            // we disregard degenerates
1236            if (NvStripifier.isDegenerate(v0, v1, v2)) {
1237                continue;
1238            }
1239
1240            // create the face info and add it to the list of faces, but only if this exact face doesn't already
1241            // exist in the list
1242            final NvFaceInfo faceInfo = new NvFaceInfo(v0, v1, v2);
1243
1244            // grab the edge infos, creating them if they do not already exist
1245            NvEdgeInfo edgeInfo01 = NvStripifier.findEdgeInfo(edgeInfos, v0, v1);
1246            if (edgeInfo01 == null) {
1247                // since one of it's edges isn't in the edge data structure, it can't already exist in the face
1248                // structure
1249                bMightAlreadyExist = false;
1250
1251                // create the info
1252                edgeInfo01 = new NvEdgeInfo(v0, v1);
1253
1254                // update the linked list on both
1255                edgeInfo01._nextV0 = edgeInfos.get(v0);
1256                edgeInfo01._nextV1 = edgeInfos.get(v1);
1257                edgeInfos.set(v0, edgeInfo01);
1258                edgeInfos.set(v1, edgeInfo01);
1259
1260                // set face 0
1261                edgeInfo01._face0 = faceInfo;
1262            } else {
1263                if (edgeInfo01._face1 != null) {
1264                    NvStripifier.logger
1265                            .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
1266                } else {
1267                    edgeInfo01._face1 = faceInfo;
1268                    bFaceUpdated[0] = true;
1269                }
1270            }
1271
1272            // grab the edge infos, creating them if they do not already exist
1273            NvEdgeInfo edgeInfo12 = NvStripifier.findEdgeInfo(edgeInfos, v1, v2);
1274            if (edgeInfo12 == null) {
1275                bMightAlreadyExist = false;
1276
1277                // create the info
1278                edgeInfo12 = new NvEdgeInfo(v1, v2);
1279
1280                // update the linked list on both
1281                edgeInfo12._nextV0 = edgeInfos.get(v1);
1282                edgeInfo12._nextV1 = edgeInfos.get(v2);
1283                edgeInfos.set(v1, edgeInfo12);
1284                edgeInfos.set(v2, edgeInfo12);
1285
1286                // set face 0
1287                edgeInfo12._face0 = faceInfo;
1288            } else {
1289                if (edgeInfo12._face1 != null) {
1290                    NvStripifier.logger
1291                            .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
1292                } else {
1293                    edgeInfo12._face1 = faceInfo;
1294                    bFaceUpdated[1] = true;
1295                }
1296            }
1297
1298            // grab the edge infos, creating them if they do not already exist
1299            NvEdgeInfo edgeInfo20 = NvStripifier.findEdgeInfo(edgeInfos, v2, v0);
1300            if (edgeInfo20 == null) {
1301                bMightAlreadyExist = false;
1302
1303                // create the info
1304                edgeInfo20 = new NvEdgeInfo(v2, v0);
1305
1306                // update the linked list on both
1307                edgeInfo20._nextV0 = edgeInfos.get(v2);
1308                edgeInfo20._nextV1 = edgeInfos.get(v0);
1309                edgeInfos.set(v2, edgeInfo20);
1310                edgeInfos.set(v0, edgeInfo20);
1311
1312                // set face 0
1313                edgeInfo20._face0 = faceInfo;
1314            } else {
1315                if (edgeInfo20._face1 != null) {
1316                    NvStripifier.logger
1317                            .warning("BuildStripifyInfo: > 2 triangles on an edge... uncertain consequences\n");
1318                } else {
1319                    edgeInfo20._face1 = faceInfo;
1320                    bFaceUpdated[2] = true;
1321                }
1322            }
1323
1324            if (bMightAlreadyExist) {
1325                if (!alreadyExists(faceInfo, faceInfos)) {
1326                    faceInfos.add(faceInfo);
1327                } else {
1328                    // cleanup pointers that point to this deleted face
1329                    if (bFaceUpdated[0]) {
1330                        edgeInfo01._face1 = null;
1331                    }
1332                    if (bFaceUpdated[1]) {
1333                        edgeInfo12._face1 = null;
1334                    }
1335                    if (bFaceUpdated[2]) {
1336                        edgeInfo20._face1 = null;
1337                    }
1338                }
1339            } else {
1340                faceInfos.add(faceInfo);
1341            }
1342        }
1343    }
1344
1345    boolean alreadyExists(final NvFaceInfo toFind, final List<NvFaceInfo> faceInfos) {
1346        for (final NvFaceInfo face : faceInfos) {
1347            if (face._v0 == toFind._v0 && face._v1 == toFind._v1 && face._v2 == toFind._v2) {
1348                return true;
1349            }
1350        }
1351
1352        return false;
1353    }
1354}