|
|
//--------------------------------------------------------------------------------------------------
// qhConvex.cpp
//
// Copyright(C) 2011 by D. Gregorius. All rights reserved.
//--------------------------------------------------------------------------------------------------
#include "qhConvex.h"
#include <algorithm>
#include <limits>
#define QH_AXIS_X 0
#define QH_AXIS_Y 1
#define QH_AXIS_Z 2
//--------------------------------------------------------------------------------------------------
// Local construction utilities
//--------------------------------------------------------------------------------------------------
struct qhHalfSpace { qhVector3 Vertex; qhReal Area; };
//--------------------------------------------------------------------------------------------------
static inline qhReal qhAngle( const qhHalfSpace& H1, const qhHalfSpace& H2 ) { qhReal Cos = qhDot( H1.Vertex, H2.Vertex ) / ( qhLength( H1.Vertex ) * qhLength( H2.Vertex ) ); return qhArcCos( qhClamp( Cos, qhReal( -1 ), qhReal( 1 ) ) ); }
//--------------------------------------------------------------------------------------------------
static inline qhHalfSpace qhMerge( const qhHalfSpace& H1, const qhHalfSpace& H2 ) { qhHalfSpace Out; Out.Vertex = ( H1.Area * H1.Vertex + H2.Area * H2.Vertex ) / ( H1.Area + H2.Area ); Out.Area = H1.Area + H2.Area;
return Out; }
//--------------------------------------------------------------------------------------------------
static inline void qhSwap( qhIteration& Lhs, qhIteration& Rhs ) { qhSwap( Lhs.Apex, Rhs.Apex ); qhSwap( Lhs.Horizon, Rhs.Horizon ); qhSwap( Lhs.Vertices, Rhs.Vertices ); qhSwap( Lhs.Faces, Rhs.Faces ); }
//--------------------------------------------------------------------------------------------------
static inline qhVector3 qhBuildCentroid( int VertexCount, const qhVector3* Vertices ) { qhVector3 Centroid = QH_VEC3_ZERO; for ( int i = 0; i < VertexCount; ++i ) { Centroid += Vertices[ i ]; }
QH_ASSERT( VertexCount > 3 ); return Centroid / qhReal( VertexCount ); }
//--------------------------------------------------------------------------------------------------
static void qhShiftVertices( qhArray< qhVector3 >& Vertices, int VertexCount, const qhVector3* VertexBase, const qhVector3& Translation ) { Vertices.Resize( VertexCount ); for ( int i = 0; i < VertexCount; ++i ) { Vertices[ i ] = VertexBase[ i ] + Translation; } }
//--------------------------------------------------------------------------------------------------
static inline qhBounds3 qhBuildBounds( qhArray< qhVector3 >& Vertices ) { qhBounds3 Bounds = QH_BOUNDS3_EMPTY; for ( int i = 0; i < Vertices.Size(); ++i ) { Bounds += Vertices[ i ]; }
return Bounds; }
//--------------------------------------------------------------------------------------------------
static void qhWeldVertices( qhArray< qhVector3 >& Vertices, const qhVector3& Tolerance ) { // DIRK_TODO: This is O(n^2). If this becomes a performance bottleneck
// since we feed large vertex buffers we should use a grid.
for ( int i = 0; i < Vertices.Size(); ++i ) { for ( int k = Vertices.Size() - 1; k > i; --k ) { QH_ASSERT( k > i ); qhVector3 Offset = Vertices[ i ] - Vertices[ k ]; if ( qhAbs( Offset.X ) < Tolerance.X && qhAbs( Offset.Y ) < Tolerance.Y && qhAbs( Offset.Z ) < Tolerance.Z ) { Vertices[ k ] = Vertices.Back(); Vertices.PopBack(); } } } }
//--------------------------------------------------------------------------------------------------
static void qhFindFarthestPointsAlongCardinalAxes( int& Index1, int& Index2, qhReal Tolerance, int VertexCount, const qhVector3* VertexBase ) { Index1 = Index2 = -1; qhVector3 V0 = VertexBase[ 0 ]; qhVector3 Min[ 3 ] = { V0, V0, V0 }; qhVector3 Max[ 3 ] = { V0, V0, V0 }; int MinIndex[ 3 ] = { 0, 0, 0 }; int MaxIndex[ 3 ] = { 0, 0, 0 };
for ( int i = 1; i < VertexCount; ++i ) { const qhVector3& V = VertexBase[ i ];
// X-Axis
if ( V.X < Min[ QH_AXIS_X ].X ) { Min[ QH_AXIS_X ] = V; MinIndex[ QH_AXIS_X ] = i; } else if ( V.X > Max[ QH_AXIS_X ].X ) { Max[ QH_AXIS_X ] = V; MaxIndex[ QH_AXIS_X ] = i; }
// Y-Axis
if ( V.Y < Min[ QH_AXIS_Y ].Y ) { Min[ QH_AXIS_Y ] = V; MinIndex[ QH_AXIS_Y ] = i; } else if ( V.Y > Max[ QH_AXIS_Y ].Y ) { Max[ QH_AXIS_Y ] = V; MaxIndex[ QH_AXIS_Y ] = i; }
// Z-Axis
if ( V.Z < Min[ QH_AXIS_Z ].Z ) { Min[ QH_AXIS_Z ] = V; MinIndex[ QH_AXIS_Z ] = i; } else if ( V.Z > Max[ QH_AXIS_Z ].Z ) { Max[ QH_AXIS_Z ] = V; MaxIndex[ QH_AXIS_Z ] = i; } }
qhVector3 Distance; Distance[ QH_AXIS_X ] = Max[ QH_AXIS_X ].X - Min[ QH_AXIS_X ].X; Distance[ QH_AXIS_Y ] = Max[ QH_AXIS_Y ].Y - Min[ QH_AXIS_Y ].Y; Distance[ QH_AXIS_Z ] = Max[ QH_AXIS_Z ].Z - Min[ QH_AXIS_Z ].Z;
int MaxElement = qhMaxElement( Distance ); if ( Distance[ MaxElement ] > qhReal( 100 ) * Tolerance ) { Index1 = MinIndex[ MaxElement ]; Index2 = MaxIndex[ MaxElement ]; } }
//--------------------------------------------------------------------------------------------------
static int qhFindFarthestPointFromLine( int Index1, int Index2, qhReal Tolerance, int VertexCount, const qhVector3* VertexBase ) { const qhVector3& A = VertexBase[ Index1 ]; const qhVector3& B = VertexBase[ Index2 ];
qhVector3 AB = B - A; qhReal MaxDistance = qhReal( 100 ) * Tolerance; int MaxIndex = -1;
for ( int i = 0; i < VertexCount; ++i ) { if ( i == Index1 || i == Index2 ) { continue; }
const qhVector3& P = VertexBase[ i ];
qhVector3 AP = P - A; qhReal s = qhDot( AP, AB ) / qhDot( AB, AB ); qhVector3 Q = A + s * AB;
qhReal Distance = qhDistance( P, Q ); if ( Distance > MaxDistance ) { MaxDistance = Distance; MaxIndex = i; } }
return MaxIndex; }
//--------------------------------------------------------------------------------------------------
static int qhFindFarthestPointFromPlane( int Index1, int Index2, int Index3, qhReal Tolerance, int VertexCount, const qhVector3* VertexBase ) { const qhVector3& A = VertexBase[ Index1 ]; const qhVector3& B = VertexBase[ Index2 ]; const qhVector3& C = VertexBase[ Index3 ]; qhPlane Plane = qhPlane( A, B, C ); Plane.Normalize(); qhReal MaxDistance = qhReal( 100 ) * Tolerance; int MaxIndex = -1;
for ( int i = 0; i < VertexCount; ++i ) { if ( i == Index1 || i == Index2 || i == Index3 ) { continue; }
qhReal Distance = qhAbs( Plane.Distance( VertexBase[ i ] ) ); if ( Distance > MaxDistance ) { MaxDistance = Distance; MaxIndex = i; } }
return MaxIndex; }
//--------------------------------------------------------------------------------------------------
// qhConvex
//--------------------------------------------------------------------------------------------------
qhConvex::qhConvex( void ) : mTolerance( 0 ) , mMinRadius( 0 ) , mMinOutside( 0 ) , mInteriorPoint( QH_VEC3_ZERO ) {
}
//--------------------------------------------------------------------------------------------------
qhConvex::~qhConvex( void ) { // Destroy faces
qhFace* Face = mFaceList.Begin(); while ( Face != mFaceList.End() ) { qhFace* Nuke = Face; Face = Face->Next;
qhRemove( Nuke ); DestroyFace( Nuke ); }
// Destroy vertices
qhVertex* Vertex = mVertexList.Begin(); while ( Vertex != mVertexList.End() ) { qhVertex* Nuke = Vertex; Vertex = Vertex->Next;
qhRemove( Nuke ); DestroyVertex( Nuke ); } }
//--------------------------------------------------------------------------------------------------
void qhConvex::Construct( int VertexCount, const qhVector3* VertexBase, qhReal RelativeWeldTolerance ) { QH_ASSERT( mIterations.Size() == 0 );
// Validate passed arguments
if ( VertexCount < 4 || VertexBase == NULL ) { return; }
// Pre-process: Shift to origin and remove duplicates
qhVector3 Centroid = qhBuildCentroid( VertexCount, VertexBase );
qhArray< qhVector3 > Vertices; qhShiftVertices( Vertices, VertexCount, VertexBase, -Centroid ); qhBounds3 Bounds = qhBuildBounds( Vertices ); qhWeldVertices( Vertices, RelativeWeldTolerance * ( Bounds.Max - Bounds.Min ) );
// Try to build an initial hull
ComputeTolerance( Vertices );
if ( !BuildInitialHull( Vertices.Size(), Vertices.Begin() ) ) { return; }
// Construct hull
qhVertex* Vertex = NextConflictVertex(); while ( Vertex != NULL ) { AddVertexToHull( Vertex ); Vertex = NextConflictVertex(); }
// Post-process: Clean and shift back to center
CleanHull();
// Shift hull back to original centroid
ShiftHull( Centroid ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::Construct( int PlaneCount, const qhPlane* PlaneBase, qhReal RelativeWeldTolerance, const qhVector3& InternalPoint ) { QH_ASSERT( mIterations.Size() == 0 );
// Validate passed arguments
if ( PlaneCount < 4 || PlaneBase == NULL ) { return; }
// Try to build dual
qhArray< qhVector3 > DualVertices; DualVertices.Resize( PlaneCount );
for ( int Index = 0; Index < PlaneCount; ++Index ) { // Shift planes so we contain the origin
qhPlane Plane = PlaneBase[ Index ]; Plane.Translate( -InternalPoint ); if ( Plane.Offset <= 0.0f ) { return; }
DualVertices[ Index ] = Plane.Normal / Plane.Offset; }
qhConvex Dual; Dual.Construct( DualVertices.Size(), DualVertices.Begin(), 0.0f ); if ( !Dual.IsConsistent() ) { return; }
// Build primal (dual of dual -> this is the convex hull defined by the planes)
const qhList< qhFace >& FaceList = Dual.GetFaceList();
qhArray< qhVector3 > PrimalVertices; PrimalVertices.Reserve( FaceList.Size() );
for ( const qhFace* Face = FaceList.Begin(); Face != FaceList.End(); Face = Face->Next ) { qhPlane Plane = Face->Plane; QH_ASSERT( Plane.Offset > 0.0f );
// Shift vertices back
qhVector3 Vertex = Plane.Normal / Plane.Offset + InternalPoint; PrimalVertices.PushBack( Vertex ); }
Construct( PrimalVertices.Size(), PrimalVertices.Begin(), RelativeWeldTolerance ); }
//--------------------------------------------------------------------------------------------------
bool qhConvex::IsConsistent( void ) const { // Convex polyhedron invariants
int V = GetVertexCount(); int E = GetEdgeCount() / 2; int F = GetFaceCount();
// Euler's identity
if ( V - E + F != 2 ) { return false; }
// Edge and face invariants
for ( const qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { // Face invariants (Topology)
if ( Face->Edge->Face != Face ) { return false; }
// Face invariants (Geometry)
if ( Face->Plane.Distance( mInteriorPoint ) > 0 ) { return false; }
if ( !qhCheckConsistency( Face ) ) { return false; }
if ( Face->Mark != QH_MARK_VISIBLE ) { return false; }
const qhHalfEdge* Edge = Face->Edge;
do { // Edge invariants (Topology)
if ( Edge->Next->Origin != Edge->Twin->Origin ) { return false; }
if ( Edge->Prev->Next != Edge ) { return false; }
if ( Edge->Next->Prev != Edge ) { return false; }
if ( Edge->Twin->Twin != Edge ) { return false; }
if ( Edge->Face != Face ) { return false; }
// Edge invariants (Geometry)
if ( qhDistance( Edge->Origin->Position, Edge->Twin->Origin->Position ) < qhReal( 1000 ) * QH_REAL_MIN ) { return false; }
Edge = Edge->Next; } while ( Edge != Face->Edge ); }
return true; }
//--------------------------------------------------------------------------------------------------
void qhConvex::Simplify( qhConvex& Convex, qhReal MaxAngle ) const { // Cluster all normals within the face tolerance
typedef qhArray< const qhFace* > qhCluster; qhArray< qhCluster > Clusters;
for ( const qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { int BestIndex = -1; qhReal BestDot = qhCos( MaxAngle );
for ( int Index = 0; Index < Clusters.Size(); ++Index ) { const qhCluster& Cluster = Clusters[ Index ]; QH_ASSERT( !Cluster.Empty() );
qhReal Dot = qhDot( Face->Plane.Normal, Cluster[ 0 ]->Plane.Normal ); if ( Dot > BestDot ) { BestIndex = Index; BestDot = Dot; } }
qhCluster& Cluster = BestIndex < 0 ? Clusters.Expand() : Clusters[ BestIndex ]; Cluster.PushBack( Face ); }
// Build dual
qhArray< qhVector3 > DualVertices; qhBounds3 DualBounds = QH_BOUNDS3_EMPTY; qhVector3 Centroid = GetCentroid();
for ( int I = 0; I < Clusters.Size(); ++I ) { const qhCluster& Cluster = Clusters[ I ]; QH_ASSERT( !Cluster.Empty() );
qhReal Area = 0; qhVector3 Vertex = QH_VEC3_ZERO; for ( int K = 0; K < Cluster.Size(); ++K ) { const qhFace* Face = Cluster[ K ]; qhPlane Plane = Face->Plane; Plane.Translate( -Centroid ); QH_ASSERT( Plane.Offset > qhReal( 0 ) );
Area += Face->Area; Vertex += Face->Area * ( Plane.Normal / Plane.Offset ); } QH_ASSERT( Area > qhReal( 0 ) ); Vertex /= Area;
DualVertices.PushBack( Vertex ); DualBounds += Vertex; }
qhConvex Dual; Dual.Construct( DualVertices.Size(), DualVertices.Begin(), 0.0f ); if ( !Dual.IsConsistent() ) { return; }
// Build the final hull
qhArray< qhVector3 > Vertices; for ( const qhFace* Face = Dual.GetFaceList().Begin(); Face != Dual.GetFaceList().End(); Face = Face->Next ) { const qhPlane& Plane = Face->Plane; QH_ASSERT( Plane.Offset > qhReal( 0 ) );
qhVector3 Vertex = ( Plane.Normal / Plane.Offset ) + Centroid; Vertices.PushBack( Vertex ); }
Convex.Construct( Vertices.Size(), Vertices.Begin(), 0.0f );
// Build half-spaces
// qhVector3 Centroid = GetCentroid();
//
// qhArray< qhHalfSpace > HalfSpaces;
// HalfSpaces.Reserve( GetFaceCount() );
//
// for ( const qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next )
// {
// qhPlane Plane = Face->Plane;
// Plane.Translate( -Centroid );
// QH_ASSERT( Plane.Offset > qhReal( 0 ) );
//
// qhHalfSpace HalfSpace;
// HalfSpace.Vertex = Plane.Normal / Plane.Offset;
// HalfSpace.Area = Face->Area;
//
// HalfSpaces.PushBack( HalfSpace );
// }
//
// // Merge faces within specified range
// while ( true )
// {
// // Find global minimum
// qhReal BestAngle = QH_PI;
// int BestIndex1 = -1;
// int BestIndex2 = -1;
//
// for ( int Index1 = 0; Index1 < HalfSpaces.Size(); ++Index1 )
// {
// const qhHalfSpace& HalfSpace1 = HalfSpaces[ Index1 ];
// for ( int Index2 = Index1 + 1; Index2 < HalfSpaces.Size(); ++Index2 )
// {
// qhHalfSpace HalfSpace2 = HalfSpaces[ Index2 ];
//
// qhReal Angle = qhAngle( HalfSpace1, HalfSpace2 );
// if ( Angle < BestAngle )
// {
// BestAngle = Angle;
// BestIndex1 = Index1;
// BestIndex2 = Index2;
// }
// }
// }
//
// // Exit if there are no more faces to merge
// if ( BestAngle > MaxAngle )
// {
// break;
// }
//
// // Merge minimizing faces
// const qhHalfSpace& HalfSpace1 = HalfSpaces[ BestIndex1 ];
// const qhHalfSpace& HalfSpace2 = HalfSpaces[ BestIndex2 ];
// qhHalfSpace HalfSpace = qhMerge( HalfSpace1, HalfSpace2 );
//
// // Remove merged half-spaces and add the new one. We need to remove
// // the second half-space first to not invalidate the first index!
// QH_ASSERT( BestIndex1 < BestIndex2 );
// HalfSpaces[ BestIndex2 ] = HalfSpaces.Back();
// HalfSpaces.PopBack();
// HalfSpaces[ BestIndex1 ] = HalfSpaces.Back();
// HalfSpaces.PopBack();
//
// HalfSpaces.PushBack( HalfSpace );
// }
//
// // Build the dual
// qhArray< qhVector3 > DualVertices;
// DualVertices.Resize( HalfSpaces.Size() );
// for ( int Index = 0; Index < HalfSpaces.Size(); ++Index )
// {
// DualVertices[ Index ] = HalfSpaces[ Index ].Vertex;
// }
//
// qhConvex Dual;
// Dual.Construct( DualVertices.Size(), DualVertices.Begin(), qhReal( 0 ) );
// if ( !Dual.IsConsistent() )
// {
// return;
// }
//
// // Build the merged hull
// qhArray< qhVector3 > Vertices;
// for ( const qhFace* Face = Dual.GetFaceList().Begin(); Face != Dual.GetFaceList().End(); Face = Face->Next )
// {
// const qhPlane& Plane = Face->Plane;
// QH_ASSERT( Plane.Offset > qhReal( 0 ) );
//
// qhVector3 Vertex = ( Plane.Normal / Plane.Offset ) + Centroid;
// Vertices.PushBack( Vertex );
// }
//
// Convex.Construct( Vertices.Size(), Vertices.Begin(), 0.0f );
}
//--------------------------------------------------------------------------------------------------
qhVertex* qhConvex::CreateVertex( const qhVector3& Position ) { qhVertex* Vertex = (qhVertex*)qhAlloc( sizeof( qhVertex ) ); new ( Vertex ) qhVertex;
Vertex->Prev = NULL; Vertex->Next = NULL;
Vertex->Mark = QH_MARK_CONFIRM; Vertex->Position = Position; Vertex->Edge = NULL; Vertex->ConflictFace = NULL; return Vertex; }
//--------------------------------------------------------------------------------------------------
void qhConvex::DestroyVertex( qhVertex* Vertex ) { QH_ASSERT( !qhInList( Vertex ) );
Vertex->~qhVertex(); qhFree( Vertex ); }
//--------------------------------------------------------------------------------------------------
qhFace* qhConvex::CreateFace( qhVertex* Vertex1, qhVertex* Vertex2, qhVertex* Vertex3 ) { qhFace* Face = (qhFace*)qhAlloc( sizeof( qhFace ) ); new ( Face ) qhFace;
qhHalfEdge* Edge1 = (qhHalfEdge*)qhAlloc( sizeof( qhHalfEdge ) ); qhHalfEdge* Edge2 = (qhHalfEdge*)qhAlloc( sizeof( qhHalfEdge ) ); qhHalfEdge* Edge3 = (qhHalfEdge*)qhAlloc( sizeof( qhHalfEdge ) );
qhPlane Plane = qhPlane( Vertex1->Position, Vertex2->Position, Vertex3->Position ); qhReal Area = qhLength( Plane.Normal ) / qhReal( 2 ); Plane.Normalize();
// Initialize face
Face->Prev = NULL; Face->Next = NULL;
Face->Edge = Edge1;
Face->Mark = QH_MARK_VISIBLE; Face->Area = Area; Face->Centroid = ( Vertex1->Position + Vertex2->Position + Vertex3->Position ) / qhReal( 3.0 ); Face->Plane = Plane; Face->Flipped = Plane.Distance( mInteriorPoint ) > 0.0f;
// Initialize edges
Edge1->Prev = Edge3; Edge1->Next = Edge2; Edge1->Origin = Vertex1; Edge1->Face = Face; Edge1->Twin = NULL;
Edge2->Prev = Edge1; Edge2->Next = Edge3; Edge2->Origin = Vertex2; Edge2->Face = Face; Edge2->Twin = NULL;
Edge3->Prev = Edge2; Edge3->Next = Edge1; Edge3->Origin = Vertex3; Edge3->Face = Face; Edge3->Twin = NULL;
return Face; }
//--------------------------------------------------------------------------------------------------
void qhConvex::DestroyFace( qhFace* Face ) { QH_ASSERT( !qhInList( Face ) ); // Edge can be null if face was merged
qhHalfEdge* Edge = Face->Edge; if ( Edge != NULL ) { do { qhHalfEdge* Nuke = Edge; Edge = Edge->Next;
qhFree( Nuke ); } while ( Edge != Face->Edge ); }
Face->~qhFace(); qhFree( Face ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::ComputeTolerance( qhArray< qhVector3 >& Vertices ) { qhBounds3 Bounds = qhBuildBounds( Vertices ); qhVector3 Max = qhMax( qhAbs( Bounds.Min ), qhAbs( Bounds.Max ) );
qhReal MaxSum = Max.X + Max.Y + Max.Z; qhReal MaxCoord = qhMax( Max.X, qhMax( Max.Y, Max.Z ) ); qhReal MaxDistance = qhMin( QH_SQRT3 * MaxCoord, MaxSum );
qhReal Tolerance = ( qhReal( 3 ) * MaxDistance * qhReal( 1.01 ) + MaxCoord ) * QH_REAL_EPSILON;
mTolerance = Tolerance; mMinRadius = qhReal( 4 ) * Tolerance; mMinOutside = qhReal( 2 ) * mMinRadius; }
//--------------------------------------------------------------------------------------------------
bool qhConvex::BuildInitialHull( int VertexCount, const qhVector3* VertexBase ) { int Index1, Index2; qhFindFarthestPointsAlongCardinalAxes( Index1, Index2, mTolerance, VertexCount, VertexBase ); if ( Index1 < 0 || Index2 < 0 ) { return false; }
int Index3 = qhFindFarthestPointFromLine( Index1, Index2, mTolerance, VertexCount, VertexBase ); if ( Index3 < 0 ) { return false; }
int Index4 = qhFindFarthestPointFromPlane( Index1, Index2, Index3, mTolerance, VertexCount, VertexBase ); if ( Index4 < 0 ) { return false; }
// Compute an interior point to detect flipped faces
mInteriorPoint = QH_VEC3_ZERO; mInteriorPoint += VertexBase[ Index1 ]; mInteriorPoint += VertexBase[ Index2 ]; mInteriorPoint += VertexBase[ Index3 ]; mInteriorPoint += VertexBase[ Index4 ]; mInteriorPoint /= qhReal( 4 );
// Check winding order
qhVector3 V1 = VertexBase[ Index1 ] - VertexBase[ Index4 ]; qhVector3 V2 = VertexBase[ Index2 ] - VertexBase[ Index4 ]; qhVector3 V3 = VertexBase[ Index3 ] - VertexBase[ Index4 ];
if ( qhDet( V1, V2, V3 ) < qhReal( 0.0 ) ) { std::swap( Index2, Index3 ); }
// Allocate initial vertices and save them in the vertex list
qhVertex* Vertex1 = CreateVertex( VertexBase[ Index1 ] ); mVertexList.PushBack( Vertex1 ); qhVertex* Vertex2 = CreateVertex( VertexBase[ Index2 ] ); mVertexList.PushBack( Vertex2 ); qhVertex* Vertex3 = CreateVertex( VertexBase[ Index3 ] ); mVertexList.PushBack( Vertex3 ); qhVertex* Vertex4 = CreateVertex( VertexBase[ Index4 ] ); mVertexList.PushBack( Vertex4 );
// Allocate initial faces and save them in the face list
qhFace* Face1 = CreateFace( Vertex1, Vertex2, Vertex3 ); mFaceList.PushBack( Face1 ); qhFace* Face2 = CreateFace( Vertex4, Vertex2, Vertex1 ); mFaceList.PushBack( Face2 ); qhFace* Face3 = CreateFace( Vertex4, Vertex3, Vertex2 ); mFaceList.PushBack( Face3 ); qhFace* Face4 = CreateFace( Vertex4, Vertex1, Vertex3 ); mFaceList.PushBack( Face4 );
// Link faces
qhLinkFaces( Face1, 0, Face2, 1 ); qhLinkFaces( Face1, 1, Face3, 1 ); qhLinkFaces( Face1, 2, Face4, 1 ); qhLinkFaces( Face2, 0, Face3, 2 ); qhLinkFaces( Face3, 0, Face4, 2 ); qhLinkFaces( Face4, 0, Face2, 2 );
QH_ASSERT( qhCheckConsistency( Face1 ) ); QH_ASSERT( qhCheckConsistency( Face2 ) ); QH_ASSERT( qhCheckConsistency( Face3 ) ); QH_ASSERT( qhCheckConsistency( Face4 ) );
// Fill initial conflict lists
for ( int i = 0; i < VertexCount; ++i ) { if ( i == Index1 || i == Index2 || i == Index3 || i == Index4 ) { continue; }
const qhVector3& Point = VertexBase[ i ];
qhReal MaxDistance = mMinOutside; qhFace* MaxFace = NULL;
for ( qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { qhReal Distance = Face->Plane.Distance( Point ); if ( Distance > MaxDistance ) { MaxDistance = Distance; MaxFace = Face; } }
if ( MaxFace != NULL ) { qhVertex* Vertex = CreateVertex( Point );
Vertex->ConflictFace = MaxFace; MaxFace->ConflictList.PushBack( Vertex ); } }
return true; }
//--------------------------------------------------------------------------------------------------
qhVertex* qhConvex::NextConflictVertex( void ) { qhVertex* MaxVertex = NULL; qhReal MaxDistance = mMinOutside;
for ( qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { if ( !Face->ConflictList.Empty() ) { for ( qhVertex* Vertex = Face->ConflictList.Begin(); Vertex != Face->ConflictList.End(); Vertex = Vertex->Next ) { QH_ASSERT( Vertex->ConflictFace == Face ); qhReal Distance = Face->Plane.Distance( Vertex->Position );
if ( Distance > MaxDistance ) { MaxDistance = Distance; MaxVertex = Vertex; } } } }
return MaxVertex; }
//--------------------------------------------------------------------------------------------------
void qhConvex::AddVertexToHull( qhVertex* Vertex ) { // Remove vertex from conflict face
qhFace* Face = Vertex->ConflictFace; Vertex->ConflictFace = NULL; Face->ConflictList.Remove( Vertex ); mVertexList.PushBack( Vertex );
// Find the horizon edges
qhArray< qhHalfEdge* > Horizon; BuildHorizon( Horizon, Vertex, Face ); QH_ASSERT( Horizon.Size() >= 3 );
// Create new cone faces
qhArray< qhFace* > Cone; BuildCone( Cone, Horizon, Vertex ); QH_ASSERT( Cone.Size() >= 3 );
#ifdef QH_DEBUG
// Push iteration before merging faces
AddIteration( Vertex, Horizon, mFaceList ); int Iteration = mIterations.Size() - 1; #endif
// Merge coplanar faces
MergeFaces( Cone ); // Resolve orphaned vertices
ResolveVertices( Cone );
// Remove hidden faces and add new ones
ResolveFaces( Cone ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::AddIteration( qhVertex* Apex, const qhArray< qhHalfEdge* >& Horizon, const qhList< qhFace >& FaceList ) { qhIteration& Iteration = mIterations.Expand();
// Save apex
Iteration.Apex = Apex->Position;
// Save horizon
for ( int i = 0; i < Horizon.Size(); ++i ) { const qhHalfEdge* Edge = Horizon[ i ]; Iteration.Horizon.PushBack( Edge->Origin->Position ); }
// Save current hull faces
for ( const qhFace* Face = FaceList.Begin(); Face != FaceList.End(); Face = Face->Next ) { int VertexCount = 0; const qhHalfEdge* Edge = Face->Edge;
do { VertexCount++; Iteration.Vertices.PushBack( Edge->Origin->Position ); Edge = Edge->Next; } while ( Edge != Face->Edge );
Iteration.Faces.PushBack( VertexCount ); } }
//--------------------------------------------------------------------------------------------------
void qhConvex::CleanHull( void ) { // Mark all vertices on the hull as visible and set leaving edge
for ( qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { qhHalfEdge* Edge = Face->Edge;
do { Edge->Origin->Mark = QH_MARK_VISIBLE;
if ( Edge->Origin->Edge == NULL ) { Edge->Origin->Edge = Edge; }
Edge = Edge->Next; } while ( Edge != Face->Edge ); }
// Remove unconfirmed vertices
qhVertex* Vertex = mVertexList.Begin(); while ( Vertex != mVertexList.End() ) { qhVertex* Next = Vertex->Next; if ( Vertex->Mark != QH_MARK_VISIBLE ) { mVertexList.Remove( Vertex ); DestroyVertex( Vertex ); }
Vertex = Next; } }
//--------------------------------------------------------------------------------------------------
void qhConvex::ShiftHull( const qhVector3& Translation ) { // Transform vertices
for ( qhVertex* Vertex = mVertexList.Begin(); Vertex != mVertexList.End(); Vertex = Vertex->Next ) { Vertex->Position += Translation; }
// Transform planes
for ( qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { Face->Plane.Translate( Translation ); }
// Shift interior point
mInteriorPoint += Translation; }
//--------------------------------------------------------------------------------------------------
void qhConvex::BuildHorizon( qhArray< qhHalfEdge* >& Horizon, qhVertex* Apex, qhFace* Seed, qhHalfEdge* Edge1 ) { // Move vertices to orphaned list
Seed->Mark = QH_MARK_DELETE;
qhVertex* Vertex = Seed->ConflictList.Begin(); while ( Vertex != Seed->ConflictList.End() ) { qhVertex* Orphan = Vertex; Vertex = Vertex->Next;
Orphan->ConflictFace = NULL; Seed->ConflictList.Remove( Orphan );
mOrphanedList.PushBack( Orphan ); } QH_ASSERT( Seed->ConflictList.Empty() );
qhHalfEdge* Edge; if ( Edge1 != NULL ) { Edge = Edge1->Next; } else { Edge1 = Seed->Edge; Edge = Edge1; }
do { qhHalfEdge* Twin = Edge->Twin; if ( Twin->Face->Mark == QH_MARK_VISIBLE ) { if ( Twin->Face->Plane.Distance( Apex->Position ) > mMinRadius ) { BuildHorizon( Horizon, Apex, Twin->Face, Twin ); } else { Horizon.PushBack( Edge ); } } Edge = Edge->Next; } while ( Edge != Edge1 ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::BuildCone( qhArray< qhFace* >& Cone, const qhArray< qhHalfEdge* >& Horizon, qhVertex* Apex ) { // Create cone faces and link bottom edges to horizon
for ( int i = 0; i < Horizon.Size(); ++i ) { qhHalfEdge* Edge = Horizon[ i ]; QH_ASSERT( Edge->Twin->Twin == Edge );
qhFace* Face = CreateFace( Apex, Edge->Origin, Edge->Twin->Origin ); Cone.PushBack( Face ); // Link face to bottom edge
qhLinkFace( Face, 1, Edge->Twin ); }
// Link new cone faces with each other
qhFace* Face1 = Cone.Back(); for ( int i = 0; i < Cone.Size(); ++i ) { qhFace* Face2 = Cone[ i ]; qhLinkFaces( Face1, 2, Face2, 0 ); Face1 = Face2; } }
//--------------------------------------------------------------------------------------------------
void qhConvex::MergeFaces( qhArray< qhFace* >& Cone ) { // Merge flipped faces
for ( int i = 0; i < Cone.Size(); ++i ) { qhFace* Face = Cone[ i ]; if ( Face->Mark == QH_MARK_VISIBLE ) { if ( Face->Flipped ) { qhReal BestArea = 0; qhHalfEdge* BestEdge = NULL;
qhHalfEdge* Edge = Face->Edge;
do { qhHalfEdge* Twin = Edge->Twin;
qhReal Area = Twin->Face->Area; if ( Area > BestArea ) { BestArea = Area; BestEdge = Edge; }
Edge = Edge->Next; } while ( Edge != Face->Edge );
QH_ASSERT( BestEdge != NULL ); ConnectFaces( BestEdge );
QH_ASSERT( Face->Mark == QH_MARK_VISIBLE ); QH_ASSERT( Face->Flipped ); Face->Flipped = false; } } }
// First merge pass
for ( int i = 0; i < Cone.Size(); ++i ) { qhFace* Face = Cone[ i ]; if ( Face->Mark == QH_MARK_VISIBLE ) { // Merge faces which are non-convex as determined by the larger face
while ( FirstPass( Face ) ) {} } }
// Second merge pass
for ( int i = 0; i < Cone.Size(); ++i ) { qhFace* Face = Cone[ i ]; if ( Face->Mark == QH_MARK_CONCAVE ) { Face->Mark = QH_MARK_VISIBLE; while ( SecondPass( Face ) ) {} } } }
//--------------------------------------------------------------------------------------------------
void qhConvex::ResolveVertices( qhArray< qhFace* >& Cone ) { // Resolve orphaned vertices
qhVertex* Vertex = mOrphanedList.Begin(); while ( Vertex != mOrphanedList.End() ) { qhVertex* Next = Vertex->Next; mOrphanedList.Remove( Vertex ); qhReal MaxDistance = mMinOutside; qhFace* MaxFace = NULL; for ( int i = 0; i < Cone.Size(); ++i ) { // Skip faces that got merged
if ( Cone[ i ]->Mark == QH_MARK_VISIBLE ) { qhReal Distance = Cone[ i ]->Plane.Distance( Vertex->Position ); if ( Distance > MaxDistance ) { MaxDistance = Distance; MaxFace = Cone[ i ]; } } } if ( MaxFace != NULL ) { QH_ASSERT( MaxFace->Mark == QH_MARK_VISIBLE ); MaxFace->ConflictList.PushBack( Vertex ); Vertex->ConflictFace = MaxFace; } else { // Vertex has been already removed from the orphaned list
// and can be destroyed
DestroyVertex( Vertex ); Vertex = NULL; } Vertex = Next; }
QH_ASSERT( mOrphanedList.Empty() ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::ResolveFaces( qhArray< qhFace* >& Cone ) { // Delete hidden faces
qhFace* Face = mFaceList.Begin(); while ( Face != mFaceList.End() ) { qhFace* Nuke = Face; Face = Face->Next;
if ( Nuke->Mark == QH_MARK_DELETE ) { QH_ASSERT( Nuke->ConflictList.Empty() );
mFaceList.Remove( Nuke ); DestroyFace( Nuke ); } }
// Add new faces
for ( int i = 0; i < Cone.Size(); ++i ) { if ( Cone[ i ]->Mark == QH_MARK_DELETE ) { DestroyFace( Cone[ i ] ); continue; }
mFaceList.PushBack( Cone[ i ] ); } }
//--------------------------------------------------------------------------------------------------
bool qhConvex::FirstPass( qhFace* Face ) { bool Concave = false;
qhHalfEdge* Edge = Face->Edge;
do { qhHalfEdge* Twin = Edge->Twin;
if ( Face->Area > Twin->Face->Area ) { if ( !Edge->IsConvex( mMinRadius ) ) { // Merge
ConnectFaces( Edge ); return true; } else if ( !Twin->IsConvex( mMinRadius ) ) { // Mark as concave and handle in second pass
Concave = true; } } else { if ( !Twin->IsConvex( mMinRadius ) ) { // Merge
ConnectFaces( Edge ); return true; } else if ( !Edge->IsConvex( mMinRadius ) ) { // Mark as concave and handle in second pass
Concave = true; } }
Edge = Edge->Next; } while ( Edge != Face->Edge );
if ( Concave ) { Face->Mark = QH_MARK_CONCAVE; }
return false; }
//--------------------------------------------------------------------------------------------------
bool qhConvex::SecondPass( qhFace* Face ) { qhHalfEdge* Edge = Face->Edge;
do { qhHalfEdge* Twin = Edge->Twin;
if ( !Edge->IsConvex( mMinRadius ) || !Twin->IsConvex( mMinRadius ) ) { ConnectFaces( Edge ); return true; }
Edge = Edge->Next; } while ( Edge != Face->Edge );
return false; }
//--------------------------------------------------------------------------------------------------
void qhConvex::ConnectFaces( qhHalfEdge* Edge ) { // The absorbing face
qhFace* Face = Edge->Face; QH_ASSERT( qhCheckConsistency( Face ) );
// Find the strip of shared edges
qhHalfEdge* Twin = Edge->Twin; QH_ASSERT( qhCheckConsistency( Twin->Face ) );
qhHalfEdge* EdgePrev = Edge->Prev; qhHalfEdge* EdgeNext = Edge->Next; qhHalfEdge* TwinPrev = Twin->Prev; qhHalfEdge* TwinNext = Twin->Next; while ( EdgePrev->Twin->Face == Twin->Face ) { QH_ASSERT( EdgePrev->Twin == TwinNext ); QH_ASSERT( TwinNext->Twin == EdgePrev );
EdgePrev = EdgePrev->Prev; TwinNext = TwinNext->Next; } QH_ASSERT( EdgePrev->Face != TwinNext->Face );
while ( EdgeNext->Twin->Face == Twin->Face ) { QH_ASSERT( EdgeNext->Twin == TwinPrev ); QH_ASSERT( TwinPrev->Twin == EdgeNext );
EdgeNext = EdgeNext->Next; TwinPrev = TwinPrev->Prev; } QH_ASSERT( EdgeNext->Face != TwinPrev->Face );
// Make sure we don't reference a shared edge
Face->Edge = EdgePrev; // Discard opposing face and absorb non-shared edges
qhArray< qhFace* > MergedFaces; MergedFaces.PushBack( Twin->Face ); Twin->Face->Mark = QH_MARK_DELETE; Twin->Face->Edge = NULL;
for ( qhHalfEdge* Absorbed = TwinNext; Absorbed != TwinPrev->Next; Absorbed = Absorbed->Next ) { Absorbed->Face = Face; }
// Delete shared edges (before connection)
DestroyEdges( EdgePrev->Next, EdgeNext ); DestroyEdges( TwinPrev->Next, TwinNext );
// Connect half edges (this can have side effects)
ConnectEdges( EdgePrev, TwinNext, MergedFaces ); ConnectEdges( TwinPrev, EdgeNext, MergedFaces );
// Rebuild geometry for the merges face
qhNewellPlane( Face ); QH_ASSERT( qhCheckConsistency( Face ) );
// Absorb conflict vertices
AbsorbFaces( Face, MergedFaces ); }
//--------------------------------------------------------------------------------------------------
void qhConvex::ConnectEdges( qhHalfEdge* Prev, qhHalfEdge* Next, qhArray< qhFace* >& MergedFaces ) { QH_ASSERT( Prev != Next ); QH_ASSERT( Prev->Face == Next->Face ); // Check for redundant edges (this has side effects)
// If this condition holds true both faces are in the same
// plane since the share three vertices.
if ( Prev->Twin->Face == Next->Twin->Face ) { // Next is redundant and will be removed.
// It should not be referenced by its associated face!
if ( Next->Face->Edge == Next ) { Next->Face->Edge = Prev; }
qhHalfEdge* Twin; if ( qhVertexCount( Prev->Twin->Face ) == 3 ) { Twin = Next->Twin->Prev->Twin; QH_ASSERT( Twin->Face->Mark != QH_MARK_DELETE );
// If the opposing face is a triangle. We will
// get rid of it *and* its associated edges
// (Don't set OpposingFace->Edge = NULL!)
qhFace* OpposingFace = Prev->Twin->Face; OpposingFace->Mark = QH_MARK_DELETE; MergedFaces.PushBack( OpposingFace ); } else { Twin = Next->Twin; // Prev->Twin is redundant and will be removed.
// It should not be referenced by its associated face!
if ( Twin->Face->Edge == Prev->Twin ) { Twin->Face->Edge = Twin; }
Twin->Next = Prev->Twin->Next; Twin->Next->Prev = Twin;
qhFree( Prev->Twin ); } Prev->Next = Next->Next; Prev->Next->Prev = Prev;
Prev->Twin = Twin; Twin->Twin = Prev;
// Destroy redundant edge and its associated vertex
mVertexList.Remove( Next->Origin ); DestroyVertex( Next->Origin );
qhFree( Next );
// Twin->Face was modified, so recompute its plane
qhNewellPlane( Twin->Face ); QH_ASSERT( qhCheckConsistency( Twin->Face ) ); } else { Prev->Next = Next; Next->Prev = Prev; } }
//--------------------------------------------------------------------------------------------------
void qhConvex::DestroyEdges( qhHalfEdge* Begin, qhHalfEdge* End ) { qhHalfEdge* Edge = Begin; while ( Edge != End ) { qhHalfEdge* Nuke = Edge; Edge = Edge->Next;
// Delete vertex if there is more than one shared edge
// DIRK_TODO: Since we run over the twin edges as well this would delete the vertex twice!
// if ( Nuke != Begin )
// {
// mVertexList.Remove( Nuke->Origin );
// DestroyVertex( Nuke->Origin );
// }
qhFree( Nuke ); Nuke = NULL; } }
//--------------------------------------------------------------------------------------------------
void qhConvex::AbsorbFaces( qhFace* Face, qhArray< qhFace* >& MergedFaces ) { for ( int i = 0; i < MergedFaces.Size(); ++i ) { QH_ASSERT( MergedFaces[ i ]->Mark == QH_MARK_DELETE ); qhList< qhVertex >& ConflictList = MergedFaces[ i ]->ConflictList;
qhVertex* Vertex = ConflictList.Begin(); while ( Vertex != ConflictList.End() ) { qhVertex* Next = Vertex->Next; ConflictList.Remove( Vertex );
if ( Face->Plane.Distance( Vertex->Position ) > mMinOutside ) { Face->ConflictList.PushBack( Vertex ); Vertex->ConflictFace = Face; } else { mOrphanedList.PushBack( Vertex ); }
Vertex = Next; }
QH_ASSERT( ConflictList.Empty() ); } }
//-------------------------------------------------------------------------------------------------
void qhConvex::GetMesh( qhMesh& Mesh ) const { Mesh.Vertices.Clear(); Mesh.Normals.Clear(); Mesh.Faces.Clear(); Mesh.Indices.Clear();
// Save vertices
for ( const qhVertex* Vertex = mVertexList.Begin(); Vertex != mVertexList.End(); Vertex = Vertex->Next ) { Mesh.Vertices.PushBack( Vertex->Position ); }
// Save faces
for ( const qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { Mesh.Normals.PushBack( Face->Plane.Normal );
int IndexStart = Mesh.Indices.Size(); const qhHalfEdge* Edge = Face->Edge;
do { int Index = mVertexList.IndexOf( Edge->Origin ); Mesh.Indices.PushBack( Index );
Edge = Edge->Next; } while ( Edge != Face->Edge );
int IndexEnd = Mesh.Indices.Size(); Mesh.Faces.PushBack( IndexEnd - IndexStart ); } }
//-------------------------------------------------------------------------------------------------
qhMass qhConvex::ComputeMass( qhReal Density ) const { // M. Kallay - "Computing the Moment of Inertia of a Solid Defined by a Triangle Mesh"
qhReal Volume = qhReal( 0 ); qhVector3 Center = QH_VEC3_ZERO;
qhReal XX = qhReal( 0 ); qhReal XY = qhReal( 0 ); qhReal YY = qhReal( 0 ); qhReal XZ = qhReal( 0 ); qhReal ZZ = qhReal( 0 ); qhReal YZ = qhReal( 0 );
// Iterate over faces and triangulate in-place
for ( const qhFace* Face = mFaceList.Begin(); Face != mFaceList.End(); Face = Face->Next ) { const qhHalfEdge* Edge1 = Face->Edge; const qhHalfEdge* Edge2 = Edge1->Next; const qhHalfEdge* Edge3 = Edge2->Next; QH_ASSERT( Edge3 != Edge1 );
qhVector3 V1 = Edge1->Origin->Position;
do { qhVector3 V2 = Edge2->Origin->Position; qhVector3 V3 = Edge3->Origin->Position;
// Signed volume of this tetrahedron
qhReal Det = qhDet( V1, V2, V3 );
// Contribution to mass
Volume += Det;
// Contribution to centroid
qhVector3 v4 = V1 + V2 + V3; Center += Det * v4;
// Contribution to inertia monomials
XX += Det * ( V1.X*V1.X + V2.X*V2.X + V3.X*V3.X + v4.X*v4.X ); YY += Det * ( V1.Y*V1.Y + V2.Y*V2.Y + V3.Y*V3.Y + v4.Y*v4.Y ); ZZ += Det * ( V1.Z*V1.Z + V2.Z*V2.Z + V3.Z*V3.Z + v4.Z*v4.Z ); XY += Det * ( V1.X*V1.Y + V2.X*V2.Y + V3.X*V3.Y + v4.X*v4.Y ); XZ += Det * ( V1.X*V1.Z + V2.X*V2.Z + V3.X*V3.Z + v4.X*v4.Z ); YZ += Det * ( V1.Y*V1.Z + V2.Y*V2.Z + V3.Y*V3.Z + v4.Y*v4.Z );
Edge2 = Edge3; Edge3 = Edge3->Next; } while ( Edge3 != Face->Edge ); } QH_ASSERT( Volume > 0.0f );
// Fetch result
qhMatrix3 Inertia; Inertia.C1.X = YY + ZZ; Inertia.C2.X = -XY; Inertia.C3.X = -XZ; Inertia.C1.Y = -XY; Inertia.C2.Y = XX + ZZ; Inertia.C3.Y = -YZ; Inertia.C1.Z = -XZ; Inertia.C2.Z = -YZ; Inertia.C3.Z = XX + YY;
qhMass Mass; Mass.Weight = Density * Volume / qhReal( 6.0 ); Mass.Center = Center / ( qhReal( 4 ) * Volume ); Mass.Inertia = ( Density / qhReal( 120 ) ) * Inertia;
return Mass; }
|