Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1631 lines
40 KiB

//--------------------------------------------------------------------------------------------------
// 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;
}