/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package jme3tools.converters.model.strip; import java.util.HashSet; import java.util.logging.Logger; /** * */ class Stripifier { private static final Logger logger = Logger.getLogger(Stripifier.class .getName()); public static int CACHE_INEFFICIENCY = 6; IntVec indices = new IntVec(); int cacheSize; int minStripLength; float meshJump; boolean bFirstTimeResetPoint; Stripifier() { super(); } /////////////////////////////////////////////////////////////////////////////////////////// // FindEdgeInfo() // // find the edge info for these two indices // static EdgeInfo findEdgeInfo(EdgeInfoVec edgeInfos, int v0, int v1) { // we can get to it through either array // because the edge infos have a v0 and v1 // and there is no order except how it was // first created. EdgeInfo infoIter = edgeInfos.at(v0); while (infoIter != null) { if (infoIter.m_v0 == v0) { if (infoIter.m_v1 == v1) return infoIter; infoIter = infoIter.m_nextV0; } else { if (infoIter.m_v0 == v1) return infoIter; infoIter = infoIter.m_nextV1; } } return null; } /////////////////////////////////////////////////////////////////////////////////////////// // FindOtherFace // // find the other face sharing these vertices // exactly like the edge info above // static FaceInfo findOtherFace(EdgeInfoVec edgeInfos, int v0, int v1, FaceInfo faceInfo) { EdgeInfo edgeInfo = findEdgeInfo(edgeInfos, v0, v1); if ((edgeInfo == null) || (v0 == v1)) { //we've hit a degenerate return null; } return (edgeInfo.m_face0 == faceInfo ? edgeInfo.m_face1 : edgeInfo.m_face0); } static boolean alreadyExists(FaceInfo faceInfo, FaceInfoVec faceInfos) { for (int i = 0; i < faceInfos.size(); ++i) { FaceInfo o = faceInfos.at(i); if ((o.m_v0 == faceInfo.m_v0) && (o.m_v1 == faceInfo.m_v1) && (o.m_v2 == faceInfo.m_v2)) return true; } return false; } /////////////////////////////////////////////////////////////////////////////////////////// // BuildStripifyInfo() // // Builds the list of all face and edge infos // void buildStripifyInfo(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos, int maxIndex) { // reserve space for the face infos, but do not resize them. int numIndices = indices.size(); faceInfos.reserve(numIndices / 3); // we actually resize the edge infos, so we must initialize to null for (int i = 0; i < maxIndex + 1; i++) edgeInfos.add(null); // iterate through the triangles of the triangle list int numTriangles = numIndices / 3; int index = 0; boolean[] bFaceUpdated = new boolean[3]; for (int i = 0; i < numTriangles; i++) { boolean bMightAlreadyExist = true; bFaceUpdated[0] = false; bFaceUpdated[1] = false; bFaceUpdated[2] = false; // grab the indices int v0 = indices.get(index++); int v1 = indices.get(index++); int v2 = indices.get(index++); //we disregard degenerates if (isDegenerate(v0, v1, v2)) continue; // create the face info and add it to the list of faces, but only // if this exact face doesn't already // exist in the list FaceInfo faceInfo = new FaceInfo(v0, v1, v2); // grab the edge infos, creating them if they do not already exist EdgeInfo edgeInfo01 = findEdgeInfo(edgeInfos, v0, v1); if (edgeInfo01 == null) { //since one of it's edges isn't in the edge data structure, it // can't already exist in the face structure bMightAlreadyExist = false; // create the info edgeInfo01 = new EdgeInfo(v0, v1); // update the linked list on both edgeInfo01.m_nextV0 = edgeInfos.at(v0); edgeInfo01.m_nextV1 = edgeInfos.at(v1); edgeInfos.set(v0, edgeInfo01); edgeInfos.set(v1, edgeInfo01); // set face 0 edgeInfo01.m_face0 = faceInfo; } else { if (edgeInfo01.m_face1 != null) { logger.info("BuildStripifyInfo: > 2 triangles on an edge" + v0 + "," + v1 + "... uncertain consequences\n"); } else { edgeInfo01.m_face1 = faceInfo; bFaceUpdated[0] = true; } } // grab the edge infos, creating them if they do not already exist EdgeInfo edgeInfo12 = findEdgeInfo(edgeInfos, v1, v2); if (edgeInfo12 == null) { bMightAlreadyExist = false; // create the info edgeInfo12 = new EdgeInfo(v1, v2); // update the linked list on both edgeInfo12.m_nextV0 = edgeInfos.at(v1); edgeInfo12.m_nextV1 = edgeInfos.at(v2); edgeInfos.set(v1, edgeInfo12); edgeInfos.set(v2, edgeInfo12); // set face 0 edgeInfo12.m_face0 = faceInfo; } else { if (edgeInfo12.m_face1 != null) { logger.info("BuildStripifyInfo: > 2 triangles on an edge" + v1 + "," + v2 + "... uncertain consequences\n"); } else { edgeInfo12.m_face1 = faceInfo; bFaceUpdated[1] = true; } } // grab the edge infos, creating them if they do not already exist EdgeInfo edgeInfo20 = findEdgeInfo(edgeInfos, v2, v0); if (edgeInfo20 == null) { bMightAlreadyExist = false; // create the info edgeInfo20 = new EdgeInfo(v2, v0); // update the linked list on both edgeInfo20.m_nextV0 = edgeInfos.at(v2); edgeInfo20.m_nextV1 = edgeInfos.at(v0); edgeInfos.set(v2, edgeInfo20); edgeInfos.set(v0, edgeInfo20); // set face 0 edgeInfo20.m_face0 = faceInfo; } else { if (edgeInfo20.m_face1 != null) { logger.info("BuildStripifyInfo: > 2 triangles on an edge" + v2 + "," + v0 + "... uncertain consequences\n"); } else { edgeInfo20.m_face1 = faceInfo; bFaceUpdated[2] = true; } } if (bMightAlreadyExist) { if (!alreadyExists(faceInfo, faceInfos)) faceInfos.add(faceInfo); else { //cleanup pointers that point to this deleted face if (bFaceUpdated[0]) edgeInfo01.m_face1 = null; if (bFaceUpdated[1]) edgeInfo12.m_face1 = null; if (bFaceUpdated[2]) edgeInfo20.m_face1 = null; } } else { faceInfos.add(faceInfo); } } } static boolean isDegenerate(FaceInfo face) { if (face.m_v0 == face.m_v1) return true; else if (face.m_v0 == face.m_v2) return true; else if (face.m_v1 == face.m_v2) return true; else return false; } static boolean isDegenerate(int v0, int v1, int v2) { if (v0 == v1) return true; else if (v0 == v2) return true; else if (v1 == v2) return true; else return false; } /////////////////////////////////////////////////////////////////////////////////////////// // GetNextIndex() // // Returns vertex of the input face which is "next" in the input index list // static int getNextIndex(IntVec indices, FaceInfo face) { int numIndices = indices.size(); int v0 = indices.get(numIndices - 2); int v1 = indices.get(numIndices - 1); int fv0 = face.m_v0; int fv1 = face.m_v1; int fv2 = face.m_v2; if (fv0 != v0 && fv0 != v1) { if ((fv1 != v0 && fv1 != v1) || (fv2 != v0 && fv2 != v1)) { logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n"); logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n"); } return fv0; } if (fv1 != v0 && fv1 != v1) { if ((fv0 != v0 && fv0 != v1) || (fv2 != v0 && fv2 != v1)) { logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n"); logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n"); } return fv1; } if (fv2 != v0 && fv2 != v1) { if ((fv0 != v0 && fv0 != v1) || (fv1 != v0 && fv1 != v1)) { logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n"); logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n"); } return fv2; } // shouldn't get here, but let's try and fail gracefully if ((fv0 == fv1) || (fv0 == fv2)) return fv0; else if ((fv1 == fv0) || (fv1 == fv2)) return fv1; else if ((fv2 == fv0) || (fv2 == fv1)) return fv2; else return -1; } /////////////////////////////////////////////////////////////////////////////////////////// // FindStartPoint() // // Finds a good starting point, namely one which has only one neighbor // static int findStartPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) { int bestCtr = -1; int bestIndex = -1; for (int i = 0; i < faceInfos.size(); i++) { int ctr = 0; if (findOtherFace(edgeInfos, faceInfos.at(i).m_v0, faceInfos.at(i).m_v1, faceInfos.at(i)) == null) ctr++; if (findOtherFace(edgeInfos, faceInfos.at(i).m_v1, faceInfos.at(i).m_v2, faceInfos.at(i)) == null) ctr++; if (findOtherFace(edgeInfos, faceInfos.at(i).m_v2, faceInfos.at(i).m_v0, faceInfos.at(i)) == null) ctr++; if (ctr > bestCtr) { bestCtr = ctr; bestIndex = i; //return i; } } //return -1; if (bestCtr == 0) return -1; return bestIndex; } /////////////////////////////////////////////////////////////////////////////////////////// // FindGoodResetPoint() // // A good reset point is one near other commited areas so that // we know that when we've made the longest strips its because // we're stripifying in the same general orientation. // FaceInfo findGoodResetPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) { // we hop into different areas of the mesh to try to get // other large open spans done. Areas of small strips can // just be left to triangle lists added at the end. FaceInfo result = null; if (result == null) { int numFaces = faceInfos.size(); int startPoint; if (bFirstTimeResetPoint) { //first time, find a face with few neighbors (look for an edge // of the mesh) startPoint = findStartPoint(faceInfos, edgeInfos); bFirstTimeResetPoint = false; } else startPoint = (int) (((float) numFaces - 1) * meshJump); if (startPoint == -1) { startPoint = (int) (((float) numFaces - 1) * meshJump); //meshJump += 0.1f; //if (meshJump > 1.0f) // meshJump = .05f; } int i = startPoint; do { // if this guy isn't visited, try him if (faceInfos.at(i).m_stripId < 0) { result = faceInfos.at(i); break; } // update the index and clamp to 0-(numFaces-1) if (++i >= numFaces) i = 0; } while (i != startPoint); // update the meshJump meshJump += 0.1f; if (meshJump > 1.0f) meshJump = .05f; } // return the best face we found return result; } /////////////////////////////////////////////////////////////////////////////////////////// // GetUniqueVertexInB() // // Returns the vertex unique to faceB // static int getUniqueVertexInB(FaceInfo faceA, FaceInfo faceB) { int facev0 = faceB.m_v0; if (facev0 != faceA.m_v0 && facev0 != faceA.m_v1 && facev0 != faceA.m_v2) return facev0; int facev1 = faceB.m_v1; if (facev1 != faceA.m_v0 && facev1 != faceA.m_v1 && facev1 != faceA.m_v2) return facev1; int facev2 = faceB.m_v2; if (facev2 != faceA.m_v0 && facev2 != faceA.m_v1 && facev2 != faceA.m_v2) return facev2; // nothing is different return -1; } /////////////////////////////////////////////////////////////////////////////////////////// // GetSharedVertices() // // Returns the (at most) two vertices shared between the two faces // static void getSharedVertices(FaceInfo faceA, FaceInfo faceB, int[] vertex) { vertex[0] = -1; vertex[1] = -1; int facev0 = faceB.m_v0; if (facev0 == faceA.m_v0 || facev0 == faceA.m_v1 || facev0 == faceA.m_v2) { if (vertex[0] == -1) vertex[0] = facev0; else { vertex[1] = facev0; return; } } int facev1 = faceB.m_v1; if (facev1 == faceA.m_v0 || facev1 == faceA.m_v1 || facev1 == faceA.m_v2) { if (vertex[0] == -1) vertex[0] = facev1; else { vertex[1] = facev1; return; } } int facev2 = faceB.m_v2; if (facev2 == faceA.m_v0 || facev2 == faceA.m_v1 || facev2 == faceA.m_v2) { if (vertex[0] == -1) vertex[0] = facev2; else { vertex[1] = facev2; return; } } } /////////////////////////////////////////////////////////////////////////////////////////// // CommitStrips() // // "Commits" the input strips by setting their m_experimentId to -1 and // adding to the allStrips // vector // static void commitStrips(StripInfoVec allStrips, StripInfoVec strips) { // Iterate through strips int numStrips = strips.size(); for (int i = 0; i < numStrips; i++) { // Tell the strip that it is now real StripInfo strip = strips.at(i); strip.m_experimentId = -1; // add to the list of real strips allStrips.add(strip); // Iterate through the faces of the strip // Tell the faces of the strip that they belong to a real strip now FaceInfoVec faces = strips.at(i).m_faces; int numFaces = faces.size(); for (int j = 0; j < numFaces; j++) { strip.markTriangle(faces.at(j)); } } } /////////////////////////////////////////////////////////////////////////////////////////// // NextIsCW() // // Returns true if the next face should be ordered in CW fashion // static boolean nextIsCW(int numIndices) { return ((numIndices % 2) == 0); } /////////////////////////////////////////////////////////////////////////////////////////// // UpdateCacheFace() // // Updates the input vertex cache with this face's vertices // static void updateCacheFace(VertexCache vcache, FaceInfo face) { if (!vcache.inCache(face.m_v0)) vcache.addEntry(face.m_v0); if (!vcache.inCache(face.m_v1)) vcache.addEntry(face.m_v1); if (!vcache.inCache(face.m_v2)) vcache.addEntry(face.m_v2); } /////////////////////////////////////////////////////////////////////////////////////////// // UpdateCacheStrip() // // Updates the input vertex cache with this strip's vertices // static void updateCacheStrip(VertexCache vcache, StripInfo strip) { for (int i = 0; i < strip.m_faces.size(); ++i) { if (!vcache.inCache(strip.m_faces.at(i).m_v0)) vcache.addEntry(strip.m_faces.at(i).m_v0); if (!vcache.inCache(strip.m_faces.at(i).m_v1)) vcache.addEntry(strip.m_faces.at(i).m_v1); if (!vcache.inCache(strip.m_faces.at(i).m_v2)) vcache.addEntry(strip.m_faces.at(i).m_v2); } } /////////////////////////////////////////////////////////////////////////////////////////// // CalcNumHitsStrip() // // returns the number of cache hits per face in the strip // static float calcNumHitsStrip(VertexCache vcache, StripInfo strip) { int numHits = 0; int numFaces = 0; for (int i = 0; i < strip.m_faces.size(); i++) { if (vcache.inCache(strip.m_faces.at(i).m_v0)) ++numHits; if (vcache.inCache(strip.m_faces.at(i).m_v1)) ++numHits; if (vcache.inCache(strip.m_faces.at(i).m_v2)) ++numHits; numFaces++; } return ((float) numHits / (float) numFaces); } /////////////////////////////////////////////////////////////////////////////////////////// // AvgStripSize() // // Finds the average strip size of the input vector of strips // static float avgStripSize(StripInfoVec strips) { int sizeAccum = 0; int numStrips = strips.size(); for (int i = 0; i < numStrips; i++) { StripInfo strip = strips.at(i); sizeAccum += strip.m_faces.size(); sizeAccum -= strip.m_numDegenerates; } return ((float) sizeAccum) / ((float) numStrips); } /////////////////////////////////////////////////////////////////////////////////////////// // CalcNumHitsFace() // // returns the number of cache hits in the face // static int calcNumHitsFace(VertexCache vcache, FaceInfo face) { int numHits = 0; if (vcache.inCache(face.m_v0)) numHits++; if (vcache.inCache(face.m_v1)) numHits++; if (vcache.inCache(face.m_v2)) numHits++; return numHits; } /////////////////////////////////////////////////////////////////////////////////////////// // NumNeighbors() // // Returns the number of neighbors that this face has // static int numNeighbors(FaceInfo face, EdgeInfoVec edgeInfoVec) { int numNeighbors = 0; if (findOtherFace(edgeInfoVec, face.m_v0, face.m_v1, face) != null) { numNeighbors++; } if (findOtherFace(edgeInfoVec, face.m_v1, face.m_v2, face) != null) { numNeighbors++; } if (findOtherFace(edgeInfoVec, face.m_v2, face.m_v0, face) != null) { numNeighbors++; } return numNeighbors; } /////////////////////////////////////////////////////////////////////////////////////////// // IsCW() // // Returns true if the face is ordered in CW fashion // static boolean isCW(FaceInfo faceInfo, int v0, int v1) { if (faceInfo.m_v0 == v0) return (faceInfo.m_v1 == v1); else if (faceInfo.m_v1 == v0) return (faceInfo.m_v2 == v1); else return (faceInfo.m_v0 == v1); } static boolean faceContainsIndex(FaceInfo face, int index) { return ((face.m_v0 == index) || (face.m_v1 == index) || (face.m_v2 == index)); } /////////////////////////////////////////////////////////////////////////////////////////// // FindTraversal() // // Finds the next face to start the next strip on. // static boolean findTraversal(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos, StripInfo strip, StripStartInfo startInfo) { // if the strip was v0.v1 on the edge, then v1 will be a vertex in the // next edge. int v = (strip.m_startInfo.m_toV1 ? strip.m_startInfo.m_startEdge.m_v1 : strip.m_startInfo.m_startEdge.m_v0); FaceInfo untouchedFace = null; EdgeInfo edgeIter = edgeInfos.at(v); while (edgeIter != null) { FaceInfo face0 = edgeIter.m_face0; FaceInfo face1 = edgeIter.m_face1; if ((face0 != null && !strip.isInStrip(face0)) && face1 != null && !strip.isMarked(face1)) { untouchedFace = face1; break; } if ((face1 != null && !strip.isInStrip(face1)) && face0 != null && !strip.isMarked(face0)) { untouchedFace = face0; break; } // find the next edgeIter edgeIter = (edgeIter.m_v0 == v ? edgeIter.m_nextV0 : edgeIter.m_nextV1); } startInfo.m_startFace = untouchedFace; startInfo.m_startEdge = edgeIter; if (edgeIter != null) { if (strip.sharesEdge(startInfo.m_startFace, edgeInfos)) startInfo.m_toV1 = (edgeIter.m_v0 == v); //note! used to be // m_v1 else startInfo.m_toV1 = (edgeIter.m_v1 == v); } return (startInfo.m_startFace != null); } //////////////////////////////////////////////////////////////////////////////////////// // RemoveSmallStrips() // // allStrips is the whole strip vector...all small strips will be deleted // from this list, to avoid leaking mem // allBigStrips is an out parameter which will contain all strips above // minStripLength // faceList is an out parameter which will contain all faces which were // removed from the striplist // void removeSmallStrips(StripInfoVec allStrips, StripInfoVec allBigStrips, FaceInfoVec faceList) { faceList.clear(); allBigStrips.clear(); //make sure these are empty FaceInfoVec tempFaceList = new FaceInfoVec(); for (int i = 0; i < allStrips.size(); i++) { if (allStrips.at(i).m_faces.size() < minStripLength) { //strip is too small, add faces to faceList for (int j = 0; j < allStrips.at(i).m_faces.size(); j++) tempFaceList.add(allStrips.at(i).m_faces.at(j)); } else { allBigStrips.add(allStrips.at(i)); } } boolean[] bVisitedList = new boolean[tempFaceList.size()]; VertexCache vcache = new VertexCache(cacheSize); int bestNumHits = -1; int numHits; int bestIndex = -9999; while (true) { bestNumHits = -1; //find best face to add next, given the current cache for (int i = 0; i < tempFaceList.size(); i++) { if (bVisitedList[i]) continue; numHits = calcNumHitsFace(vcache, tempFaceList.at(i)); if (numHits > bestNumHits) { bestNumHits = numHits; bestIndex = i; } } if (bestNumHits == -1.0f) break; bVisitedList[bestIndex] = true; updateCacheFace(vcache, tempFaceList.at(bestIndex)); faceList.add(tempFaceList.at(bestIndex)); } } //////////////////////////////////////////////////////////////////////////////////////// // CreateStrips() // // Generates actual strips from the list-in-strip-order. // int createStrips(StripInfoVec allStrips, IntVec stripIndices, boolean bStitchStrips) { int numSeparateStrips = 0; FaceInfo tLastFace = new FaceInfo(0, 0, 0); int nStripCount = allStrips.size(); //we infer the cw/ccw ordering depending on the number of indices //this is screwed up by the fact that we insert -1s to denote changing // strips //this is to account for that int accountForNegatives = 0; for (int i = 0; i < nStripCount; i++) { StripInfo strip = allStrips.at(i); int nStripFaceCount = strip.m_faces.size(); // Handle the first face in the strip { FaceInfo tFirstFace = new FaceInfo(strip.m_faces.at(0).m_v0, strip.m_faces.at(0).m_v1, strip.m_faces.at(0).m_v2); // If there is a second face, reorder vertices such that the // unique vertex is first if (nStripFaceCount > 1) { int nUnique = getUniqueVertexInB(strip.m_faces.at(1), tFirstFace); if (nUnique == tFirstFace.m_v1) { int tmp = tFirstFace.m_v0; tFirstFace.m_v0 = tFirstFace.m_v1; tFirstFace.m_v1 = tmp; } else if (nUnique == tFirstFace.m_v2) { int tmp = tFirstFace.m_v0; tFirstFace.m_v0 = tFirstFace.m_v2; tFirstFace.m_v2 = tmp; } // If there is a third face, reorder vertices such that the // shared vertex is last if (nStripFaceCount > 2) { if (isDegenerate(strip.m_faces.at(1))) { int pivot = strip.m_faces.at(1).m_v1; if (tFirstFace.m_v1 == pivot) { int tmp = tFirstFace.m_v1; tFirstFace.m_v1 = tFirstFace.m_v2; tFirstFace.m_v2 = tmp; } } else { int[] nShared = new int[2]; getSharedVertices(strip.m_faces.at(2), tFirstFace, nShared); if ((nShared[0] == tFirstFace.m_v1) && (nShared[1] == -1)) { int tmp = tFirstFace.m_v1; tFirstFace.m_v1 = tFirstFace.m_v2; tFirstFace.m_v2 = tmp; } } } } if ((i == 0) || !bStitchStrips) { if (!isCW(strip.m_faces.at(0), tFirstFace.m_v0, tFirstFace.m_v1)) stripIndices.add(tFirstFace.m_v0); } else { // Double tap the first in the new strip stripIndices.add(tFirstFace.m_v0); // Check CW/CCW ordering if (nextIsCW(stripIndices.size() - accountForNegatives) != isCW( strip.m_faces.at(0), tFirstFace.m_v0, tFirstFace.m_v1)) { stripIndices.add(tFirstFace.m_v0); } } stripIndices.add(tFirstFace.m_v0); stripIndices.add(tFirstFace.m_v1); stripIndices.add(tFirstFace.m_v2); // Update last face info tLastFace.set(tFirstFace); } for (int j = 1; j < nStripFaceCount; j++) { int nUnique = getUniqueVertexInB(tLastFace, strip.m_faces.at(j)); if (nUnique != -1) { stripIndices.add(nUnique); // Update last face info tLastFace.m_v0 = tLastFace.m_v1; tLastFace.m_v1 = tLastFace.m_v2; tLastFace.m_v2 = nUnique; } else { //we've hit a degenerate stripIndices.add(strip.m_faces.at(j).m_v2); tLastFace.m_v0 = strip.m_faces.at(j).m_v0; //tLastFace.m_v1; tLastFace.m_v1 = strip.m_faces.at(j).m_v1; //tLastFace.m_v2; tLastFace.m_v2 = strip.m_faces.at(j).m_v2; //tLastFace.m_v1; } } // Double tap between strips. if (bStitchStrips) { if (i != nStripCount - 1) stripIndices.add(tLastFace.m_v2); } else { //-1 index indicates next strip stripIndices.add(-1); accountForNegatives++; numSeparateStrips++; } // Update last face info tLastFace.m_v0 = tLastFace.m_v1; tLastFace.m_v1 = tLastFace.m_v2; tLastFace.m_v2 = tLastFace.m_v2; } if (bStitchStrips) numSeparateStrips = 1; return numSeparateStrips; } /////////////////////////////////////////////////////////////////////////////////////////// // FindAllStrips() // // Does the stripification, puts output strips into vector allStrips // // Works by setting runnning a number of experiments in different areas of // the mesh, and // accepting the one which results in the longest strips. It then accepts // this, and moves // on to a different area of the mesh. We try to jump around the mesh some, // to ensure that // large open spans of strips get generated. // void findAllStrips(StripInfoVec allStrips, FaceInfoVec allFaceInfos, EdgeInfoVec allEdgeInfos, int numSamples) { // the experiments int experimentId = 0; int stripId = 0; boolean done = false; int loopCtr = 0; while (!done) { loopCtr++; // // PHASE 1: Set up numSamples * numEdges experiments // StripInfoVec[] experiments = new StripInfoVec[numSamples * 6]; for (int i = 0; i < experiments.length; i++) experiments[i] = new StripInfoVec(); int experimentIndex = 0; HashSet resetPoints = new HashSet(); /* NvFaceInfo */ for (int i = 0; i < numSamples; i++) { // Try to find another good reset point. // If there are none to be found, we are done FaceInfo nextFace = findGoodResetPoint(allFaceInfos, allEdgeInfos); if (nextFace == null) { done = true; break; } // If we have already evaluated starting at this face in this // slew of experiments, then skip going any further else if (resetPoints.contains(nextFace)) { continue; } // trying it now... resetPoints.add(nextFace); // otherwise, we shall now try experiments for starting on the // 01,12, and 20 edges // build the strip off of this face's 0-1 edge EdgeInfo edge01 = findEdgeInfo(allEdgeInfos, nextFace.m_v0, nextFace.m_v1); StripInfo strip01 = new StripInfo(new StripStartInfo(nextFace, edge01, true), stripId++, experimentId++); experiments[experimentIndex++].add(strip01); // build the strip off of this face's 1-0 edge EdgeInfo edge10 = findEdgeInfo(allEdgeInfos, nextFace.m_v0, nextFace.m_v1); StripInfo strip10 = new StripInfo(new StripStartInfo(nextFace, edge10, false), stripId++, experimentId++); experiments[experimentIndex++].add(strip10); // build the strip off of this face's 1-2 edge EdgeInfo edge12 = findEdgeInfo(allEdgeInfos, nextFace.m_v1, nextFace.m_v2); StripInfo strip12 = new StripInfo(new StripStartInfo(nextFace, edge12, true), stripId++, experimentId++); experiments[experimentIndex++].add(strip12); // build the strip off of this face's 2-1 edge EdgeInfo edge21 = findEdgeInfo(allEdgeInfos, nextFace.m_v1, nextFace.m_v2); StripInfo strip21 = new StripInfo(new StripStartInfo(nextFace, edge21, false), stripId++, experimentId++); experiments[experimentIndex++].add(strip21); // build the strip off of this face's 2-0 edge EdgeInfo edge20 = findEdgeInfo(allEdgeInfos, nextFace.m_v2, nextFace.m_v0); StripInfo strip20 = new StripInfo(new StripStartInfo(nextFace, edge20, true), stripId++, experimentId++); experiments[experimentIndex++].add(strip20); // build the strip off of this face's 0-2 edge EdgeInfo edge02 = findEdgeInfo(allEdgeInfos, nextFace.m_v2, nextFace.m_v0); StripInfo strip02 = new StripInfo(new StripStartInfo(nextFace, edge02, false), stripId++, experimentId++); experiments[experimentIndex++].add(strip02); } // // PHASE 2: Iterate through that we setup in the last phase // and really build each of the strips and strips that follow to // see how // far we get // int numExperiments = experimentIndex; for (int i = 0; i < numExperiments; i++) { // get the strip set // build the first strip of the list experiments[i].at(0).build(allEdgeInfos, allFaceInfos); int experimentId2 = experiments[i].at(0).m_experimentId; StripInfo stripIter = experiments[i].at(0); StripStartInfo startInfo = new StripStartInfo(null, null, false); while (findTraversal(allFaceInfos, allEdgeInfos, stripIter, startInfo)) { // create the new strip info //TODO startInfo clone ? stripIter = new StripInfo(startInfo, stripId++, experimentId2); // build the next strip stripIter.build(allEdgeInfos, allFaceInfos); // add it to the list experiments[i].add(stripIter); } } // // Phase 3: Find the experiment that has the most promise // int bestIndex = 0; double bestValue = 0; for (int i = 0; i < numExperiments; i++) { float avgStripSizeWeight = 1.0f; //float numTrisWeight = 0.0f; float numStripsWeight = 0.0f; float avgStripSize = avgStripSize(experiments[i]); float numStrips = experiments[i].size(); float value = avgStripSize * avgStripSizeWeight + (numStrips * numStripsWeight); //float value = 1.f / numStrips; //float value = numStrips * avgStripSize; if (value > bestValue) { bestValue = value; bestIndex = i; } } // // Phase 4: commit the best experiment of the bunch // commitStrips(allStrips, experiments[bestIndex]); } } /////////////////////////////////////////////////////////////////////////////////////////// // SplitUpStripsAndOptimize() // // Splits the input vector of strips (allBigStrips) into smaller, cache // friendly pieces, then // reorders these pieces to maximize cache hits // The final strips are output through outStrips // void splitUpStripsAndOptimize(StripInfoVec allStrips, StripInfoVec outStrips, EdgeInfoVec edgeInfos, FaceInfoVec outFaceList) { int threshold = cacheSize; StripInfoVec tempStrips = new StripInfoVec(); int j; //split up strips into threshold-sized pieces for (int i = 0; i < allStrips.size(); i++) { StripInfo currentStrip; StripStartInfo startInfo = new StripStartInfo(null, null, false); int actualStripSize = 0; for (j = 0; j < allStrips.at(i).m_faces.size(); ++j) { if (!isDegenerate(allStrips.at(i).m_faces.at(j))) actualStripSize++; } if (actualStripSize /* allStrips.at(i).m_faces.size() */ > threshold) { int numTimes = actualStripSize /* allStrips.at(i).m_faces.size() */ / threshold; int numLeftover = actualStripSize /* allStrips.at(i).m_faces.size() */ % threshold; int degenerateCount = 0; for (j = 0; j < numTimes; j++) { currentStrip = new StripInfo(startInfo, 0, -1); int faceCtr = j * threshold + degenerateCount; boolean bFirstTime = true; while (faceCtr < threshold + (j * threshold) + degenerateCount) { if (isDegenerate(allStrips.at(i).m_faces.at(faceCtr))) { degenerateCount++; //last time or first time through, no need for a // degenerate if ((((faceCtr + 1) != threshold + (j * threshold) + degenerateCount) || ((j == numTimes - 1) && (numLeftover < 4) && (numLeftover > 0))) && !bFirstTime) { currentStrip.m_faces .add(allStrips.at(i).m_faces .at(faceCtr++)); } else ++faceCtr; } else { currentStrip.m_faces.add(allStrips.at(i).m_faces .at(faceCtr++)); bFirstTime = false; } } /* * threshold; faceCtr < threshold+(j*threshold); faceCtr++) { * currentStrip.m_faces.add(allStrips.at(i).m_faces.at(faceCtr]); } */ ///* if (j == numTimes - 1) //last time through { if ((numLeftover < 4) && (numLeftover > 0)) //way too // small { //just add to last strip int ctr = 0; while (ctr < numLeftover) { if (!isDegenerate(allStrips.at(i).m_faces .at(faceCtr))) { currentStrip.m_faces .add(allStrips.at(i).m_faces .at(faceCtr++)); ++ctr; } else { currentStrip.m_faces .add(allStrips.at(i).m_faces .at(faceCtr++)); ++degenerateCount; } } numLeftover = 0; } } //*/ tempStrips.add(currentStrip); } int leftOff = j * threshold + degenerateCount; if (numLeftover != 0) { currentStrip = new StripInfo(startInfo, 0, -1); int ctr = 0; boolean bFirstTime = true; while (ctr < numLeftover) { if (!isDegenerate(allStrips.at(i).m_faces.at(leftOff))) { ctr++; bFirstTime = false; currentStrip.m_faces.add(allStrips.at(i).m_faces .at(leftOff++)); } else if (!bFirstTime) currentStrip.m_faces.add(allStrips.at(i).m_faces .at(leftOff++)); else leftOff++; } /* * for(int k = 0; k < numLeftover; k++) { * currentStrip.m_faces.add(allStrips.at(i).m_faces[leftOff++]); } */ tempStrips.add(currentStrip); } } else { //we're not just doing a tempStrips.add(allBigStrips[i]) // because // this way we can delete allBigStrips later to free the memory currentStrip = new StripInfo(startInfo, 0, -1); for (j = 0; j < allStrips.at(i).m_faces.size(); j++) currentStrip.m_faces.add(allStrips.at(i).m_faces.at(j)); tempStrips.add(currentStrip); } } //add small strips to face list StripInfoVec tempStrips2 = new StripInfoVec(); removeSmallStrips(tempStrips, tempStrips2, outFaceList); outStrips.clear(); //screw optimization for now // for(i = 0; i < tempStrips.size(); ++i) // outStrips.add(tempStrips[i]); if (tempStrips2.size() != 0) { //Optimize for the vertex cache VertexCache vcache = new VertexCache(cacheSize); float bestNumHits = -1.0f; float numHits; int bestIndex = -99999; int firstIndex = 0; float minCost = 10000.0f; for (int i = 0; i < tempStrips2.size(); i++) { int numNeighbors = 0; //find strip with least number of neighbors per face for (j = 0; j < tempStrips2.at(i).m_faces.size(); j++) { numNeighbors += numNeighbors(tempStrips2.at(i).m_faces .at(j), edgeInfos); } float currCost = (float) numNeighbors / (float) tempStrips2.at(i).m_faces.size(); if (currCost < minCost) { minCost = currCost; firstIndex = i; } } updateCacheStrip(vcache, tempStrips2.at(firstIndex)); outStrips.add(tempStrips2.at(firstIndex)); tempStrips2.at(firstIndex).visited = true; boolean bWantsCW = (tempStrips2.at(firstIndex).m_faces.size() % 2) == 0; //this n^2 algo is what slows down stripification so much.... // needs to be improved while (true) { bestNumHits = -1.0f; //find best strip to add next, given the current cache for (int i = 0; i < tempStrips2.size(); i++) { if (tempStrips2.at(i).visited) continue; numHits = calcNumHitsStrip(vcache, tempStrips2.at(i)); if (numHits > bestNumHits) { bestNumHits = numHits; bestIndex = i; } else if (numHits >= bestNumHits) { //check previous strip to see if this one requires it // to switch polarity StripInfo strip = tempStrips2.at(i); int nStripFaceCount = strip.m_faces.size(); FaceInfo tFirstFace = new FaceInfo( strip.m_faces.at(0).m_v0, strip.m_faces.at(0).m_v1, strip.m_faces.at(0).m_v2); // If there is a second face, reorder vertices such // that the // unique vertex is first if (nStripFaceCount > 1) { int nUnique = getUniqueVertexInB(strip.m_faces .at(1), tFirstFace); if (nUnique == tFirstFace.m_v1) { int tmp = tFirstFace.m_v0; tFirstFace.m_v0 = tFirstFace.m_v1; tFirstFace.m_v1 = tmp; } else if (nUnique == tFirstFace.m_v2) { int tmp = tFirstFace.m_v0; tFirstFace.m_v0 = tFirstFace.m_v2; tFirstFace.m_v2 = tmp; } // If there is a third face, reorder vertices such // that the // shared vertex is last if (nStripFaceCount > 2) { int[] nShared = new int[2]; getSharedVertices(strip.m_faces.at(2), tFirstFace, nShared); if ((nShared[0] == tFirstFace.m_v1) && (nShared[1] == -1)) { int tmp = tFirstFace.m_v2; tFirstFace.m_v2 = tFirstFace.m_v1; tFirstFace.m_v1 = tmp; } } } // Check CW/CCW ordering if (bWantsCW == isCW(strip.m_faces.at(0), tFirstFace.m_v0, tFirstFace.m_v1)) { //I like this one! bestIndex = i; } } } if (bestNumHits == -1.0f) break; tempStrips2.at(bestIndex).visited = true; updateCacheStrip(vcache, tempStrips2.at(bestIndex)); outStrips.add(tempStrips2.at(bestIndex)); bWantsCW = (tempStrips2.at(bestIndex).m_faces.size() % 2 == 0) ? bWantsCW : !bWantsCW; } } } /////////////////////////////////////////////////////////////////////////////////////////// // Stripify() // // // in_indices are the input indices of the mesh to stripify // in_cacheSize is the target cache size // void stripify(IntVec in_indices, int in_cacheSize, int in_minStripLength, int maxIndex, StripInfoVec outStrips, FaceInfoVec outFaceList) { meshJump = 0.0f; bFirstTimeResetPoint = true; //used in FindGoodResetPoint() //the number of times to run the experiments int numSamples = 10; //the cache size, clamped to one cacheSize = Math.max(1, in_cacheSize - CACHE_INEFFICIENCY); minStripLength = in_minStripLength; //this is the strip size threshold below which we dump the strip into // a list indices = in_indices; // build the stripification info FaceInfoVec allFaceInfos = new FaceInfoVec(); EdgeInfoVec allEdgeInfos = new EdgeInfoVec(); buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex); StripInfoVec allStrips = new StripInfoVec(); // stripify findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples); //split up the strips into cache friendly pieces, optimize them, then // dump these into outStrips splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos, outFaceList); } }