/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* 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 the 'Xith3D Project Group' 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) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.utility.geometry.nvtristrip;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Vector;
/**
* @author YVG
*/
class Stripifier
{
public static int CACHE_INEFFICIENCY = 6;
private Vector< Integer > indices = new Vector< Integer >();
private int cacheSize;
private int minStripLength;
private float meshJump;
private boolean bFirstTimeResetPoint;
Stripifier()
{
super();
}
/**
* Finds the edge info for these two indices.
*
* @param edgeInfos
* @param v0
* @param v1
*
* @return the edge info for these two indices
*/
static EdgeInfo findEdgeInfo( ArrayList< EdgeInfo > 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.get( v0 );
while ( infoIter != null )
{
if ( infoIter.m_v0 == v0 )
{
if ( infoIter.m_v1 == v1 )
return ( infoIter );
infoIter = infoIter.m_nextV0;
}
else
{
assert ( infoIter.m_v1 == v0 );
if ( infoIter.m_v0 == v1 )
return ( infoIter );
infoIter = infoIter.m_nextV1;
}
}
return ( null );
}
/**
* Finds the other face sharing these vertices exactly like the edge info above.
*
* @param edgeInfos
* @param v0
* @param v1
* @param faceInfo
*
* @return the other face sharing these vertices
*/
static FaceInfo findOtherFace( ArrayList< EdgeInfo > 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;
}
assert ( edgeInfo != null );
return ( edgeInfo.m_face0 == faceInfo ? edgeInfo.m_face1 : edgeInfo.m_face0 );
}
static boolean alreadyExists( FaceInfo faceInfo, ArrayList< FaceInfo > faceInfos )
{
for ( int i = 0; i < faceInfos.size(); ++i )
{
FaceInfo o = faceInfos.get( i );
if ( ( o.m_v0 == faceInfo.m_v0 ) && ( o.m_v1 == faceInfo.m_v1 ) && ( o.m_v2 == faceInfo.m_v2 ) )
return true;
}
return false;
}
/**
* Builds the list of all face and edge infos.
*
* @param faceInfos
* @param edgeInfos
* @param maxIndex
*/
void buildStripifyInfo( ArrayList< FaceInfo > faceInfos, ArrayList< EdgeInfo > edgeInfos, int maxIndex )
{
// reserve space for the face infos, but do not resize them.
int numIndices = indices.size();
faceInfos.ensureCapacity( 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.get( v0 );
edgeInfo01.m_nextV1 = edgeInfos.get( v1 );
edgeInfos.set( v0, edgeInfo01 );
edgeInfos.set( v1, edgeInfo01 );
// set face 0
edgeInfo01.m_face0 = faceInfo;
}
else
{
if ( edgeInfo01.m_face1 != null )
{
System.out.println( "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.get( v1 );
edgeInfo12.m_nextV1 = edgeInfos.get( v2 );
edgeInfos.set( v1, edgeInfo12 );
edgeInfos.set( v2, edgeInfo12 );
// set face 0
edgeInfo12.m_face0 = faceInfo;
}
else
{
if ( edgeInfo12.m_face1 != null )
{
System.out.println( "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.get( v2 );
edgeInfo20.m_nextV1 = edgeInfos.get( v0 );
edgeInfos.set( v2, edgeInfo20 );
edgeInfos.set( v0, edgeInfo20 );
// set face 0
edgeInfo20.m_face0 = faceInfo;
}
else
{
if ( edgeInfo20.m_face1 != null )
{
System.out.println( "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;
}
/**
* @param indices
* @param face
* @return vertex of the input face which is "next" in the input index list
*/
static int getNextIndex( Vector< Integer > indices, FaceInfo face )
{
int numIndices = indices.size();
assert ( numIndices >= 2 );
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 ) )
{
System.out.println( "GetNextIndex: Triangle doesn't have all of its vertices\n" );
System.out.println( "GetNextIndex: Duplicate triangle probably got us derailed\n" );
}
return fv0;
}
if ( fv1 != v0 && fv1 != v1 )
{
if ( ( fv0 != v0 && fv0 != v1 ) || ( fv2 != v0 && fv2 != v1 ) )
{
System.out.println( "GetNextIndex: Triangle doesn't have all of its vertices\n" );
System.out.println( "GetNextIndex: Duplicate triangle probably got us derailed\n" );
}
return fv1;
}
if ( fv2 != v0 && fv2 != v1 )
{
if ( ( fv0 != v0 && fv0 != v1 ) || ( fv1 != v0 && fv1 != v1 ) )
{
System.out.println( "GetNextIndex: Triangle doesn't have all of its vertices\n" );
System.out.println( "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;
}
/**
* Finds a good starting point, namely one which has only one neighbor.
*
* @param faceInfos
* @param edgeInfos
*
* @return a good starting point
*/
static int findStartPoint( ArrayList< FaceInfo > faceInfos, ArrayList< EdgeInfo > edgeInfos )
{
int bestCtr = -1;
int bestIndex = -1;
for ( int i = 0; i < faceInfos.size(); i++ )
{
int ctr = 0;
if ( findOtherFace( edgeInfos, faceInfos.get( i ).m_v0, faceInfos.get( i ).m_v1, faceInfos.get( i ) ) == null )
ctr++;
if ( findOtherFace( edgeInfos, faceInfos.get( i ).m_v1, faceInfos.get( i ).m_v2, faceInfos.get( i ) ) == null )
ctr++;
if ( findOtherFace( edgeInfos, faceInfos.get( i ).m_v2, faceInfos.get( i ).m_v0, faceInfos.get( i ) ) == null )
ctr++;
if ( ctr > bestCtr )
{
bestCtr = ctr;
bestIndex = i;
//return i;
}
}
//return -1;
if ( bestCtr == 0 )
return ( -1 );
return bestIndex;
}
/**
* 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.
*
* @param faceInfos
* @param edgeInfos
*/
FaceInfo findGoodResetPoint( ArrayList< FaceInfo > faceInfos, ArrayList< EdgeInfo > 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.get( i ).m_stripId < 0 )
{
result = faceInfos.get( 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;
}
/**
* @param faceA
* @param faceB
*
* @return 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;
}
/**
* Retrieves (at most) the two vertices shared between the two faces and
* writes them into <b>vertex</b>.
*
* @param faceA
* @param faceB
* @param vertex
*/
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;
}
}
}
/**
* "Commits" the input strips by setting their m_experimentId to -1 and
* adding to the allStrips vector.
*
* @param allStrips
* @param strips
*/
static void commitStrips( ArrayList< StripInfo > allStrips, ArrayList< StripInfo > 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.get( 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
ArrayList< FaceInfo > faces = strips.get( i ).m_faces;
int numFaces = faces.size();
for ( int j = 0; j < numFaces; j++ )
{
strip.markTriangle( faces.get( j ) );
}
}
}
/**
* @param numIndices
*
* @return true if the next face should be ordered in CW fashion
*/
static boolean nextIsCW( int numIndices )
{
return ( ( numIndices % 2 ) == 0 );
}
/**
* Updates the input vertex cache with this face's vertices.
*
* @param vcache
* @param face
*/
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 );
}
/**
* Updates the input vertex cache with this strip's vertices.
*
* @param vcache
* @param strip
*/
static void updateCacheStrip( VertexCache vcache, StripInfo strip )
{
for ( int i = 0; i < strip.m_faces.size(); ++i )
{
if ( !vcache.inCache( strip.m_faces.get( i ).m_v0 ) )
vcache.addEntry( strip.m_faces.get( i ).m_v0 );
if ( !vcache.inCache( strip.m_faces.get( i ).m_v1 ) )
vcache.addEntry( strip.m_faces.get( i ).m_v1 );
if ( !vcache.inCache( strip.m_faces.get( i ).m_v2 ) )
vcache.addEntry( strip.m_faces.get( i ).m_v2 );
}
}
/**
* @param vcache
* @param strip
*
* @return 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.get( i ).m_v0 ) )
++numHits;
if ( vcache.inCache( strip.m_faces.get( i ).m_v1 ) )
++numHits;
if ( vcache.inCache( strip.m_faces.get( i ).m_v2 ) )
++numHits;
numFaces++;
}
return ( (float)numHits / (float)numFaces );
}
/**
* Finds the average strip size of the input vector of strips.
* @param strips
*
* @return the average strip size of the input vector of strips
*/
static float avgStripSize( ArrayList< StripInfo > strips )
{
int sizeAccum = 0;
int numStrips = strips.size();
for ( int i = 0; i < numStrips; i++ )
{
StripInfo strip = strips.get( i );
sizeAccum += strip.m_faces.size();
sizeAccum -= strip.m_numDegenerates;
}
return ( (float)sizeAccum ) / ( (float)numStrips );
}
/**
* @param vcache
* @param face
* @return 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;
}
/**
* @param face
* @param edgeInfoVec
* @return the number of neighbors that this face has
*/
static int numNeighbors( FaceInfo face, ArrayList< EdgeInfo > 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;
}
/**
* @param faceInfo
* @param v0
* @param v1
*
* @return 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 ) );
}
/**
* Finds the next face to start the next strip on.
*
* @param faceInfos
* @param edgeInfos
* @param strip
* @param startInfo
*
* @return the next face to start the next strip on
*/
static boolean findTraversal( ArrayList< FaceInfo > faceInfos, ArrayList< EdgeInfo > 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.get( 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 );
}
/**
* @param allStrips the whole strip vector...all small strips will be deleted
* from this list, to avoid leaking mem
* @param allBigStrips an out parameter which will contain all strips above minStripLength
* @param faceList an out parameter which will contain all faces which were faceList is
* removed from the striplist
*/
void removeSmallStrips( ArrayList< StripInfo > allStrips, ArrayList< StripInfo > allBigStrips, ArrayList< FaceInfo > faceList )
{
faceList.clear();
allBigStrips.clear(); //make sure these are empty
ArrayList< FaceInfo > tempFaceList = new ArrayList< FaceInfo >();
for ( int i = 0; i < allStrips.size(); i++ )
{
if ( allStrips.get( i ).m_faces.size() < minStripLength )
{
//strip is too small, add faces to faceList
for ( int j = 0; j < allStrips.get( i ).m_faces.size(); j++ )
tempFaceList.add( allStrips.get( i ).m_faces.get( j ) );
}
else
{
allBigStrips.add( allStrips.get( 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.get( i ) );
if ( numHits > bestNumHits )
{
bestNumHits = numHits;
bestIndex = i;
}
}
if ( bestNumHits == -1.0f )
break;
bVisitedList[ bestIndex ] = true;
updateCacheFace( vcache, tempFaceList.get( bestIndex ) );
faceList.add( tempFaceList.get( bestIndex ) );
}
}
/**
* Generates actual strips from the list-in-strip-order.
*
* @param allStrips
* @param stripIndices
* @param bStitchStrips
*/
int createStrips( ArrayList< StripInfo > allStrips, Vector< Integer > stripIndices, boolean bStitchStrips )
{
int numSeparateStrips = 0;
FaceInfo tLastFace = new FaceInfo( 0, 0, 0 );
int nStripCount = allStrips.size();
assert ( nStripCount > 0 );
//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.get( i );
int nStripFaceCount = strip.m_faces.size();
assert ( nStripFaceCount > 0 );
// Handle the first face in the strip
{
FaceInfo tFirstFace = new FaceInfo( strip.m_faces.get( 0 ).m_v0, strip.m_faces.get( 0 ).m_v1, strip.m_faces.get( 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.get( 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.get( 1 ) ) )
{
int pivot = strip.m_faces.get( 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.get( 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.get( 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.get( 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.get( 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.get( j ).m_v2 );
tLastFace.m_v0 = strip.m_faces.get( j ).m_v0; //tLastFace.m_v1;
tLastFace.m_v1 = strip.m_faces.get( j ).m_v1; //tLastFace.m_v2;
tLastFace.m_v2 = strip.m_faces.get( 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;
}
/**
* 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.
*
* @param allStrips
* @param allFaceInfos
* @param allEdgeInfos
* @param numSamples
*/
void findAllStrips( ArrayList< StripInfo > allStrips, ArrayList< FaceInfo > allFaceInfos, ArrayList< EdgeInfo > 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
//
ArrayList< ArrayList< StripInfo >> experiments = new ArrayList< ArrayList< StripInfo >>( numSamples * 6 );
for ( int i = 0; i < numSamples * 6; i++ )
experiments.add( new ArrayList< StripInfo >() );
int experimentIndex = 0;
HashSet< FaceInfo > resetPoints = new HashSet< FaceInfo >();
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
assert ( nextFace.m_stripId < 0 );
// 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.get( 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.get( 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.get( 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.get( 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.get( 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.get( 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.get( i ).get( 0 ).build( allEdgeInfos, allFaceInfos );
int experimentId2 = experiments.get( i ).get( 0 ).m_experimentId;
StripInfo stripIter = experiments.get( i ).get( 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.get( 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 numStripsWeight = 0.0f;
float avgStripSize = avgStripSize( experiments.get( i ) );
float numStrips = (float)experiments.get( 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.get( bestIndex ) );
}
}
/**
* 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.
*
* @param allStrips
* @param outStrips
* @param edgeInfos
* @param outFaceList
*/
void splitUpStripsAndOptimize( ArrayList< StripInfo > allStrips, ArrayList< StripInfo > outStrips, ArrayList< EdgeInfo > edgeInfos, ArrayList< FaceInfo > outFaceList )
{
int threshold = cacheSize;
ArrayList< StripInfo > tempStrips = new ArrayList< StripInfo >();
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.get( i ).m_faces.size(); ++j )
{
if ( !isDegenerate( allStrips.get( i ).m_faces.get( 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.get( i ).m_faces.get( 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.get( i ).m_faces.get( faceCtr++ ) );
}
else
++faceCtr;
}
else
{
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( 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.get( i ).m_faces.get( faceCtr ) ) )
{
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( faceCtr++ ) );
++ctr;
}
else
{
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( 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.get( i ).m_faces.get( leftOff ) ) )
{
ctr++;
bFirstTime = false;
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( leftOff++ ) );
}
else if ( !bFirstTime )
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( 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.get( i ).m_faces.size(); j++ )
currentStrip.m_faces.add( allStrips.get( i ).m_faces.get( j ) );
tempStrips.add( currentStrip );
}
}
//add small strips to face list
ArrayList< StripInfo > tempStrips2 = new ArrayList< StripInfo >();
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.get( i ).m_faces.size(); j++ )
{
numNeighbors += numNeighbors( tempStrips2.get( i ).m_faces.get( j ), edgeInfos );
}
float currCost = (float)numNeighbors / (float)tempStrips2.get( i ).m_faces.size();
if ( currCost < minCost )
{
minCost = currCost;
firstIndex = i;
}
}
updateCacheStrip( vcache, tempStrips2.get( firstIndex ) );
outStrips.add( tempStrips2.get( firstIndex ) );
tempStrips2.get( firstIndex ).visited = true;
boolean bWantsCW = ( tempStrips2.get( 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.get( i ).visited )
continue;
numHits = calcNumHitsStrip( vcache, tempStrips2.get( 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.get( i );
int nStripFaceCount = strip.m_faces.size();
FaceInfo tFirstFace = new FaceInfo( strip.m_faces.get( 0 ).m_v0, strip.m_faces.get( 0 ).m_v1, strip.m_faces.get( 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.get( 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.get( 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.get( 0 ), tFirstFace.m_v0, tFirstFace.m_v1 ) )
{
//I like this one!
bestIndex = i;
}
}
}
if ( bestNumHits == -1.0f )
break;
tempStrips2.get( bestIndex ).visited = true;
updateCacheStrip( vcache, tempStrips2.get( bestIndex ) );
outStrips.add( tempStrips2.get( bestIndex ) );
bWantsCW = ( tempStrips2.get( bestIndex ).m_faces.size() % 2 == 0 ) ? bWantsCW : !bWantsCW;
}
}
}
/**
* @param in_indices the input indices of the mesh to stripify
* @param in_cacheSize the target cache size.
* @param in_minStripLength
* @param maxIndex
* @param outStrips
* @param outFaceList
*/
void stripify( Vector< Integer > in_indices, int in_cacheSize, int in_minStripLength, int maxIndex, ArrayList< StripInfo > outStrips, ArrayList< FaceInfo > 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
ArrayList< FaceInfo > allFaceInfos = new ArrayList< FaceInfo >();
ArrayList< EdgeInfo > allEdgeInfos = new ArrayList< EdgeInfo >();
buildStripifyInfo( allFaceInfos, allEdgeInfos, maxIndex );
ArrayList< StripInfo > allStrips = new ArrayList< StripInfo >();
// 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 );
}
}