/* | |
* 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<FaceInfo> resetPoints = new HashSet<FaceInfo>(); /* 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); | |
} | |
} |