float ComputeEdgeCollapseCost( PMVertex src, PMVertex dest )
{
// if we collapse edge uv by moving src to dest then how
// much different will the model change, i.e. how much "error".
// The method of determining cost was designed in order
// to exploit small and coplanar regions for
// effective polygon reduction.
Vector3 edgeVector = src.position - dest.position;
float cost;
float curvature = 0.001f;
// find the "sides" triangles that are on the edge uv
List<PMTriangle> sides = new List<PMTriangle>();
// Iterate over src's faces and find 'sides' of the shared edge which is being collapsed
foreach ( PMTriangle srcFace in src.faces )
{
// Check if this tri also has dest in it (shared edge)
if ( srcFace.HasCommonVertex( dest ) )
sides.Add( srcFace );
}
// Special cases
// If we're looking at a border vertex
if ( src.IsBorder )
{
if ( sides.Count > 1 )
{
// src is on a border, but the src-dest edge has more than one tri on it
// So it must be collapsing inwards
// Mark as very high-value cost
// curvature = 1.0f;
cost = 1.0f;
}
else
{
// Collapsing ALONG a border
// We can't use curvature to measure the effect on the model
// Instead, see what effect it has on 'pulling' the other border edges
// The more colinear, the less effect it will have
// So measure the 'kinkiness' (for want of a better term)
// Normally there can be at most 1 other border edge attached to this
// However in weird cases there may be more, so find the worst
Vector3 collapseEdge, otherBorderEdge;
float kinkiness, maxKinkiness;
maxKinkiness = 0.0f;
edgeVector.Normalize();
collapseEdge = edgeVector;
foreach ( PMVertex neighbor in src.neighbors )
{
if ( neighbor != dest && neighbor.IsManifoldEdgeWith( src ) )
{
otherBorderEdge = src.position - neighbor.position;
otherBorderEdge.Normalize();
// This time, the nearer the dot is to -1, the better, because that means
// the edges are opposite each other, therefore less kinkiness
// Scale into [0..1]
kinkiness = ( otherBorderEdge.Dot( collapseEdge ) + 1.002f ) * 0.5f;
maxKinkiness = Utility.Max( kinkiness, maxKinkiness );
}
}
cost = maxKinkiness;
}
}
else // not a border
{
// Standard inner vertex
// Calculate curvature
// use the triangle facing most away from the sides
// to determine our curvature term
// Iterate over src's faces again
foreach ( PMTriangle srcFace in src.faces )
{
float mincurv = 1.0f; // curve for face i and closer side to it
// Iterate over the sides
foreach ( PMTriangle sideFace in sides )
{
// Dot product of face normal gives a good delta angle
float dotprod = srcFace.normal.Dot( sideFace.normal );
// NB we do (1-..) to invert curvature where 1 is high curvature [0..1]
// Whilst dot product is high when angle difference is low
mincurv = Utility.Min( mincurv, ( 1.002f - dotprod ) * 0.5f );
}
curvature = Utility.Max( curvature, mincurv );
}
cost = curvature;
}
// check for texture seam ripping
if ( src.seam && !dest.seam )
cost = 1.0f;
// Check for singular triangle destruction
// If src and dest both only have 1 triangle (and it must be a shared one)
// then this would destroy the shape, so don't do this
if ( src.faces.Count == 1 && dest.faces.Count == 1 )
cost = float.MaxValue;
// Degenerate case check
// Are we going to invert a face normal of one of the neighbouring faces?
// Can occur when we have a very small remaining edge and collapse crosses it
// Look for a face normal changing by > 90 degrees
foreach ( PMTriangle srcFace in src.faces )
{
// Ignore the deleted faces (those including src & dest)
if ( !srcFace.HasCommonVertex( dest ) )
{
// Test the new face normal
PMVertex v0, v1, v2;
// Replace src with dest wherever it is
v0 = ( srcFace.vertex[ 0 ].commonVertex == src ) ? dest : srcFace.vertex[ 0 ].commonVertex;
v1 = ( srcFace.vertex[ 1 ].commonVertex == src ) ? dest : srcFace.vertex[ 1 ].commonVertex;
v2 = ( srcFace.vertex[ 2 ].commonVertex == src ) ? dest : srcFace.vertex[ 2 ].commonVertex;
// Cross-product 2 edges
Vector3 e1 = v1.position - v0.position;
Vector3 e2 = v2.position - v1.position;
Vector3 newNormal = e1.Cross( e2 );
newNormal.Normalize();
// Dot old and new face normal
// If < 0 then more than 90 degree difference
if ( newNormal.Dot( srcFace.normal ) < 0.0f )
{
// Don't do it!
cost = float.MaxValue;
break; // No point continuing
}
}
}
Debug.Assert( cost >= 0 );
return cost;
}