//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // // Notes: For some semblance of clarity. All spatial orientation notation assumes you're // looking at the polyhedron from an outside point located spherically above // the primitive in question // //======================================================================================// #include "mathlib/polyhedron.h" #include "mathlib/vmatrix.h" #include #include #include "tier1/utlvector.h" #include "mathlib/ssemath.h" struct GeneratePolyhedronFromPlanes_Point; struct GeneratePolyhedronFromPlanes_PointLL; struct GeneratePolyhedronFromPlanes_Line; struct GeneratePolyhedronFromPlanes_LineLL; struct GeneratePolyhedronFromPlanes_Polygon; struct GeneratePolyhedronFromPlanes_PolygonLL; struct GeneratePolyhedronFromPlanes_UnorderedPointLL; struct GeneratePolyhedronFromPlanes_UnorderedLineLL; struct GeneratePolyhedronFromPlanes_UnorderedPolygonLL; Vector FindPointInPlanes( const float *pPlanes, int planeCount ); bool FindConvexShapeLooseAABB( const float *pInwardFacingPlanes, int iPlaneCount, Vector *pAABBMins, Vector *pAABBMaxs ); CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, int iPointCount, const fltx4 *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory, fltx4 vShiftResultPositions ); CPolyhedron *ConvertLinkedGeometryToPolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, bool bUseTemporaryMemory, fltx4 vShiftResultPositions ); //#define ENABLE_DEBUG_POLYHEDRON_DUMPS //Dumps debug information to disk for use with glview. Requires that tier2 also be in all projects using debug mathlib //#define DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS //dumps successfully generated polyhedrons #define USE_WORLD_CENTERED_POSITIONS //shift all our incoming math towards world origin before performing operations on it, shift it back in the result. #ifdef DBGFLAG_ASSERT void DumpPolyhedronToGLView( const CPolyhedron *pPolyhedron, const char *pFilename, const VMatrix *pTransform, const char *szfileOpenOptions = "ab" ); void DumpPlaneToGlView( const float *pPlane, float fGrayScale, const char *pszFileName, const VMatrix *pTransform ); void DumpLineToGLView( const Vector &vPoint1, const Vector &vColor1, const Vector &vPoint2, const Vector &vColor2, float fThickness, FILE *pFile ); void DumpAABBToGLView( const Vector &vCenter, const Vector &vExtents, const Vector &vColor, FILE *pFile ); #if defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) && defined( WIN32 ) #include "winlite.h" #endif static VMatrix s_matIdentity( 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS void DumpWorkingStatePolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pAllPolygons, GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDeadPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pAllLines, GeneratePolyhedronFromPlanes_UnorderedLineLL *pDeadLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, GeneratePolyhedronFromPlanes_UnorderedPointLL *pDeadPoints, const char *pFilename, const VMatrix *pTransform ); #endif #if defined( DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS ) static int g_iPolyhedronDumpCounter = 0; #endif #define DBG_ONLY(x) x #else #define DBG_ONLY(x) #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #if defined( DBGFLAG_ASSERT ) && defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) void CreateDumpDirectory( const char *szDirectoryName ) { #if defined( WIN32 ) CreateDirectory( szDirectoryName, NULL ); #else Assert( false ); //TODO: create directories in linux #endif } #define DEBUG_POLYHEDRON_CONVERSION 1 typedef bool (*PFN_PolyhedronCarvingDebugStepCallback)( CPolyhedron *pPolyhedron ); //function that receives a polyhedron conversion after each cut. For the slowest, surest debugging possible. Returns true if the polyhedron passes mustard, false to dump the current work state PFN_PolyhedronCarvingDebugStepCallback g_pPolyhedronCarvingDebugStepCallback = NULL; #endif void CPolyhedron_AllocByNew::Release( void ) { delete this; } CPolyhedron_AllocByNew *CPolyhedron_AllocByNew::Allocate( unsigned short iVertices, unsigned short iLines, unsigned short iIndices, unsigned short iPolygons ) //creates the polyhedron along with enough memory to hold all it's data in a single allocation { void *pMemory = new unsigned char [ sizeof( CPolyhedron_AllocByNew ) + (iVertices * sizeof(Vector)) + (iLines * sizeof(Polyhedron_IndexedLine_t)) + (iIndices * sizeof( Polyhedron_IndexedLineReference_t )) + (iPolygons * sizeof( Polyhedron_IndexedPolygon_t ))]; #include "tier0/memdbgoff.h" //the following placement new doesn't compile with memory debugging CPolyhedron_AllocByNew *pAllocated = new ( pMemory ) CPolyhedron_AllocByNew; #include "tier0/memdbgon.h" pAllocated->iVertexCount = iVertices; pAllocated->iLineCount = iLines; pAllocated->iIndexCount = iIndices; pAllocated->iPolygonCount = iPolygons; pAllocated->pVertices = (Vector *)(pAllocated + 1); //start vertex memory at the end of the class pAllocated->pLines = (Polyhedron_IndexedLine_t *)(pAllocated->pVertices + iVertices); pAllocated->pIndices = (Polyhedron_IndexedLineReference_t *)(pAllocated->pLines + iLines); pAllocated->pPolygons = (Polyhedron_IndexedPolygon_t *)(pAllocated->pIndices + iIndices); return pAllocated; } class CPolyhedron_TempMemory : public CPolyhedron { public: #ifdef DBGFLAG_ASSERT int iReferenceCount; #endif virtual void Release( void ) { #ifdef DBGFLAG_ASSERT --iReferenceCount; #endif } CPolyhedron_TempMemory( void ) #ifdef DBGFLAG_ASSERT : iReferenceCount( 0 ) #endif { }; }; static CUtlVector s_TempMemoryPolyhedron_Buffer; static CPolyhedron_TempMemory s_TempMemoryPolyhedron; CPolyhedron *GetTempPolyhedron( unsigned short iVertices, unsigned short iLines, unsigned short iIndices, unsigned short iPolygons ) //grab the temporary polyhedron. Avoids new/delete for quick work. Can only be in use by one chunk of code at a time { AssertMsg( s_TempMemoryPolyhedron.iReferenceCount == 0, "Temporary polyhedron memory being rewritten before released" ); #ifdef DBGFLAG_ASSERT ++s_TempMemoryPolyhedron.iReferenceCount; #endif s_TempMemoryPolyhedron_Buffer.SetCount( (sizeof( Vector ) * iVertices) + (sizeof( Polyhedron_IndexedLine_t ) * iLines) + (sizeof( Polyhedron_IndexedLineReference_t ) * iIndices) + (sizeof( Polyhedron_IndexedPolygon_t ) * iPolygons) ); s_TempMemoryPolyhedron.iVertexCount = iVertices; s_TempMemoryPolyhedron.iLineCount = iLines; s_TempMemoryPolyhedron.iIndexCount = iIndices; s_TempMemoryPolyhedron.iPolygonCount = iPolygons; s_TempMemoryPolyhedron.pVertices = (Vector *)s_TempMemoryPolyhedron_Buffer.Base(); s_TempMemoryPolyhedron.pLines = (Polyhedron_IndexedLine_t *)(&s_TempMemoryPolyhedron.pVertices[s_TempMemoryPolyhedron.iVertexCount]); s_TempMemoryPolyhedron.pIndices = (Polyhedron_IndexedLineReference_t *)(&s_TempMemoryPolyhedron.pLines[s_TempMemoryPolyhedron.iLineCount]); s_TempMemoryPolyhedron.pPolygons = (Polyhedron_IndexedPolygon_t *)(&s_TempMemoryPolyhedron.pIndices[s_TempMemoryPolyhedron.iIndexCount]); return &s_TempMemoryPolyhedron; } Vector CPolyhedron::Center( void ) const { if( iVertexCount == 0 ) return vec3_origin; Vector vAABBMin, vAABBMax; vAABBMin = vAABBMax = pVertices[0]; for( int i = 1; i != iVertexCount; ++i ) { Vector &vPoint = pVertices[i]; if( vPoint.x < vAABBMin.x ) vAABBMin.x = vPoint.x; if( vPoint.y < vAABBMin.y ) vAABBMin.y = vPoint.y; if( vPoint.z < vAABBMin.z ) vAABBMin.z = vPoint.z; if( vPoint.x > vAABBMax.x ) vAABBMax.x = vPoint.x; if( vPoint.y > vAABBMax.y ) vAABBMax.y = vPoint.y; if( vPoint.z > vAABBMax.z ) vAABBMax.z = vPoint.z; } return ((vAABBMin + vAABBMax) * 0.5f); } enum PolyhedronPointPlanarity { POINT_DEAD, POINT_ONPLANE, POINT_ALIVE }; struct GeneratePolyhedronFromPlanes_Point //must be aligned to 16 byte boundary { fltx4 ptPosition; //w = -1 to make plane distance an all addition operation after the multiply GeneratePolyhedronFromPlanes_LineLL *pConnectedLines; //keep these in a clockwise order, circular linking float fPlaneDist; //used in plane cutting PolyhedronPointPlanarity planarity; union //temporary work variables { int iSaveIndices; }; #ifdef DBGFLAG_ASSERT struct ClipDebugData_t { void Reset( void ) { memset( this, 0, sizeof( *this ) ); } bool bIsNew; Vector vWorkingStateColorOverride; bool bVisited; float fInitialPlaneDistance; } debugdata; #endif }; enum PolyhedronLinePlanarity { LINE_ONPLANE = 0, LINE_ALIVE = (1 << 0), LINE_DEAD = (1 << 1), LINE_CUT = LINE_ALIVE | LINE_DEAD, }; PolyhedronLinePlanarity &operator|=( PolyhedronLinePlanarity &a, const PolyhedronLinePlanarity &b ) { a = (PolyhedronLinePlanarity)((int)a | int(b)); return a; } struct GeneratePolyhedronFromPlanes_LineLL { GeneratePolyhedronFromPlanes_Line *pLine; //whatever is referencing the line should know which side of the line it's on (points and polygons). //for polygons, it's the index back to the polygon's self. It's also which point to follow to continue going clockwise, which makes polygon 0 the one on the left side of an upward facing line vector //for points, it's the OTHER point's index int iReferenceIndex; GeneratePolyhedronFromPlanes_LineLL *pPrev; GeneratePolyhedronFromPlanes_LineLL *pNext; }; struct GeneratePolyhedronFromPlanes_Line { GeneratePolyhedronFromPlanes_Point *pPoints[2]; //the 2 connecting points in no particular order GeneratePolyhedronFromPlanes_Polygon *pPolygons[2]; //viewing from the outside with the point connections going up, 0 is the left polygon, 1 is the right int iSaveIndices; PolyhedronLinePlanarity planarity; bool bNewLengthThisPass; GeneratePolyhedronFromPlanes_LineLL PointLineLinks[2]; //rather than going into a point and searching for its link to this line, lets just cache it to eliminate searching GeneratePolyhedronFromPlanes_LineLL PolygonLineLinks[2]; //rather than going into a polygon and searching for its link to this line, lets just cache it to eliminate searching #ifdef POLYHEDRON_EXTENSIVE_DEBUGGING int iDebugFlags; #endif void InitLineLinks( void ) { PointLineLinks[0].iReferenceIndex = 1; PointLineLinks[1].iReferenceIndex = 0; PolygonLineLinks[0].iReferenceIndex = 0; PolygonLineLinks[1].iReferenceIndex = 1; PointLineLinks[0].pLine = PointLineLinks[1].pLine = PolygonLineLinks[0].pLine = PolygonLineLinks[1].pLine = this; } #ifdef DBGFLAG_ASSERT struct ClipDebugData_t { void Reset( void ) { memset( this, 0, sizeof( *this ) ); } bool bIsNew; //was generated during this cut Vector vWorkingStateColorOverride; bool bTested; } debugdata; #endif }; struct GeneratePolyhedronFromPlanes_Polygon { Vector vSurfaceNormal; //float fNormalDist; GeneratePolyhedronFromPlanes_LineLL *pLines; //keep these in a clockwise order, circular linking bool bDead; bool bHasNewPoints; bool bMovedExistingPoints; #ifdef DBGFLAG_ASSERT struct ClipDebugData_t { void Reset( void ) { memset( this, 0, sizeof( *this ) ); } bool bIsNew; //only one should be new per clip, unless we triangulate Vector vWorkingStateColorOverride; } debugdata; #endif }; struct GeneratePolyhedronFromPlanes_UnorderedPolygonLL //an unordered collection of polygons { GeneratePolyhedronFromPlanes_Polygon polygon; GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pNext; GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPrev; }; struct GeneratePolyhedronFromPlanes_UnorderedLineLL //an unordered collection of lines { GeneratePolyhedronFromPlanes_Line line; GeneratePolyhedronFromPlanes_UnorderedLineLL *pNext; GeneratePolyhedronFromPlanes_UnorderedLineLL *pPrev; }; #pragma pack( push ) #pragma pack( 16 ) //help align the position to 16 byte boundaries when in arrays struct GeneratePolyhedronFromPlanes_UnorderedPointLL //an unordered collection of points { GeneratePolyhedronFromPlanes_Point point; GeneratePolyhedronFromPlanes_UnorderedPointLL *pNext; GeneratePolyhedronFromPlanes_UnorderedPointLL *pPrev; }; #pragma pack(pop) #ifdef DBGFLAG_ASSERT void Debug_ResetWorkingStateColorOverrides( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pAllPolygons, GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, GeneratePolyhedronFromPlanes_UnorderedLineLL *pAllLines ) { while( pAllPolygons ) { pAllPolygons->polygon.debugdata.vWorkingStateColorOverride.Init(); pAllPolygons = pAllPolygons->pNext; } while( pAllPoints ) { pAllPoints->point.debugdata.vWorkingStateColorOverride.Init(); pAllPoints = pAllPoints->pNext; } while( pAllLines ) { pAllLines->line.debugdata.vWorkingStateColorOverride.Init(); pAllLines = pAllLines->pNext; } } #define DBG_RESETWORKINGSTATECOLORS() Debug_ResetWorkingStateColorOverrides( pAllPolygons, pAllPoints, pAllLines ); #else #define DBG_RESETWORKINGSTATECOLORS() #endif CPolyhedron *ClipPolyhedron( const CPolyhedron *pExistingPolyhedron, const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ) { if( pExistingPolyhedron == NULL ) return NULL; AssertMsg( (pExistingPolyhedron->iVertexCount >= 3) && (pExistingPolyhedron->iPolygonCount >= 2), "Polyhedron doesn't meet absolute minimum spec" ); const size_t kFltX4Align = sizeof( fltx4 ) - 1; uint8 *pAlignedAlloc = (uint8 *)stackalloc( (sizeof( fltx4 ) * iPlaneCount) + kFltX4Align ); pAlignedAlloc = (uint8 *)(((size_t)(pAlignedAlloc + kFltX4Align)) & ~kFltX4Align); fltx4 *pUsefulPlanes = (fltx4 *)pAlignedAlloc; int iUsefulPlaneCount = 0; Vector *pExistingVertices = pExistingPolyhedron->pVertices; //A large part of clipping will either eliminate the polyhedron entirely, or clip nothing at all, so lets just check for those first and throw away useless planes { int iLiveCount = 0; int iDeadCount = 0; const float fNegativeOnPlaneEpsilon = -fOnPlaneEpsilon; for( int i = 0; i != iPlaneCount; ++i ) { Vector vNormal = *((Vector *)&pOutwardFacingPlanes[(i * 4) + 0]); float fPlaneDist = pOutwardFacingPlanes[(i * 4) + 3]; for( int j = 0; j != pExistingPolyhedron->iVertexCount; ++j ) { float fPointDist = vNormal.Dot( pExistingVertices[j] ) - fPlaneDist; if( fPointDist <= fNegativeOnPlaneEpsilon ) ++iLiveCount; else if( fPointDist > fOnPlaneEpsilon ) ++iDeadCount; } if( iLiveCount == 0 ) { //all points are dead or on the plane, so the polyhedron is dead return NULL; } if( iDeadCount != 0 ) { //at least one point died, this plane yields useful results SubFloat( pUsefulPlanes[iUsefulPlaneCount], 0 ) = vNormal.x; //PolyhedronFloatStandardize( vNormal.x ); SubFloat( pUsefulPlanes[iUsefulPlaneCount], 1 ) = vNormal.y; //PolyhedronFloatStandardize( vNormal.y ); SubFloat( pUsefulPlanes[iUsefulPlaneCount], 2 ) = vNormal.z; //PolyhedronFloatStandardize( vNormal.z ); SubFloat( pUsefulPlanes[iUsefulPlaneCount], 3 ) = fPlaneDist; //PolyhedronFloatStandardize( fPlaneDist ); ++iUsefulPlaneCount; } } } if( iUsefulPlaneCount == 0 ) { //testing shows that the polyhedron won't even be cut, clone the existing polyhedron and return that CPolyhedron *pReturn; if( bUseTemporaryMemory ) { pReturn = GetTempPolyhedron( pExistingPolyhedron->iVertexCount, pExistingPolyhedron->iLineCount, pExistingPolyhedron->iIndexCount, pExistingPolyhedron->iPolygonCount ); } else { pReturn = CPolyhedron_AllocByNew::Allocate( pExistingPolyhedron->iVertexCount, pExistingPolyhedron->iLineCount, pExistingPolyhedron->iIndexCount, pExistingPolyhedron->iPolygonCount ); } memcpy( pReturn->pVertices, pExistingPolyhedron->pVertices, sizeof( Vector ) * pReturn->iVertexCount ); memcpy( pReturn->pLines, pExistingPolyhedron->pLines, sizeof( Polyhedron_IndexedLine_t ) * pReturn->iLineCount ); memcpy( pReturn->pIndices, pExistingPolyhedron->pIndices, sizeof( Polyhedron_IndexedLineReference_t ) * pReturn->iIndexCount ); memcpy( pReturn->pPolygons, pExistingPolyhedron->pPolygons, sizeof( Polyhedron_IndexedPolygon_t ) * pReturn->iPolygonCount ); #if defined( DEBUG_POLYHEDRON_CONVERSION ) //last bit of debugging from whatever outside source wants this stupid thing if( g_pPolyhedronCarvingDebugStepCallback != NULL ) { AssertMsg( g_pPolyhedronCarvingDebugStepCallback( pReturn ), "Outside conversion failed" ); } #endif return pReturn; } //convert the polyhedron to linked geometry GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints = (GeneratePolyhedronFromPlanes_UnorderedPointLL *)stackalloc( pExistingPolyhedron->iVertexCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedPointLL ) ); GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines = (GeneratePolyhedronFromPlanes_UnorderedLineLL *)stackalloc( pExistingPolyhedron->iLineCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedLineLL ) ); GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons = (GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)stackalloc( pExistingPolyhedron->iPolygonCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedPolygonLL ) ); #if defined( USE_WORLD_CENTERED_POSITIONS ) fltx4 vPointOffset = LoadZeroSIMD(); #endif //setup points for( int i = 0; i != pExistingPolyhedron->iVertexCount; ++i ) { SubFloat( pPoints[i].point.ptPosition, 0 ) = pExistingPolyhedron->pVertices[i].x; SubFloat( pPoints[i].point.ptPosition, 1 ) = pExistingPolyhedron->pVertices[i].y; SubFloat( pPoints[i].point.ptPosition, 2 ) = pExistingPolyhedron->pVertices[i].z; SubFloat( pPoints[i].point.ptPosition, 3 ) = -1.0f; #if defined( USE_WORLD_CENTERED_POSITIONS ) vPointOffset = AddSIMD( vPointOffset, pPoints[i].point.ptPosition ); #endif pPoints[i].point.pConnectedLines = NULL; //we won't be circular linking until later } #if defined( USE_WORLD_CENTERED_POSITIONS ) //move everything towards origin for more precise math vPointOffset = MulSIMD( vPointOffset, ReplicateX4( -1.0f / (float)pExistingPolyhedron->iVertexCount ) ); vPointOffset = FloorSIMD( vPointOffset ); SubFloat( vPointOffset, 3 ) = -1.0f; for( int i = 0; i != pExistingPolyhedron->iVertexCount; ++i ) { pPoints[i].point.ptPosition = AddSIMD( pPoints[i].point.ptPosition, vPointOffset ); SubFloat( pPoints[i].point.ptPosition, 3 ) = -1.0f; } for( int i = 0; i != iUsefulPlaneCount; ++i ) { fltx4 vMul = MulSIMD( pUsefulPlanes[i], vPointOffset ); SubFloat( pUsefulPlanes[i], 3 ) = SubFloat(vMul, 0) + SubFloat(vMul, 1) + SubFloat(vMul, 2) - SubFloat(vMul, 3 ); } fltx4 vResultOffset = NegSIMD( vPointOffset ); SubFloat( vResultOffset, 3 ) = 0.0f; #else fltx4 vResultOffset = LoadZeroSIMD(); #endif //setup lines and interlink to points (line links are not yet circularly linked, and are unordered) for( int i = 0; i != pExistingPolyhedron->iLineCount; ++i ) { pLines[i].line.InitLineLinks(); for( int j = 0; j != 2; ++j ) { pLines[i].line.pPoints[j] = &pPoints[pExistingPolyhedron->pLines[i].iPointIndices[j]].point; GeneratePolyhedronFromPlanes_LineLL *pLineLink = &pLines[i].line.PointLineLinks[j]; //pLineLink->pPrev = NULL; pLineLink->pNext = pLines[i].line.pPoints[j]->pConnectedLines; pLines[i].line.pPoints[j]->pConnectedLines = pLineLink; } } //setup polygons for( int i = 0; i != pExistingPolyhedron->iPolygonCount; ++i ) { pPolygons[i].polygon.vSurfaceNormal = pExistingPolyhedron->pPolygons[i].polyNormal; Polyhedron_IndexedLineReference_t *pOffsetPolyhedronLines = &pExistingPolyhedron->pIndices[pExistingPolyhedron->pPolygons[i].iFirstIndex]; GeneratePolyhedronFromPlanes_LineLL *pFirstLink = &pLines[pOffsetPolyhedronLines[0].iLineIndex].line.PolygonLineLinks[pOffsetPolyhedronLines[0].iEndPointIndex]; pPolygons[i].polygon.pLines = pFirstLink; //technically going to link to itself on first pass, then get linked properly immediately afterward for( int j = 0; j != pExistingPolyhedron->pPolygons[i].iIndexCount; ++j ) { GeneratePolyhedronFromPlanes_LineLL *pLineLink = &pLines[pOffsetPolyhedronLines[j].iLineIndex].line.PolygonLineLinks[pOffsetPolyhedronLines[j].iEndPointIndex]; pLineLink->pLine->pPolygons[pLineLink->iReferenceIndex] = &pPolygons[i].polygon; pLineLink->pPrev = pPolygons[i].polygon.pLines; pPolygons[i].polygon.pLines->pNext = pLineLink; pPolygons[i].polygon.pLines = pLineLink; } pFirstLink->pPrev = pPolygons[i].polygon.pLines; pPolygons[i].polygon.pLines->pNext = pFirstLink; } //go back to point line links so we can circularly link them as well as order them now that every point has all its line links for( int i = 0; i != pExistingPolyhedron->iVertexCount; ++i ) { //interlink the points { GeneratePolyhedronFromPlanes_LineLL *pLastVisitedLink = pPoints[i].point.pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pCurrentLink = pLastVisitedLink; do { pCurrentLink->pPrev = pLastVisitedLink; pLastVisitedLink = pCurrentLink; pCurrentLink = pCurrentLink->pNext; } while( pCurrentLink ); //circular link pLastVisitedLink->pNext = pPoints[i].point.pConnectedLines; pPoints[i].point.pConnectedLines->pPrev = pLastVisitedLink; } //fix ordering GeneratePolyhedronFromPlanes_LineLL *pFirstLink = pPoints[i].point.pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pFirstLink; GeneratePolyhedronFromPlanes_LineLL *pSearchLink; GeneratePolyhedronFromPlanes_Polygon *pLookingForPolygon; Assert( pFirstLink->pNext != pFirstLink ); do { pLookingForPolygon = pWorkLink->pLine->pPolygons[1 - pWorkLink->iReferenceIndex]; //grab pointer to left polygon pSearchLink = pWorkLink->pPrev; while( pSearchLink->pLine->pPolygons[pSearchLink->iReferenceIndex] != pLookingForPolygon ) pSearchLink = pSearchLink->pPrev; Assert( pSearchLink->pLine->pPolygons[pSearchLink->iReferenceIndex] == pWorkLink->pLine->pPolygons[1 - pWorkLink->iReferenceIndex] ); //pluck the search link from wherever it is pSearchLink->pPrev->pNext = pSearchLink->pNext; pSearchLink->pNext->pPrev = pSearchLink->pPrev; //insert the search link just before the work link pSearchLink->pPrev = pWorkLink->pPrev; pSearchLink->pNext = pWorkLink; pSearchLink->pPrev->pNext = pSearchLink; pWorkLink->pPrev = pSearchLink; pWorkLink = pSearchLink; } while( pWorkLink != pFirstLink ); } //setup point collection { pPoints[0].pPrev = NULL; pPoints[0].pNext = &pPoints[1]; int iLastPoint = pExistingPolyhedron->iVertexCount - 1; for( int i = 1; i != iLastPoint; ++i ) { pPoints[i].pPrev = &pPoints[i - 1]; pPoints[i].pNext = &pPoints[i + 1]; } pPoints[iLastPoint].pPrev = &pPoints[iLastPoint - 1]; pPoints[iLastPoint].pNext = NULL; } //setup line collection { pLines[0].pPrev = NULL; pLines[0].pNext = &pLines[1]; int iLastLine = pExistingPolyhedron->iLineCount - 1; for( int i = 1; i != iLastLine; ++i ) { pLines[i].pPrev = &pLines[i - 1]; pLines[i].pNext = &pLines[i + 1]; } pLines[iLastLine].pPrev = &pLines[iLastLine - 1]; pLines[iLastLine].pNext = NULL; } //setup polygon collection { pPolygons[0].pPrev = NULL; pPolygons[0].pNext = &pPolygons[1]; int iLastPolygon = pExistingPolyhedron->iPolygonCount - 1; for( int i = 1; i != iLastPolygon; ++i ) { pPolygons[i].pPrev = &pPolygons[i - 1]; pPolygons[i].pNext = &pPolygons[i + 1]; } pPolygons[iLastPolygon].pPrev = &pPolygons[iLastPolygon - 1]; pPolygons[iLastPolygon].pNext = NULL; } CPolyhedron *pRetVal = ClipLinkedGeometry( pPolygons, pLines, pPoints, pExistingPolyhedron->iVertexCount, pUsefulPlanes, iUsefulPlaneCount, fOnPlaneEpsilon, bUseTemporaryMemory, vResultOffset ); #if defined( USE_WORLD_CENTERED_POSITIONS ) && defined( DEBUG_POLYHEDRON_CONVERSION ) //last bit of debugging from whatever outside source wants this stupid thing if( pRetVal && (g_pPolyhedronCarvingDebugStepCallback != NULL) ) { VMatrix matScaleCentered; matScaleCentered.Identity(); matScaleCentered[0][0] = matScaleCentered[1][1] = matScaleCentered[2][2] = 10.0f; matScaleCentered.SetTranslation( -pRetVal->Center() * 10.0f ); DumpPolyhedronToGLView( pRetVal, "AssertPolyhedron.txt", &matScaleCentered ); AssertMsg( g_pPolyhedronCarvingDebugStepCallback( pRetVal ), "Outside conversion failed. Offset failure" ); //this REALLY sucks. Because the difference between success and failure was a translation of all points by the same vector. LAME } #endif //#if defined( USE_WORLD_CENTERED_POSITIONS ) return pRetVal; } Vector FindPointInPlanes( const float *pPlanes, int planeCount ) { Vector point = vec3_origin; for ( int i = 0; i < planeCount; i++ ) { float fD = DotProduct( *(Vector *)&pPlanes[i*4], point ) - pPlanes[i*4 + 3]; if ( fD < 0 ) { point -= fD * (*(Vector *)&pPlanes[i*4]); } } return point; } bool FindConvexShapeLooseAABB( const fltx4 *pInwardFacingPlanes, int iPlaneCount, Vector *pAABBMins, Vector *pAABBMaxs ) //bounding box of the convex shape (subject to floating point error) { //returns false if the AABB hasn't been set if( pAABBMins == NULL && pAABBMaxs == NULL ) //no use in actually finding out what it is return false; struct FindConvexShapeAABB_Polygon_t { float *verts; int iVertCount; }; const size_t kPlaneAlign = sizeof( Vector4D ) - 1; uint8 *pAlignedAlloc = (uint8 *)stackalloc( (sizeof( Vector4D ) * iPlaneCount) + kPlaneAlign ); pAlignedAlloc = (uint8 *)(((size_t)(pAlignedAlloc + kPlaneAlign)) & ~kPlaneAlign); Vector4D *pMovedPlanes = (Vector4D *)pAlignedAlloc; for( int i = 0; i != iPlaneCount; ++i ) { pMovedPlanes[i].Init( SubFloat( pInwardFacingPlanes[i], 0 ), SubFloat( pInwardFacingPlanes[i], 1 ), SubFloat( pInwardFacingPlanes[i], 2 ), SubFloat( pInwardFacingPlanes[i], 3 ) - 100.0f ); //move planes out a lot to kill some imprecision problems } //vAABBMins = vAABBMaxs = FindPointInPlanes( pPlanes, iPlaneCount ); float *vertsIn = NULL; //we'll be allocating a new buffer for this with each new polygon, and moving it off to the polygon array float *vertsOut = (float *)stackalloc( (iPlaneCount + 4) * (sizeof( float ) * 3) ); //each plane will initially have 4 points in its polygon representation, and each plane clip has the possibility to add 1 point to the polygon float *vertsSwap; FindConvexShapeAABB_Polygon_t *pPolygons = (FindConvexShapeAABB_Polygon_t *)stackalloc( iPlaneCount * sizeof( FindConvexShapeAABB_Polygon_t ) ); int iPolyCount = 0; for ( int i = 0; i < iPlaneCount; i++ ) { Vector vPlaneNormal = pMovedPlanes[i].AsVector3D(); float fPlaneDist = pMovedPlanes[i].w;// + 50.0f; if( vertsIn == NULL ) vertsIn = (float *)stackalloc( (iPlaneCount + 4) * (sizeof( float ) * 3) ); // Build a big-ass poly in this plane int vertCount = PolyFromPlane( (Vector *)vertsIn, vPlaneNormal, fPlaneDist, 100000.0f ); //chop it by every other plane for( int j = 0; j < iPlaneCount; j++ ) { // don't clip planes with themselves if ( i == j ) continue; // Chop the polygon against this plane vertCount = ClipPolyToPlane( (Vector *)vertsIn, vertCount, (Vector *)vertsOut, pMovedPlanes[j].AsVector3D(), pMovedPlanes[j].w, 0.0f ); //swap the input and output arrays vertsSwap = vertsIn; vertsIn = vertsOut; vertsOut = vertsSwap; // Less than a poly left, something's wrong, don't bother with this polygon if ( vertCount < 3 ) break; } if ( vertCount < 3 ) continue; //not enough to work with pPolygons[iPolyCount].iVertCount = vertCount; pPolygons[iPolyCount].verts = vertsIn; vertsIn = NULL; ++iPolyCount; } if( iPolyCount == 0 ) return false; //initialize the AABB to the first point available Vector vAABBMins, vAABBMaxs; vAABBMins = vAABBMaxs = ((Vector *)pPolygons[0].verts)[0]; if( pAABBMins && pAABBMaxs ) //they want the full box { for( int i = 0; i != iPolyCount; ++i ) { Vector *PolyVerts = (Vector *)pPolygons[i].verts; for( int j = 0; j != pPolygons[i].iVertCount; ++j ) { if( PolyVerts[j].x < vAABBMins.x ) vAABBMins.x = PolyVerts[j].x; if( PolyVerts[j].y < vAABBMins.y ) vAABBMins.y = PolyVerts[j].y; if( PolyVerts[j].z < vAABBMins.z ) vAABBMins.z = PolyVerts[j].z; if( PolyVerts[j].x > vAABBMaxs.x ) vAABBMaxs.x = PolyVerts[j].x; if( PolyVerts[j].y > vAABBMaxs.y ) vAABBMaxs.y = PolyVerts[j].y; if( PolyVerts[j].z > vAABBMaxs.z ) vAABBMaxs.z = PolyVerts[j].z; } } *pAABBMins = vAABBMins; *pAABBMaxs = vAABBMaxs; } else if( pAABBMins ) //they only want the min { for( int i = 0; i != iPolyCount; ++i ) { Vector *PolyVerts = (Vector *)pPolygons[i].verts; for( int j = 0; j != pPolygons[i].iVertCount; ++j ) { if( PolyVerts[j].x < vAABBMins.x ) vAABBMins.x = PolyVerts[j].x; if( PolyVerts[j].y < vAABBMins.y ) vAABBMins.y = PolyVerts[j].y; if( PolyVerts[j].z < vAABBMins.z ) vAABBMins.z = PolyVerts[j].z; } } *pAABBMins = vAABBMins; } else //they only want the max { for( int i = 0; i != iPolyCount; ++i ) { Vector *PolyVerts = (Vector *)pPolygons[i].verts; for( int j = 0; j != pPolygons[i].iVertCount; ++j ) { if( PolyVerts[j].x > vAABBMaxs.x ) vAABBMaxs.x = PolyVerts[j].x; if( PolyVerts[j].y > vAABBMaxs.y ) vAABBMaxs.y = PolyVerts[j].y; if( PolyVerts[j].z > vAABBMaxs.z ) vAABBMaxs.z = PolyVerts[j].z; } } *pAABBMaxs = vAABBMaxs; } return true; } CPolyhedron *ConvertLinkedGeometryToPolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, bool bUseTemporaryMemory, fltx4 vShiftResultPositions ) { Assert( (pPolygons != NULL) && (pLines != NULL) && (pPoints != NULL) ); unsigned int iPolyCount = 0, iLineCount = 0, iPointCount = 0, iIndexCount = 0; GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pPolygons; do { ++iPolyCount; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pActivePolygonWalk->polygon.pLines; GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; Assert( pLineWalk != NULL ); do { ++iIndexCount; pLineWalk = pLineWalk->pNext; } while( pLineWalk != pFirstLine ); pActivePolygonWalk = pActivePolygonWalk->pNext; } while( pActivePolygonWalk ); GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pLines; do { ++iLineCount; pActiveLineWalk = pActiveLineWalk->pNext; } while( pActiveLineWalk ); GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pPoints; do { ++iPointCount; pActivePointWalk = pActivePointWalk->pNext; } while( pActivePointWalk ); CPolyhedron *pReturn; if( bUseTemporaryMemory ) { pReturn = GetTempPolyhedron( iPointCount, iLineCount, iIndexCount, iPolyCount ); } else { pReturn = CPolyhedron_AllocByNew::Allocate( iPointCount, iLineCount, iIndexCount, iPolyCount ); } Vector *pVertexArray = pReturn->pVertices; Polyhedron_IndexedLine_t *pLineArray = pReturn->pLines; Polyhedron_IndexedLineReference_t *pIndexArray = pReturn->pIndices; Polyhedron_IndexedPolygon_t *pPolyArray = pReturn->pPolygons; //copy points pActivePointWalk = pPoints; for( unsigned int i = 0; i != iPointCount; ++i ) { #if defined( USE_WORLD_CENTERED_POSITIONS ) fltx4 vShiftedResult = AddSIMD( pActivePointWalk->point.ptPosition, vShiftResultPositions ); pVertexArray[i].Init( SubFloat( vShiftedResult, 0 ), SubFloat( vShiftedResult, 1 ), SubFloat( vShiftedResult, 2 ) ); #else pVertexArray[i].Init( SubFloat( pActivePointWalk->point.ptPosition, 0 ), SubFloat( pActivePointWalk->point.ptPosition, 1 ), SubFloat( pActivePointWalk->point.ptPosition, 2 ) ); #endif pActivePointWalk->point.iSaveIndices = i; //storing array indices pActivePointWalk = pActivePointWalk->pNext; } //copy lines pActiveLineWalk = pLines; for( unsigned int i = 0; i != iLineCount; ++i ) { pLineArray[i].iPointIndices[0] = (unsigned short)pActiveLineWalk->line.pPoints[0]->iSaveIndices; pLineArray[i].iPointIndices[1] = (unsigned short)pActiveLineWalk->line.pPoints[1]->iSaveIndices; pActiveLineWalk->line.iSaveIndices = i; //storing array indices pActiveLineWalk = pActiveLineWalk->pNext; } //copy polygons and indices at the same time pActivePolygonWalk = pPolygons; iIndexCount = 0; for( unsigned int i = 0; i != iPolyCount; ++i ) { pPolyArray[i].polyNormal = pActivePolygonWalk->polygon.vSurfaceNormal; pPolyArray[i].iFirstIndex = iIndexCount; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pActivePolygonWalk->polygon.pLines; GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; do { pIndexArray[iIndexCount].iLineIndex = pLineWalk->pLine->iSaveIndices; pIndexArray[iIndexCount].iEndPointIndex = pLineWalk->iReferenceIndex; ++iIndexCount; pLineWalk = pLineWalk->pNext; } while( pLineWalk != pFirstLine ); pPolyArray[i].iIndexCount = iIndexCount - pPolyArray[i].iFirstIndex; pActivePolygonWalk = pActivePolygonWalk->pNext; } #if defined( DBGFLAG_ASSERT ) && defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) && defined( DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS ) char szCollisionFile[128]; CreateDumpDirectory( "PolyhedronDumps" ); Q_snprintf( szCollisionFile, 128, "PolyhedronDumps/NewStyle_PolyhedronDump%i.txt", g_iPolyhedronDumpCounter ); ++g_iPolyhedronDumpCounter; remove( szCollisionFile ); DumpPolyhedronToGLView( pReturn, szCollisionFile, &s_matIdentity ); DumpPolyhedronToGLView( pReturn, "PolyhedronDumps/NewStyle_PolyhedronDump_All-Appended.txt", &s_matIdentity ); #endif #if defined( DEBUG_POLYHEDRON_CONVERSION ) && 0 //probably too redundant to check here //last bit of debugging from whatever outside source wants this stupid thing if( (g_pPolyhedronCarvingDebugStepCallback != NULL) && (pReturn != NULL) ) { AssertMsg( g_pPolyhedronCarvingDebugStepCallback( pReturn ), "Outside conversion failed" ); } #endif return pReturn; } #ifdef DBGFLAG_ASSERT void DumpPointListToGLView( GeneratePolyhedronFromPlanes_UnorderedPointLL *pHead, PolyhedronPointPlanarity planarity, const Vector &vColor, const char *szDumpFile, const VMatrix *pTransform ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS if( pTransform == NULL ) pTransform = &s_matIdentity; FILE *pFile = fopen( szDumpFile, "ab" ); while( pHead ) { if( pHead->point.planarity == planarity ) { const Vector vPointExtents( 0.5f, 0.5f, 0.01f ); fltx4 f4Pos = pHead->point.ptPosition; Vector vPos( SubFloat( f4Pos, 0 ), SubFloat( f4Pos, 1 ), SubFloat( f4Pos, 2 ) ); DumpAABBToGLView( (*pTransform) * vPos, vPointExtents, vColor, pFile ); } pHead = pHead->pNext; } fclose( pFile ); #endif } const char * DumpPolyhedronCutHistory( const CUtlVector &DumpedHistory, const CUtlVector &CutHistory, const VMatrix *pTransform ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS if( pTransform == NULL ) pTransform = &s_matIdentity; static char szDumpFile[100] = "FailedPolyhedronCut_Error.txt"; //most recent filename returned for further dumping for( int i = 0; i != DumpedHistory.Count(); ++i ) { if( DumpedHistory[i] != NULL ) { Q_snprintf( szDumpFile, 100, "FailedPolyhedronCut_%d.txt", i ); DumpPolyhedronToGLView( DumpedHistory[i], szDumpFile, pTransform, "wb" ); if( CutHistory.Count() > i ) { DumpPlaneToGlView( CutHistory[i], 1.0f, szDumpFile, pTransform ); } } } return szDumpFile; #else return NULL; #endif } #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS #define DUMP_POLYHEDRON_SCALE 10.0f bool g_bDumpNullPolyhedrons = false; #define AssertMsg_DumpPolyhedron_Destructors(condition, destructors, message)\ if( (condition) == false )\ {\ if( ((destructors).DebugCutHistory.Count() != 0) && ((destructors).PlaneCutHistory.Count() != 0) )\ {\ VMatrix matTransform;\ matTransform.Identity();\ matTransform[0][0] = matTransform[1][1] = matTransform[2][2] = DUMP_POLYHEDRON_SCALE;\ matTransform.SetTranslation( -(destructors).DebugCutHistory.Tail()->Center() * DUMP_POLYHEDRON_SCALE );\ const char *szLastDumpFile = DumpPolyhedronCutHistory( (destructors).DebugCutHistory, (destructors).PlaneCutHistory, &matTransform );\ DumpPointListToGLView( (destructors).pAllPoints, POINT_ALIVE, Vector( 0.9f, 0.9f, 0.9f ), szLastDumpFile, &matTransform );\ DumpPointListToGLView( (destructors).pAllPoints, POINT_ONPLANE, Vector( 0.5f, 0.5f, 0.5f ), szLastDumpFile, &matTransform );\ DumpPointListToGLView( (destructors).pDeadPointCollection, POINT_DEAD, Vector( 0.1f, 0.1f, 0.1f ), szLastDumpFile, &matTransform );\ DumpWorkingStatePolyhedron( (destructors).pAllPolygons, (destructors).pDeadPolygonCollection, (destructors).pAllLines, (destructors).pDeadLineCollection, (destructors).pAllPoints, (destructors).pDeadPointCollection, "FailedPolyhedronCut_LastCutDebug.txt", &matTransform );\ DumpPlaneToGlView( (destructors).PlaneCutHistory.Tail(), 1.0f, "FailedPolyhedronCut_LastCutDebug.txt", &matTransform );\ }\ AssertMsg( condition, message );\ } #else #define AssertMsg_DumpPolyhedron_Destructors(condition, destructors, message) AssertMsg( condition, message ) #endif #else #define AssertMsg_DumpPolyhedron_Destructors(condition, destructors, message) NULL; #endif #define Assert_DumpPolyhedron_Destructors(condition, destructors) AssertMsg_DumpPolyhedron_Destructors( condition, destructors, #condition ) #define AssertMsg_DumpPolyhedron(condition, message) AssertMsg_DumpPolyhedron_Destructors(condition, destructors, message) #define Assert_DumpPolyhedron(condition) Assert_DumpPolyhedron_Destructors( condition, destructors ) //a little class that acts like a small block heap, using stack memory given to it class CStackMemoryDispenser { public: CStackMemoryDispenser( void *pStackAllocation, size_t iStackAllocationSize ) { m_pDispenserBuffer = (unsigned char *)pStackAllocation; m_iDispenserSizeLeft = iStackAllocationSize; m_pDeleteList = NULL; } ~CStackMemoryDispenser( void ) { RecurseDelete( m_pDeleteList ); } void *Allocate( size_t iSize, size_t iAlignTo = 16 ) { AssertMsg( ((iAlignTo - 1) & iAlignTo) == 0, "Alignment must be a power of 2" ); size_t iAlignOffset = iAlignTo - ((size_t)m_pDispenserBuffer) & (iAlignTo - 1); m_iDispenserSizeLeft -= iAlignOffset; m_pDispenserBuffer += iAlignOffset; if( iSize > m_iDispenserSizeLeft ) { //allocate a new buffer size_t iNewBufferSize = MAX( 128 * 1024, (iSize + iAlignTo) * 2 ); //either allocate 128k or enough to hold 2x the allocation. unsigned char *pNewBuffer = new unsigned char [iNewBufferSize]; //allocate 128k at a time *(void **)pNewBuffer = NULL; //insert this allocation into the linked list of allocations to delete on destruct void **pWriteDeleteAddress = &m_pDeleteList; while( *pWriteDeleteAddress != NULL ) { pWriteDeleteAddress = (void **)*pWriteDeleteAddress; } *pWriteDeleteAddress = pNewBuffer; //save this as the new dispenser buffer, skipping the linked list pointer m_pDispenserBuffer = pNewBuffer + sizeof( void * ); m_iDispenserSizeLeft = iNewBufferSize - sizeof( void * ); iAlignOffset = iAlignTo - ((size_t)m_pDispenserBuffer) & (iAlignTo - 1); //recompute alignment offset m_iDispenserSizeLeft -= iAlignOffset; m_pDispenserBuffer += iAlignOffset; } void *pRetVal = m_pDispenserBuffer; m_pDispenserBuffer += iSize; m_iDispenserSizeLeft -= iSize; Assert( (((size_t)pRetVal) & (iAlignTo - 1)) == 0 ); return pRetVal; } private: static void RecurseDelete( void *pDelete ) { if( pDelete != NULL ) { RecurseDelete( *(void **)pDelete ); delete [](void**)pDelete; } } unsigned char *m_pDispenserBuffer; size_t m_iDispenserSizeLeft; void *m_pDeleteList; //a linked list of pointers to actual memory allocations we had to make that need to be deleted. The first thing in each allocation is a reserved space for another pointer }; template class CStackItemDispenser { public: CStackItemDispenser( CStackMemoryDispenser &MemoryDispenser ) : m_FallbackDispenser( MemoryDispenser ) { COMPILE_TIME_ASSERT( sizeof( T ) > sizeof( void * ) ); m_pHead = NULL; } T *Allocate( void ) { if( m_pHead != NULL ) { T *pRetVal = m_pHead; m_pHead = *(T **)m_pHead; #ifdef DBGFLAG_ASSERT memset( pRetVal, 0xCCCCCCCC, sizeof( T ) ); #endif return pRetVal; } else { return (T *)m_FallbackDispenser.Allocate( sizeof( T ) ); } } void Free( T *pFree ) { *(T **)pFree = m_pHead; m_pHead = pFree; } private: CStackMemoryDispenser &m_FallbackDispenser; T *m_pHead; }; inline void ComputePlanarDistances( GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, int iPointCount, fltx4 fPlane ) { uint8 *pAlignedAlloc = (uint8 *)stackalloc( (iPointCount) * sizeof(fltx4) + 15 ); pAlignedAlloc = (uint8 *)(((size_t)(pAlignedAlloc + 15)) & ~15); fltx4 *pIntermediateResults = (fltx4 *)pAlignedAlloc; int i = 0; GeneratePolyhedronFromPlanes_UnorderedPointLL *pPointWalk = pAllPoints; do { Assert( SubFloat( pPointWalk->point.ptPosition, 3 ) == -1.0f ); pIntermediateResults[i] = MulSIMD( fPlane, pPointWalk->point.ptPosition ); ++i; pPointWalk = pPointWalk->pNext; } while( pPointWalk != NULL ); i = 0; pPointWalk = pAllPoints; do { pPointWalk->point.fPlaneDist = SubFloat( pIntermediateResults[i], 0 ) + SubFloat( pIntermediateResults[i], 1 ) + SubFloat( pIntermediateResults[i], 2 ) + SubFloat( pIntermediateResults[i], 3 ); DBG_ONLY( pPointWalk->point.debugdata.fInitialPlaneDistance = pPointWalk->point.fPlaneDist; ); ++i; pPointWalk = pPointWalk->pNext; } while( pPointWalk != NULL ); } class CClipLinkedGeometryDestructors { public: int &iPointCount; GeneratePolyhedronFromPlanes_UnorderedPolygonLL *&pAllPolygons; GeneratePolyhedronFromPlanes_UnorderedLineLL *&pAllLines; GeneratePolyhedronFromPlanes_UnorderedPointLL *&pAllPoints; GeneratePolyhedronFromPlanes_UnorderedPointLL *&pDeadPointCollection; #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS GeneratePolyhedronFromPlanes_UnorderedPolygonLL *&pDeadPolygonCollection; GeneratePolyhedronFromPlanes_UnorderedLineLL *&pDeadLineCollection; CClipLinkedGeometryDestructors( int &iPointCount_IN, GeneratePolyhedronFromPlanes_UnorderedPolygonLL *&pAllPolygons_IN, GeneratePolyhedronFromPlanes_UnorderedPolygonLL *&pDeadPolygonCollection_IN, GeneratePolyhedronFromPlanes_UnorderedLineLL *&pAllLines_IN, GeneratePolyhedronFromPlanes_UnorderedLineLL *&pDeadLineCollection_IN, GeneratePolyhedronFromPlanes_UnorderedPointLL *&pAllPoints_IN, GeneratePolyhedronFromPlanes_UnorderedPointLL *&pDeadPointCollection_IN ) : iPointCount( iPointCount_IN ), pAllPolygons( pAllPolygons_IN ), pAllLines( pAllLines_IN ), pDeadPolygonCollection( pDeadPolygonCollection_IN ), pDeadLineCollection( pDeadLineCollection_IN ), pAllPoints( pAllPoints_IN ), pDeadPointCollection( pDeadPointCollection_IN ) {}; #else CStackItemDispenser &polygonAllocator; CStackItemDispenser &lineAllocator; CClipLinkedGeometryDestructors( int &iPointCount_IN, GeneratePolyhedronFromPlanes_UnorderedPolygonLL *&pAllPolygons_IN, CStackItemDispenser &polygonAllocator_IN, GeneratePolyhedronFromPlanes_UnorderedLineLL *&pAllLines_IN, CStackItemDispenser &lineAllocator_IN, GeneratePolyhedronFromPlanes_UnorderedPointLL *&pAllPoints_IN, GeneratePolyhedronFromPlanes_UnorderedPointLL *&pDeadPointCollection_IN ) : iPointCount( iPointCount_IN ), pAllPolygons( pAllPolygons_IN ), pAllLines( pAllLines_IN ), polygonAllocator( polygonAllocator_IN ), lineAllocator( lineAllocator_IN ), pAllPoints( pAllPoints_IN ), pDeadPointCollection( pDeadPointCollection_IN ) {}; #endif #if defined( DBGFLAG_ASSERT ) //let some generic debug data hitch a ride on this structure since it goes pretty much everywhere CUtlVector DebugCutHistory; CUtlVector PlaneCutHistory; CUtlVector DebugCutPlaneIndex; bool bDebugTrigger; ~CClipLinkedGeometryDestructors( void ) { for( int i = 0; i != DebugCutHistory.Count(); ++i ) { if( DebugCutHistory[i] != NULL ) { DebugCutHistory[i]->Release(); } } } #endif }; static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedPointLL *DestructPoint( GeneratePolyhedronFromPlanes_UnorderedPointLL *pKillPoint, CClipLinkedGeometryDestructors &destructors ) { #if defined( DBGFLAG_ASSERT ) { GeneratePolyhedronFromPlanes_UnorderedPointLL *pDeadPointWalk = destructors.pDeadPointCollection; while( pDeadPointWalk ) { Assert( pDeadPointWalk != pKillPoint ); pDeadPointWalk = pDeadPointWalk->pNext; } } #endif GeneratePolyhedronFromPlanes_UnorderedPointLL *pRetVal = pKillPoint->pNext; DBG_ONLY( pKillPoint->point.planarity = POINT_DEAD; ); if( pKillPoint->pNext ) { pKillPoint->pNext->pPrev = pKillPoint->pPrev; } if( pKillPoint == destructors.pAllPoints ) { destructors.pAllPoints = pKillPoint->pNext; } else { pKillPoint->pPrev->pNext = pKillPoint->pNext; } pKillPoint->pNext = destructors.pDeadPointCollection; destructors.pDeadPointCollection = pKillPoint; --destructors.iPointCount; return pRetVal; } static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedPointLL *DestructPoint( GeneratePolyhedronFromPlanes_Point *pKillPoint, CClipLinkedGeometryDestructors &destructors ) { #ifdef OSX Assert( &(((GeneratePolyhedronFromPlanes_UnorderedPointLL *)pKillPoint)->point) == pKillPoint ); #else // This COMPILE_TIME_ASSERT was breaking gcc under OSX COMPILE_TIME_ASSERT( offsetof(GeneratePolyhedronFromPlanes_UnorderedPointLL, point) == 0 ); #endif return DestructPoint( (GeneratePolyhedronFromPlanes_UnorderedPointLL *)pKillPoint, destructors ); } static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedLineLL *DestructLine( GeneratePolyhedronFromPlanes_UnorderedLineLL *pKillLine, CClipLinkedGeometryDestructors &destructors ) { GeneratePolyhedronFromPlanes_UnorderedLineLL *pRetVal = pKillLine->pNext; DBG_ONLY( pKillLine->line.planarity = LINE_DEAD; ); if( pKillLine->pNext ) { pKillLine->pNext->pPrev = pKillLine->pPrev; } if( pKillLine == destructors.pAllLines ) { destructors.pAllLines = pKillLine->pNext; } else { pKillLine->pPrev->pNext = pKillLine->pNext; } #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS pKillLine->pNext = destructors.pDeadLineCollection; destructors.pDeadLineCollection = pKillLine; #else destructors.lineAllocator.Free( pKillLine ); #endif return pRetVal; } static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedLineLL *DestructLine( GeneratePolyhedronFromPlanes_Line *pKillLine, CClipLinkedGeometryDestructors &destructors ) { #ifdef OSX Assert( &(((GeneratePolyhedronFromPlanes_UnorderedLineLL *)pKillLine)->line) == pKillLine ); #else // This COMPILE_TIME_ASSERT was breaking gcc under OSX COMPILE_TIME_ASSERT( offsetof(GeneratePolyhedronFromPlanes_UnorderedLineLL, line) == 0 ); #endif return DestructLine( (GeneratePolyhedronFromPlanes_UnorderedLineLL *)pKillLine, destructors ); } static FORCEINLINE void UnlinkLine( GeneratePolyhedronFromPlanes_Line *pUnlinkLine ) { //disconnect the line from everything for( int i = 0; i != 2; ++i ) { pUnlinkLine->PointLineLinks[i].pNext->pPrev = pUnlinkLine->PointLineLinks[i].pPrev; pUnlinkLine->PointLineLinks[i].pPrev->pNext = pUnlinkLine->PointLineLinks[i].pNext; pUnlinkLine->PolygonLineLinks[i].pNext->pPrev = pUnlinkLine->PolygonLineLinks[i].pPrev; pUnlinkLine->PolygonLineLinks[i].pPrev->pNext = pUnlinkLine->PolygonLineLinks[i].pNext; pUnlinkLine->pPoints[i]->pConnectedLines = pUnlinkLine->PointLineLinks[i].pNext; pUnlinkLine->pPolygons[i]->pLines = pUnlinkLine->PolygonLineLinks[i].pNext; Assert( (pUnlinkLine->pPoints[i]->pConnectedLines != &pUnlinkLine->PointLineLinks[i]) || (pUnlinkLine->pPoints[i]->planarity == POINT_DEAD) ); Assert( (pUnlinkLine->pPolygons[i]->pLines != &pUnlinkLine->PolygonLineLinks[i]) || (pUnlinkLine->pPolygons[i]->bDead == true) ); } } static FORCEINLINE void UnlinkLine( GeneratePolyhedronFromPlanes_UnorderedLineLL *pUnlinkLine ) { UnlinkLine( &pUnlinkLine->line ); } static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedPolygonLL *DestructPolygon( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pKillPolygon, CClipLinkedGeometryDestructors &destructors ) { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pRetVal = pKillPolygon->pNext; if( pKillPolygon->pNext ) { pKillPolygon->pNext->pPrev = pKillPolygon->pPrev; } if( pKillPolygon == destructors.pAllPolygons ) { destructors.pAllPolygons = pKillPolygon->pNext; } else { pKillPolygon->pPrev->pNext = pKillPolygon->pNext; } #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS pKillPolygon->pNext = destructors.pDeadPolygonCollection; destructors.pDeadPolygonCollection = pKillPolygon; #else destructors.polygonAllocator.Free( pKillPolygon ); #endif return pRetVal; } static FORCEINLINE GeneratePolyhedronFromPlanes_UnorderedPolygonLL *DestructPolygon( GeneratePolyhedronFromPlanes_Polygon *pKillPolygon, CClipLinkedGeometryDestructors &destructors ) { #ifdef OSX Assert( &(((GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)pKillPolygon)->polygon) == pKillPolygon ); #else // This COMPILE_TIME_ASSERT was breaking gcc under OSX COMPILE_TIME_ASSERT( offsetof(GeneratePolyhedronFromPlanes_UnorderedPolygonLL, polygon) == 0 ); #endif return DestructPolygon( (GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)pKillPolygon, destructors ); } //remove a known degenerate polygon. Returns INVALID pointer to the line we destroy in here. GeneratePolyhedronFromPlanes_Line *RemoveDegeneratePolygon( GeneratePolyhedronFromPlanes_Polygon *pDegeneratePolygon, CClipLinkedGeometryDestructors &destructors ) { Assert_DumpPolyhedron( pDegeneratePolygon->pLines->pNext == pDegeneratePolygon->pLines->pPrev ); Assert_DumpPolyhedron( pDegeneratePolygon->pLines->pNext != pDegeneratePolygon->pLines ); //just for the sake of paranoia. Check that it's not a single-lined polygon Assert_DumpPolyhedron( (pDegeneratePolygon->pLines->pLine->pPoints[pDegeneratePolygon->pLines->iReferenceIndex] == pDegeneratePolygon->pLines->pNext->pLine->pPoints[1 - pDegeneratePolygon->pLines->pNext->iReferenceIndex]) && (pDegeneratePolygon->pLines->pLine->pPoints[1 - pDegeneratePolygon->pLines->iReferenceIndex] == pDegeneratePolygon->pLines->pNext->pLine->pPoints[pDegeneratePolygon->pLines->pNext->iReferenceIndex]) ); //both the lines are the same, just ditch one and link up the real polygons GeneratePolyhedronFromPlanes_LineLL *pKeepLine = pDegeneratePolygon->pLines; GeneratePolyhedronFromPlanes_LineLL *pDeadLine = pKeepLine->pNext; GeneratePolyhedronFromPlanes_Line *pRetVal = pDeadLine->pLine; #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pDegeneratePolygon->bDead = true; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. pDeadLine->pLine->planarity = LINE_DEAD; #endif pKeepLine->pLine->pPolygons[pKeepLine->iReferenceIndex] = pDeadLine->pLine->pPolygons[1 - pDeadLine->iReferenceIndex]; //unlink pDeadLine from the dead polygon so it doesn't mess with pKeepLine when we unlink it pDeadLine->pLine->PolygonLineLinks[pDeadLine->iReferenceIndex].pNext = &pDeadLine->pLine->PolygonLineLinks[pDeadLine->iReferenceIndex]; pDeadLine->pLine->PolygonLineLinks[pDeadLine->iReferenceIndex].pPrev = &pDeadLine->pLine->PolygonLineLinks[pDeadLine->iReferenceIndex]; //insert pKeepLine just after pDeadLine on pDeadLine's flip side polygon, so when we unlink pDeadLine, everything links into place smoothly pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex].pPrev = &pDeadLine->pLine->PolygonLineLinks[1 - pDeadLine->iReferenceIndex]; pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex].pNext = pDeadLine->pLine->PolygonLineLinks[1 - pDeadLine->iReferenceIndex].pNext; pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex].pPrev->pNext = &pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex]; pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex].pNext->pPrev = &pKeepLine->pLine->PolygonLineLinks[pKeepLine->iReferenceIndex]; UnlinkLine( pDeadLine->pLine ); DestructLine( pDeadLine->pLine, destructors ); DestructPolygon( pDegeneratePolygon, destructors ); return pRetVal; } //search/kill redundant points on the specified polygon void RemoveDegeneratePoints( GeneratePolyhedronFromPlanes_Polygon *pSearchPolygon, CClipLinkedGeometryDestructors &destructors ) { AssertMsg( destructors.pAllPolygons->pNext != NULL && destructors.pAllPolygons->pNext->pNext != NULL, "RemoveDegeneratePoints() is not safe to run on 2D polyhedrons, early out before you get here" ); GeneratePolyhedronFromPlanes_LineLL *pHeadLine = pSearchPolygon->pLines; GeneratePolyhedronFromPlanes_LineLL *pWalkLine = pHeadLine; do { while( true ) //inner loop to support retesting the same line over and over again( even if it's the head ) { GeneratePolyhedronFromPlanes_LineLL *pPointLineLink = &pWalkLine->pLine->PointLineLinks[1 - pWalkLine->iReferenceIndex]; Assert_DumpPolyhedron( (pPointLineLink->pLine->pPolygons[0]->pLines != pPointLineLink->pLine->pPolygons[0]->pLines->pNext) && (pPointLineLink->pLine->pPolygons[0]->pLines != pPointLineLink->pLine->pPolygons[0]->pLines->pNext->pNext) && (pPointLineLink->pLine->pPolygons[1]->pLines != pPointLineLink->pLine->pPolygons[1]->pLines->pNext) && (pPointLineLink->pLine->pPolygons[1]->pLines != pPointLineLink->pLine->pPolygons[1]->pLines->pNext->pNext) ); //try iterating forward 2 lines, jumping over already-dead lines. //if we end up where we started, the point is redundant //go forward 1 GeneratePolyhedronFromPlanes_LineLL *pCircleBackLineLink = pPointLineLink->pNext; //and again pCircleBackLineLink = pCircleBackLineLink->pNext; //point is connected to only 2 lines. This can only be part of a convex if that convex is entirely 2D or if the lines perfectly agree with eachother. //Based on the assumption that the convex is 3D. We can force the lines to perfectly agree with each other by eliminating one and patching the other to do the work of both. if( pCircleBackLineLink == pPointLineLink ) { //connect the root of the next line to our root, this way multiple occurrences in a row can chain to the same root GeneratePolyhedronFromPlanes_Point *pDeadPoint = pWalkLine->pLine->pPoints[1 - pWalkLine->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pRootLine = &pWalkLine->pPrev->pLine->PointLineLinks[1 - pWalkLine->pPrev->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pSurvivingLine = pWalkLine; Assert_DumpPolyhedron( pDeadPoint->planarity != POINT_DEAD ); Assert_DumpPolyhedron( pRootLine->pLine->planarity != LINE_DEAD ); if( pWalkLine->pPrev == pHeadLine ) { pHeadLine = pWalkLine; } pSurvivingLine = &pSurvivingLine->pLine->PointLineLinks[1 - pSurvivingLine->iReferenceIndex]; //convert it to point space #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pDeadPoint->planarity = POINT_DEAD; pRootLine->pLine->planarity = LINE_DEAD; #endif //the dead line/point removal code will unlink these properly. But will leave surviving line's endpoint pointing at the dead point. //relink surviving to the root point { //unlink dead point from surviving line pSurvivingLine->pNext->pPrev = pSurvivingLine->pPrev; pSurvivingLine->pPrev->pNext = pSurvivingLine->pNext; //arbitrarily insert after dead line on the root point, before or after doesn't matter as it'll be unlinked soon anyway pSurvivingLine->pNext = pRootLine->pNext; pSurvivingLine->pNext->pPrev = pSurvivingLine; pSurvivingLine->pPrev = pRootLine; pRootLine->pNext = pSurvivingLine; //steal root from dead line pSurvivingLine->pLine->pPoints[1 - pSurvivingLine->iReferenceIndex] = pRootLine->pLine->pPoints[1 - pRootLine->iReferenceIndex]; } //pRootLine is fully connected to the root point, dead point, and both polygons. Unlink should work properly UnlinkLine( pRootLine->pLine ); DestructLine( pRootLine->pLine, destructors ); DestructPoint( pDeadPoint, destructors ); } else { break; } } pWalkLine = pWalkLine->pNext; } while (pWalkLine != pHeadLine); } //given two lines that are both connected to the same two points, merge them. //returns true if any deleted line was on the new polygon's edge. (could have deleted pValidLineForNewPolygon) //pMergeLine[1] needs to be deleted after completion static bool MergeTwoLines( GeneratePolyhedronFromPlanes_Line *pMergeLines[2], int iDyingPolygonReferenceIndices[2], CClipLinkedGeometryDestructors &destructors, bool bAllowNullPolygonCollapse ) { //pMergeLines[0] will be the surviving line and pMergeLines[1] will be eliminated. Assert( pMergeLines[0]->pPolygons[iDyingPolygonReferenceIndices[0]] == pMergeLines[1]->pPolygons[iDyingPolygonReferenceIndices[1]] ); GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pKillPolygon = (GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)pMergeLines[1]->pPolygons[iDyingPolygonReferenceIndices[1]]; //merge the polygon pointer from good side of 1 to the dead side of 0 pMergeLines[0]->pPolygons[iDyingPolygonReferenceIndices[0]] = pMergeLines[1]->pPolygons[1 - iDyingPolygonReferenceIndices[1]]; bool bDeletedALineBorderingNewPolygon = (pMergeLines[1]->pPolygons[0] == NULL) || (pMergeLines[1]->pPolygons[1] == NULL); //swap the link from the polygon attached to 1 so it points at 0 instead of 1 { GeneratePolyhedronFromPlanes_LineLL *pEditLink = &pMergeLines[1]->PolygonLineLinks[1 - iDyingPolygonReferenceIndices[1]]; GeneratePolyhedronFromPlanes_LineLL *pSwappedLink = &pMergeLines[0]->PolygonLineLinks[iDyingPolygonReferenceIndices[0]]; pSwappedLink->pNext = pEditLink->pNext; pSwappedLink->pPrev = pEditLink->pPrev; pSwappedLink->pPrev->pNext = pSwappedLink; pSwappedLink->pNext->pPrev = pSwappedLink; if( pMergeLines[1]->pPolygons[1 - iDyingPolygonReferenceIndices[1]]->pLines == pEditLink ) { pMergeLines[1]->pPolygons[1 - iDyingPolygonReferenceIndices[1]]->pLines = pSwappedLink; } } //have all associated points use 0 as their head pointer in case any were using 1 as a head pointer. pMergeLines[0]->pPoints[0]->pConnectedLines = &pMergeLines[0]->PointLineLinks[0]; pMergeLines[0]->pPoints[1]->pConnectedLines = &pMergeLines[0]->PointLineLinks[1]; //unlink 1 from both points { for( int i = 0; i != 2; ++i ) { GeneratePolyhedronFromPlanes_LineLL *pUnlinkLine = &pMergeLines[1]->PointLineLinks[i]; pUnlinkLine->pNext->pPrev = pUnlinkLine->pPrev; pUnlinkLine->pPrev->pNext = pUnlinkLine->pNext; } } //kill the line DestructLine( pMergeLines[1], destructors ); if( pKillPolygon || bAllowNullPolygonCollapse ) { if( pKillPolygon ) //kill the polygon as well { DestructPolygon( pKillPolygon, destructors ); } //in cases where we collapse a polygon, it's possible for the new line to be completely redundant along with 1 point in a set of 2 polygons for( int i = 0; i != 2; ++i ) { GeneratePolyhedronFromPlanes_LineLL *pPointLink = &pMergeLines[0]->PointLineLinks[i]; GeneratePolyhedronFromPlanes_LineLL *pNextLink = pPointLink->pNext; if( (pNextLink->pNext == pPointLink) && //only 2 lines connected to this point (pPointLink->pLine->pPolygons[1 - pPointLink->iReferenceIndex] == pNextLink->pLine->pPolygons[pNextLink->iReferenceIndex]) && (pPointLink->pLine->pPolygons[pPointLink->iReferenceIndex] == pNextLink->pLine->pPolygons[1 - pNextLink->iReferenceIndex]) ) //and they're bounded by the same 2 polygons { Assert( pPointLink->pLine->pPolygons[pPointLink->iReferenceIndex] == pNextLink->pLine->pPolygons[1 - pNextLink->iReferenceIndex] ); //one rotation around a point should yield this as a shared polygon in all cases GeneratePolyhedronFromPlanes_Point *pRedundantPoint = pMergeLines[0]->pPoints[i]; GeneratePolyhedronFromPlanes_LineLL *pSurvivingLine = pRedundantPoint->pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pRedundantLine = pSurvivingLine->pNext; Assert( pSurvivingLine->pNext == pRedundantLine ); Assert( pRedundantLine->pNext == pSurvivingLine ); //link over the redundant point in the surviving line pSurvivingLine->pLine->pPoints[1 - pSurvivingLine->iReferenceIndex] = pRedundantLine->pLine->pPoints[pRedundantLine->iReferenceIndex]; //link over the redunant line from the side opposite the redundant point { GeneratePolyhedronFromPlanes_LineLL *pRedunantPointLink = &pRedundantLine->pLine->PointLineLinks[pRedundantLine->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pSwapLink = &pSurvivingLine->pLine->PointLineLinks[1 - pSurvivingLine->iReferenceIndex]; pSwapLink->pNext = pRedunantPointLink->pNext; pSwapLink->pPrev = pRedunantPointLink->pPrev; pSwapLink->pNext->pPrev = pSwapLink; pSwapLink->pPrev->pNext = pSwapLink; pRedundantLine->pLine->pPoints[pRedundantLine->iReferenceIndex]->pConnectedLines = pSwapLink; } //link past the redundant line in both polygons for( int j = 0; j != 2; ++j ) { if( pRedundantLine->pLine->pPolygons[i] == NULL ) { bDeletedALineBorderingNewPolygon = true; } else { GeneratePolyhedronFromPlanes_LineLL *pLinkOver = &pRedundantLine->pLine->PolygonLineLinks[j]; pLinkOver->pNext->pPrev = pLinkOver->pPrev; pLinkOver->pPrev->pNext = pLinkOver->pNext; if( pRedundantLine->pLine->pPolygons[j]->pLines == pLinkOver ) pRedundantLine->pLine->pPolygons[j]->pLines = pLinkOver->pNext; } } //kill the redundant line DestructLine( pRedundantLine->pLine, destructors ); //kill the redundant point DestructPoint( pRedundantPoint, destructors ); break; } } } return bDeletedALineBorderingNewPolygon; } static inline GeneratePolyhedronFromPlanes_Point *AllocatePoint( GeneratePolyhedronFromPlanes_UnorderedPointLL * &pAllPoints, CStackItemDispenser &pointAllocator, int &iPointCount ) { pAllPoints->pPrev = pointAllocator.Allocate(); DBG_ONLY( pAllPoints->pPrev->point.debugdata.Reset() ); pAllPoints->pPrev->pNext = pAllPoints; pAllPoints = pAllPoints->pPrev; pAllPoints->pPrev = NULL; DBG_ONLY( pAllPoints->point.debugdata.bIsNew = true; ); Assert( (((size_t)&pAllPoints->point.ptPosition) & 15) == 0 ); ++iPointCount; return &pAllPoints->point; } static inline GeneratePolyhedronFromPlanes_Line *AllocateLine( GeneratePolyhedronFromPlanes_UnorderedLineLL * &pAllLines, CStackItemDispenser &lineAllocator ) { //before we forget, add this line to the active list pAllLines->pPrev = lineAllocator.Allocate(); DBG_ONLY( pAllLines->pPrev->line.debugdata.Reset(); ); pAllLines->pPrev->pNext = pAllLines; pAllLines = pAllLines->pPrev; pAllLines->pPrev = NULL; pAllLines->line.InitLineLinks(); pAllLines->line.planarity = LINE_ONPLANE; pAllLines->line.bNewLengthThisPass = true; DBG_ONLY( pAllLines->line.debugdata.bIsNew = true; ); return &pAllLines->line; } static inline GeneratePolyhedronFromPlanes_Polygon *AllocatePolygon( GeneratePolyhedronFromPlanes_UnorderedPolygonLL * &pAllPolygons, CStackItemDispenser &polygonAllocator, const Vector &vSurfaceNormal/*, float fPlaneDist*/ ) { pAllPolygons->pPrev = polygonAllocator.Allocate(); DBG_ONLY( pAllPolygons->pPrev->polygon.debugdata.Reset() ); pAllPolygons->pPrev->pNext = pAllPolygons; pAllPolygons = pAllPolygons->pPrev; pAllPolygons->pPrev = NULL; pAllPolygons->polygon.bDead = false; //technically missing all it's sides, but we're fixing it now pAllPolygons->polygon.bHasNewPoints = true; pAllPolygons->polygon.bMovedExistingPoints = false; pAllPolygons->polygon.vSurfaceNormal = vSurfaceNormal; //pAllPolygons->polygon.fNormalDist = fPlaneDist; DBG_ONLY( pAllPolygons->polygon.debugdata.bIsNew = true; ); return &pAllPolygons->polygon; } #if defined( DBGFLAG_ASSERT ) int g_iDebugPolyhedronClipProcess = -1; #endif struct MarkPlanarityControlStruct_t { MarkPlanarityControlStruct_t( GeneratePolyhedronFromPlanes_Polygon *pPolygon, CStackItemDispenser &lineAlloc, GeneratePolyhedronFromPlanes_UnorderedLineLL *&pLines, CStackItemDispenser &pointAlloc, GeneratePolyhedronFromPlanes_UnorderedPointLL *&pPoints, int &iPoints, float fPlaneEpsilon ) : pNewPolygon( pPolygon ), lineAllocator( lineAlloc ), pAllLines( pLines ), pointAllocator( pointAlloc ), pAllPoints( pPoints ), iPointCount( iPoints ), fOnPlaneEpsilon( fPlaneEpsilon ), fNegativeOnPlaneEpsilon( -fPlaneEpsilon ) { bAllPointsDead = true; GeneratePolyhedronFromPlanes_Line *pStartLine = AllocateLine( pAllLines, lineAllocator ); { //A bit of setup on the dummy line, links to nothing pStartLine->pPolygons[0] = NULL; pStartLine->PolygonLineLinks[0].pNext = &pStartLine->PolygonLineLinks[0]; pStartLine->PolygonLineLinks[0].pPrev = &pStartLine->PolygonLineLinks[0]; pStartLine->pPolygons[1] = pNewPolygon; pStartLine->PolygonLineLinks[1].pNext = &pStartLine->PolygonLineLinks[1]; pStartLine->PolygonLineLinks[1].pPrev = &pStartLine->PolygonLineLinks[1]; pStartLine->pPoints[0] = NULL; pStartLine->PointLineLinks[0].pNext = &pStartLine->PointLineLinks[0]; pStartLine->PointLineLinks[0].pPrev = &pStartLine->PointLineLinks[0]; pStartLine->pPoints[1] = NULL; pStartLine->PointLineLinks[1].pNext = &pStartLine->PointLineLinks[1]; pStartLine->PointLineLinks[1].pPrev = &pStartLine->PointLineLinks[1]; pStartLine->planarity = LINE_ONPLANE; } pActivePolyLine = &pStartLine->PolygonLineLinks[1]; pPolygon->pLines = pActivePolyLine; } GeneratePolyhedronFromPlanes_LineLL *pActivePolyLine; GeneratePolyhedronFromPlanes_Polygon *pNewPolygon; CStackItemDispenser &lineAllocator; GeneratePolyhedronFromPlanes_UnorderedLineLL *&pAllLines; CStackItemDispenser &pointAllocator; GeneratePolyhedronFromPlanes_UnorderedPointLL *&pAllPoints; int &iPointCount; float fOnPlaneEpsilon; float fNegativeOnPlaneEpsilon; bool bAllPointsDead; #if defined( DBGFLAG_ASSERT ) fltx4 vCutPlane; #endif }; GeneratePolyhedronFromPlanes_Line *MarkPlanarity_CreateNewPolyLine( MarkPlanarityControlStruct_t &control ) { GeneratePolyhedronFromPlanes_Line *pNewLine = AllocateLine( control.pAllLines, control.lineAllocator ); //make sure we can link into it { pNewLine->pPoints[0] = NULL; pNewLine->PointLineLinks[0].pPrev = NULL; pNewLine->PointLineLinks[0].pNext = NULL; pNewLine->pPoints[1] = NULL; pNewLine->PointLineLinks[1].pPrev = NULL; pNewLine->PointLineLinks[1].pNext = NULL; pNewLine->pPolygons[0] = NULL; pNewLine->PolygonLineLinks[0].pNext = NULL; pNewLine->PolygonLineLinks[0].pPrev = NULL; pNewLine->pPolygons[1] = control.pNewPolygon; pNewLine->PolygonLineLinks[1].pNext = control.pActivePolyLine->pNext; pNewLine->PolygonLineLinks[1].pPrev = control.pActivePolyLine; control.pActivePolyLine->pNext->pPrev = &pNewLine->PolygonLineLinks[1]; control.pActivePolyLine->pNext = &pNewLine->PolygonLineLinks[1]; control.pActivePolyLine = &pNewLine->PolygonLineLinks[1]; control.pActivePolyLine->pLine->planarity = LINE_ONPLANE; } return pNewLine; } //design the following algorithms to never crawl past the cutting plane. That way we can get consistent results void Recursive_MarkPlanarity_OnPlane( GeneratePolyhedronFromPlanes_LineLL *pLineWalk, MarkPlanarityControlStruct_t &control ); void Recursive_MarkPlanarity_Dead( GeneratePolyhedronFromPlanes_LineLL *pLineWalk, MarkPlanarityControlStruct_t &control ) { Assert( pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->planarity == POINT_DEAD ); //Assert( !pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->debugdata.bVisited || (pLineWalk->pLine->planarity == LINE_DEAD) ); DBG_ONLY( pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->debugdata.bVisited = true; ); while( pLineWalk->pLine->planarity == LINE_ALIVE ) //any line connected to a dead point can't be alive, therefore has not been traversed since we only travel through dead points { Assert( !pLineWalk->pLine->debugdata.bTested && !pLineWalk->pLine->debugdata.bIsNew ); DBG_ONLY( pLineWalk->pLine->debugdata.bTested = true; ); GeneratePolyhedronFromPlanes_Point *pConnectedPoint = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]; if( pConnectedPoint->fPlaneDist > control.fOnPlaneEpsilon ) { //point dead, line is also dead pConnectedPoint->planarity = POINT_DEAD; pLineWalk->pLine->planarity = LINE_DEAD; //branch into the point as a new root Recursive_MarkPlanarity_Dead( pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pNext, control ); } else if( pConnectedPoint->fPlaneDist >= control.fNegativeOnPlaneEpsilon ) { //point onplane, line is dead pConnectedPoint->planarity = POINT_ONPLANE; pLineWalk->pLine->planarity = LINE_DEAD; control.pNewPolygon->bMovedExistingPoints = true; //mark the polygon as using off-plane points Recursive_MarkPlanarity_OnPlane( pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pNext, control ); control.pActivePolyLine->pLine->pPoints[0] = pConnectedPoint; control.pActivePolyLine->pLine->PointLineLinks[0].pNext = &pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex]; control.pActivePolyLine->pLine->PointLineLinks[0].pPrev = pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pPrev; } else { //point alive, line is cut Assert( pConnectedPoint->planarity == POINT_ALIVE ); pLineWalk->pLine->planarity = LINE_CUT; control.bAllPointsDead = false; int iDeadIndex = 1 - pLineWalk->iReferenceIndex; int iLivingIndex = pLineWalk->iReferenceIndex; GeneratePolyhedronFromPlanes_Point *pDeadPoint = pLineWalk->pLine->pPoints[iDeadIndex]; GeneratePolyhedronFromPlanes_Point *pLivingPoint = pLineWalk->pLine->pPoints[iLivingIndex]; //Generate a new point GeneratePolyhedronFromPlanes_Point *pNewPoint = AllocatePoint( control.pAllPoints, control.pointAllocator, control.iPointCount ); { Assert( (pDeadPoint->fPlaneDist - pLivingPoint->fPlaneDist) > control.fOnPlaneEpsilon ); float fInvTotalDist = 1.0f/(pDeadPoint->fPlaneDist - pLivingPoint->fPlaneDist); //subtraction because the living index is known to be negative pNewPoint->ptPosition = SubSIMD( MulSIMD(pLivingPoint->ptPosition, ReplicateX4(pDeadPoint->fPlaneDist * fInvTotalDist)), MulSIMD(pDeadPoint->ptPosition, ReplicateX4(pLivingPoint->fPlaneDist * fInvTotalDist)) ); SubFloat( pNewPoint->ptPosition, 3 ) = -1.0f; #if defined( DBGFLAG_ASSERT ) //check length of line that will remain in the polyhedron, and doublecheck planar distance of new point { fltx4 vAliveLineDiff = SubSIMD( pNewPoint->ptPosition, pLivingPoint->ptPosition ); Vector vecAliveLineDiff( SubFloat( vAliveLineDiff, 0 ), SubFloat( vAliveLineDiff, 1 ), SubFloat( vAliveLineDiff, 2 ) ); float fLineLength = vecAliveLineDiff.Length(); AssertMsg( fLineLength > control.fOnPlaneEpsilon, "Dangerously short line" ); fltx4 vDist = MulSIMD( control.vCutPlane, pNewPoint->ptPosition ); float fDebugDist; fDebugDist = SubFloat( vDist, 0 ) + SubFloat( vDist, 1 ) + SubFloat( vDist, 2 ) + SubFloat( vDist, 3 ); //just for looking at in watch windows //Assert( fabs( fDebugDist ) <= control.fOnPlaneEpsilon ); } #endif pNewPoint->planarity = POINT_ONPLANE; pNewPoint->fPlaneDist = 0.0f; } pLineWalk->pLine->pPolygons[0]->bHasNewPoints = true; pLineWalk->pLine->pPolygons[1]->bHasNewPoints = true; GeneratePolyhedronFromPlanes_Line *pCompletedPolyLine = control.pActivePolyLine->pLine; GeneratePolyhedronFromPlanes_Line *pNewPolyLine = MarkPlanarity_CreateNewPolyLine( control ); GeneratePolyhedronFromPlanes_Line *pCutLine = pLineWalk->pLine; GeneratePolyhedronFromPlanes_Line *pNewLivingLine = AllocateLine( control.pAllLines, control.lineAllocator ); //going to relink the cut line to be between the dead point and the new point //also going to create a new line between new point and live point //The new line will copy the cut line's iReferenceIndex orientation { //relink point pointers pNewLivingLine->pPoints[iDeadIndex] = pNewPoint; pNewLivingLine->pPoints[iLivingIndex] = pCutLine->pPoints[iLivingIndex]; pCutLine->pPoints[iLivingIndex] = pNewPoint; pNewPoint->pConnectedLines = &pNewLivingLine->PointLineLinks[iDeadIndex]; pCutLine->planarity = LINE_DEAD; pNewLivingLine->planarity = LINE_ALIVE; //new line steals cut line's living index linkages pNewLivingLine->PointLineLinks[iLivingIndex].pNext = pCutLine->PointLineLinks[iLivingIndex].pNext; pNewLivingLine->PointLineLinks[iLivingIndex].pPrev = pCutLine->PointLineLinks[iLivingIndex].pPrev; pNewLivingLine->PointLineLinks[iLivingIndex].pNext->pPrev = &pNewLivingLine->PointLineLinks[iLivingIndex]; pNewLivingLine->PointLineLinks[iLivingIndex].pPrev->pNext = &pNewLivingLine->PointLineLinks[iLivingIndex]; pNewLivingLine->pPoints[iLivingIndex]->pConnectedLines = &pNewLivingLine->PointLineLinks[iLivingIndex]; //crosslink cut living to new dead pCutLine->PointLineLinks[iLivingIndex].pNext = pCutLine->PointLineLinks[iLivingIndex].pPrev = &pNewLivingLine->PointLineLinks[iDeadIndex]; pNewLivingLine->PointLineLinks[iDeadIndex].pNext = pNewLivingLine->PointLineLinks[iDeadIndex].pPrev = &pCutLine->PointLineLinks[iLivingIndex]; //fix up polygon linkages pNewLivingLine->pPolygons[0] = pCutLine->pPolygons[0]; pNewLivingLine->pPolygons[1] = pCutLine->pPolygons[1]; //insert after cut line for 0 polygon pNewLivingLine->PolygonLineLinks[0].pNext = pCutLine->PolygonLineLinks[0].pNext; pNewLivingLine->PolygonLineLinks[0].pPrev = &pCutLine->PolygonLineLinks[0]; pCutLine->PolygonLineLinks[0].pNext = &pNewLivingLine->PolygonLineLinks[0]; pNewLivingLine->PolygonLineLinks[0].pNext->pPrev = &pNewLivingLine->PolygonLineLinks[0]; //insert before cut line for 1 polygon pNewLivingLine->PolygonLineLinks[1].pNext = &pCutLine->PolygonLineLinks[1]; pNewLivingLine->PolygonLineLinks[1].pPrev = pCutLine->PolygonLineLinks[1].pPrev; pCutLine->PolygonLineLinks[1].pPrev = &pNewLivingLine->PolygonLineLinks[1]; pNewLivingLine->PolygonLineLinks[1].pPrev->pNext = &pNewLivingLine->PolygonLineLinks[1]; } //We now should have everything we need to finish constructing pCompletedPolyLine { //link polygon completed line to new point pCompletedPolyLine->pPoints[1] = pNewPoint; //Make a T junction between cut line, new line, and completed polygon line pCompletedPolyLine->PointLineLinks[1].pPrev = &pCutLine->PointLineLinks[iLivingIndex]; pCutLine->PointLineLinks[iLivingIndex].pNext = &pCompletedPolyLine->PointLineLinks[1]; pCompletedPolyLine->PointLineLinks[1].pNext = &pNewLivingLine->PointLineLinks[iDeadIndex]; pNewLivingLine->PointLineLinks[iDeadIndex].pPrev = &pCompletedPolyLine->PointLineLinks[1]; //pCompletedLine->pLine->pPoints[0] should already have valid values that just need to be linked back in pCompletedPolyLine->PointLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PointLineLinks[0]; pCompletedPolyLine->PointLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PointLineLinks[0]; //link outwardly into the patched up polygon pCompletedPolyLine->pPolygons[0] = pNewLivingLine->pPolygons[iDeadIndex]; //left side of line going to living point is the polygon that will survive the planar clip pCompletedPolyLine->PolygonLineLinks[0].pPrev = &pNewLivingLine->PolygonLineLinks[iDeadIndex]; pCompletedPolyLine->PolygonLineLinks[0].pNext = pNewLivingLine->PolygonLineLinks[iDeadIndex].pNext; pCompletedPolyLine->PolygonLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PolygonLineLinks[0]; pCompletedPolyLine->PolygonLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PolygonLineLinks[0]; #if defined( DBGFLAG_ASSERT ) if( pCompletedPolyLine->pPoints[0] && pCompletedPolyLine->pPoints[1] ) { fltx4 vLineTemp = SubSIMD( pCompletedPolyLine->pPoints[1]->ptPosition, pCompletedPolyLine->pPoints[0]->ptPosition ); AssertMsg( (SubFloat( vLineTemp, 0 ) != 0.0f) || (SubFloat( vLineTemp, 1 ) != 0.0f) || (SubFloat( vLineTemp, 2 ) != 0.0f), "Created zero length line" ); } #endif } //keep updating the drag line pNewPolyLine->pPoints[0] = pNewPoint; pNewPolyLine->PointLineLinks[0].pNext = &pCutLine->PointLineLinks[iLivingIndex]; pNewPolyLine->PointLineLinks[0].pPrev = &pNewLivingLine->PointLineLinks[iDeadIndex]; } pLineWalk = pLineWalk->pNext; } /*if( pLineWalk->pLine->planarity == LINE_DEAD ) { //left polygon is dead #if defined( DBGFLAG_ASSERT ) //make sure { //walk the polygon and ensure it should be dead GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalkHead = &pLineWalk->pLine->PolygonLineLinks[1 - pLineWalk->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalk = pDebugLineWalkHead; do { Assert( pDebugLineWalk->pLine->planarity != LINE_ALIVE ); pDebugLineWalk = pDebugLineWalk->pNext; } while (pDebugLineWalk != pDebugLineWalkHead); } #endif Assert( !pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex]->bDead ); //not already marked dead pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex]->bDead = true; }*/ } bool Recursive_CanOnPlanePolyCrawlDead( GeneratePolyhedronFromPlanes_LineLL *pLineWalk, MarkPlanarityControlStruct_t &control ); //Once we start traversing on-plane points, our options reduce. We do this to ensure we never traverse a section that isn't touching the cut plane void Recursive_MarkPlanarity_OnPlane( GeneratePolyhedronFromPlanes_LineLL *pLineWalk, MarkPlanarityControlStruct_t &control ) { Assert( pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->planarity == POINT_ONPLANE ); //Assert( !pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->debugdata.bVisited ); DBG_ONLY( pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->debugdata.bVisited = true; ); //onplane's are only allowed to crawl within their polygon. So if the very next line is onplane, recurse. Alive has no work to do, and dead will be picked up later in the dead-crawling algorithm if( pLineWalk->pLine->planarity == LINE_ALIVE ) //not already traversed { Assert( !pLineWalk->pLine->debugdata.bTested && !pLineWalk->pLine->debugdata.bIsNew ); //DBG_ONLY( pLineWalk->pLine->debugdata.bTested = true; ); GeneratePolyhedronFromPlanes_Point *pConnectedPoint = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]; if( (pConnectedPoint->fPlaneDist > control.fOnPlaneEpsilon) && !Recursive_CanOnPlanePolyCrawlDead( pLineWalk, control ) ) { //do nothing, for consistency we want to be sure we traverse from the dead point to here } else if( pConnectedPoint->fPlaneDist >= control.fNegativeOnPlaneEpsilon ) { //point onplane, line is onplane, line is part of the new polygon pConnectedPoint->planarity = POINT_ONPLANE; pLineWalk->pLine->planarity = LINE_ONPLANE; Recursive_MarkPlanarity_OnPlane( pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pNext, control ); //Stitch the line we just traversed into the polygon as-is { //remove from existing dead polygon int iDeadSide = 1 - pLineWalk->iReferenceIndex; pLineWalk->pLine->PolygonLineLinks[iDeadSide].pNext->pPrev = pLineWalk->pLine->PolygonLineLinks[iDeadSide].pPrev; pLineWalk->pLine->PolygonLineLinks[iDeadSide].pPrev->pNext = pLineWalk->pLine->PolygonLineLinks[iDeadSide].pNext; /*if( pLineWalk->pLine->PolygonLineLinks[iDeadSide].pNext == &pLineWalk->pLine->PolygonLineLinks[iDeadSide] ) { //this was the last line in the polygon pLineWalk->pLine->pPolygons[iDeadSide]->pLines = NULL; pLineWalk->pLine->pPolygons[iDeadSide]->bDead = true; } else*/ { pLineWalk->pLine->pPolygons[iDeadSide]->pLines = pLineWalk->pLine->PolygonLineLinks[iDeadSide].pNext; } //now replace it with the new polygon pLineWalk->pLine->pPolygons[iDeadSide] = control.pNewPolygon; //insert before pActivePolyLine pLineWalk->pLine->PolygonLineLinks[iDeadSide].pPrev = control.pActivePolyLine->pPrev; control.pActivePolyLine->pPrev->pNext = &pLineWalk->pLine->PolygonLineLinks[iDeadSide]; pLineWalk->pLine->PolygonLineLinks[iDeadSide].pNext = control.pActivePolyLine; control.pActivePolyLine->pPrev = &pLineWalk->pLine->PolygonLineLinks[iDeadSide]; } } else { //point alive, line is alive, root point stitched into the new polygon GeneratePolyhedronFromPlanes_Line *pCompletedPolyLine = control.pActivePolyLine->pLine; //We now should have everything we need to finish constructing pCompletedLine { //pCompletedLine->pLine->pPoints[0] should already have valid values that just need to be linked back in pCompletedPolyLine->PointLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PointLineLinks[0]; pCompletedPolyLine->PointLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PointLineLinks[0]; pCompletedPolyLine->pPoints[1] = pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]; //root point is endpoint for completed polygon line pCompletedPolyLine->PointLineLinks[1].pPrev = pLineWalk->pPrev; pCompletedPolyLine->PointLineLinks[1].pNext = pLineWalk; pCompletedPolyLine->PointLineLinks[1].pNext->pPrev = &pCompletedPolyLine->PointLineLinks[1]; pCompletedPolyLine->PointLineLinks[1].pPrev->pNext = &pCompletedPolyLine->PointLineLinks[1]; //link outwardly into the patched up polygon pCompletedPolyLine->pPolygons[0] = pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex]; //left side of line going to live point is the polygon that will survive the planar clip pCompletedPolyLine->PolygonLineLinks[0].pPrev = &pLineWalk->pLine->PolygonLineLinks[1 - pLineWalk->iReferenceIndex]; pCompletedPolyLine->PolygonLineLinks[0].pNext = pCompletedPolyLine->PolygonLineLinks[0].pPrev->pNext; pCompletedPolyLine->PolygonLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PolygonLineLinks[0]; pCompletedPolyLine->PolygonLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PolygonLineLinks[0]; } #if defined( DBGFLAG_ASSERT ) if( pCompletedPolyLine->pPoints[0] && pCompletedPolyLine->pPoints[1] ) { fltx4 vLineTemp = SubSIMD( pCompletedPolyLine->pPoints[1]->ptPosition, pCompletedPolyLine->pPoints[0]->ptPosition ); AssertMsg( (SubFloat( vLineTemp, 0 ) != 0.0f) || (SubFloat( vLineTemp, 1 ) != 0.0f) || (SubFloat( vLineTemp, 2 ) != 0.0f), "Created zero length line" ); } #endif //point 0 will be filled in as we backtrack to a dead point that recursed into Recursive_MarkPlanarity_OnPlane(). If we recursed from another onplane point, we'll be recycling its line MarkPlanarity_CreateNewPolyLine( control ); } } /*if( pLineWalk->pLine->planarity == LINE_DEAD ) { //left polygon is dead #if defined( DBGFLAG_ASSERT ) //make sure { //walk the polygon and ensure it should be dead GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalkHead = &pLineWalk->pLine->PolygonLineLinks[1 - pLineWalk->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalk = pDebugLineWalkHead; do { Assert( pDebugLineWalk->pLine->planarity != LINE_ALIVE ); pDebugLineWalk = pDebugLineWalk->pNext; } while (pDebugLineWalk != pDebugLineWalkHead); } #endif Assert( !pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex]->bDead ); //not already marked dead pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex]->bDead = true; }*/ } bool Recursive_CanOnPlanePolyCrawlDead( GeneratePolyhedronFromPlanes_LineLL *pLineWalk, MarkPlanarityControlStruct_t &control ) { pLineWalk = pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pNext; if( pLineWalk->pLine->planarity == LINE_ALIVE ) //not already traversed { GeneratePolyhedronFromPlanes_Point *pConnectedPoint = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]; if( pConnectedPoint->fPlaneDist > control.fOnPlaneEpsilon ) { GeneratePolyhedronFromPlanes_LineLL *pTestAlive = pLineWalk->pNext; if( pTestAlive->pLine->pPoints[pTestAlive->iReferenceIndex]->fPlaneDist < control.fNegativeOnPlaneEpsilon ) { //couldn't have possibly crawled here from the other direction if we continue the onplane streak return Recursive_CanOnPlanePolyCrawlDead( pLineWalk, control ); } } if( pConnectedPoint->fPlaneDist >= control.fNegativeOnPlaneEpsilon ) { GeneratePolyhedronFromPlanes_LineLL *pTestAlive = pLineWalk->pNext; if( pTestAlive->pLine->pPoints[pTestAlive->iReferenceIndex]->fPlaneDist < control.fNegativeOnPlaneEpsilon ) { //couldn't have possibly crawled here from the other direction return true; } } } return false; } void RecomputePolygonSurfaceNormal( GeneratePolyhedronFromPlanes_Polygon *pPolygon ) { Vector vAggregateNormal = vec3_origin; GeneratePolyhedronFromPlanes_LineLL *pLineWalkHead = pPolygon->pLines; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pLineWalkHead->pPrev; Vector vLastLine; fltx4 vLineTemp = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); pLineWalk = pLineWalk->pNext; vLastLine.Init( SubFloat( vLineTemp, 0 ), SubFloat( vLineTemp, 1 ), SubFloat( vLineTemp, 2 ) ); do { vLineTemp = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); Vector vThisLine( SubFloat( vLineTemp, 0 ), SubFloat( vLineTemp, 1 ), SubFloat( vLineTemp, 2 ) ); Vector vCross = vThisLine.Cross( vLastLine ); vAggregateNormal += vThisLine.Cross( vLastLine ); //intentionally not normalizing until the end. Larger lines deserve more influence in the result vLastLine = vThisLine; pLineWalk = pLineWalk->pNext; } while ( pLineWalk != pLineWalkHead ); vAggregateNormal.NormalizeInPlace(); pPolygon->vSurfaceNormal = vAggregateNormal; } CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pAllPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pAllLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, int iPointCount, const fltx4 *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory, fltx4 vShiftResultPositions ) { //const float fNegativeOnPlaneEpsilon = -fOnPlaneEpsilon; const float fOnPlaneEpsilonSquared = fOnPlaneEpsilon * fOnPlaneEpsilon; #ifdef DBGFLAG_ASSERT GeneratePolyhedronFromPlanes_Point *pStartPoint = NULL; static int iPolyhedronClipCount = 0; ++iPolyhedronClipCount; #endif size_t iStackMemorySize = (64 * 1024); //start off trying to allocate 128k void *pStackMemory = stackalloc( iStackMemorySize ); while( pStackMemory == NULL ) { iStackMemorySize = iStackMemorySize >> 1; pStackMemory = stackalloc( iStackMemorySize ); } CStackMemoryDispenser memoryDispenser( pStackMemory, iStackMemorySize ); //Collections of dead pointers for reallocation, data in them shouldn't be touched until the current loop iteration is done. GeneratePolyhedronFromPlanes_UnorderedPointLL *pDeadPointCollection = NULL; #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS GeneratePolyhedronFromPlanes_UnorderedLineLL *pDeadLineCollection = NULL; GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDeadPolygonCollection = NULL; #endif CStackItemDispenser pointAllocator( memoryDispenser ); CStackItemDispenser lineAllocator( memoryDispenser ); CStackItemDispenser polygonAllocator( memoryDispenser ); #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS CClipLinkedGeometryDestructors destructors( iPointCount, pAllPolygons, pDeadPolygonCollection, pAllLines, pDeadLineCollection, pAllPoints, pDeadPointCollection ); #else CClipLinkedGeometryDestructors destructors( iPointCount, pAllPolygons, polygonAllocator, pAllLines, lineAllocator, pAllPoints, pDeadPointCollection ); #endif DBG_ONLY( destructors.DebugCutHistory.AddToTail( ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false, vShiftResultPositions ) ) ); DBG_ONLY( destructors.DebugCutPlaneIndex.AddToTail( -1 ) ); for( int iCurrentPlane = 0; iCurrentPlane != iPlaneCount; ++iCurrentPlane ) { #if defined( DBGFLAG_ASSERT ) destructors.bDebugTrigger = (g_iDebugPolyhedronClipProcess == iCurrentPlane); if( destructors.bDebugTrigger ) { g_iDebugPolyhedronClipProcess = -1; //remove the need for cleanup code wherever someone wanted this to break; } #endif //clear out work variables { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; do { pActivePolygonWalk->polygon.bDead = false; pActivePolygonWalk->polygon.bHasNewPoints = false; pActivePolygonWalk->polygon.bMovedExistingPoints = false; DBG_ONLY( pActivePolygonWalk->polygon.debugdata.Reset(); ); pActivePolygonWalk = pActivePolygonWalk->pNext; } while( pActivePolygonWalk ); GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; do { pActiveLineWalk->line.planarity = LINE_ALIVE; pActiveLineWalk->line.bNewLengthThisPass = false; DBG_ONLY( pActiveLineWalk->line.debugdata.Reset(); ); pActiveLineWalk = pActiveLineWalk->pNext; } while( pActiveLineWalk ); GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; do { pActivePointWalk->point.planarity = POINT_ALIVE; DBG_ONLY( pActivePointWalk->point.debugdata.Reset(); ); pActivePointWalk = pActivePointWalk->pNext; } while( pActivePointWalk ); } while( pDeadPointCollection != NULL ) { GeneratePolyhedronFromPlanes_UnorderedPointLL *pFree = pDeadPointCollection; pDeadPointCollection = pDeadPointCollection->pNext; pointAllocator.Free( pFree ); } #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS while( pDeadLineCollection != NULL ) { GeneratePolyhedronFromPlanes_UnorderedLineLL *pFree = pDeadLineCollection; pDeadLineCollection = pDeadLineCollection->pNext; lineAllocator.Free( pFree ); } while( pDeadPolygonCollection != NULL ) { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pFree = pDeadPolygonCollection; pDeadPolygonCollection = pDeadPolygonCollection->pNext; polygonAllocator.Free( pFree ); } #endif #if defined( DBGFLAG_ASSERT ) int iPatchMethod = -1; #endif //find point distances from the plane ComputePlanarDistances( pAllPoints, iPointCount, pOutwardFacingPlanes[iCurrentPlane] ); //find "most dead" point. We'll be using that as a starting point for an algorithm that walks the edge of the new polygon //this walk method lets us categorize points without getting into impossible situations where 2 diagonal corners of a quad are dead, but the other points are onplane/alive GeneratePolyhedronFromPlanes_Point *pMostDeadPoint = &pAllPoints->point; float fMostDeadPointDist = pAllPoints->point.fPlaneDist; { GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints->pNext; do { if( pActivePointWalk->point.fPlaneDist > fMostDeadPointDist ) { pMostDeadPoint = &pActivePointWalk->point; fMostDeadPointDist = pActivePointWalk->point.fPlaneDist; } pActivePointWalk = pActivePointWalk->pNext; } while( pActivePointWalk ); } if( fMostDeadPointDist <= fOnPlaneEpsilon ) { //no cuts made continue; } Vector vSurfaceNormal( SubFloat( pOutwardFacingPlanes[iCurrentPlane], 0 ), SubFloat( pOutwardFacingPlanes[iCurrentPlane], 1 ), SubFloat( pOutwardFacingPlanes[iCurrentPlane], 2 ) ); GeneratePolyhedronFromPlanes_Polygon *pNewPolygon = AllocatePolygon( pAllPolygons, polygonAllocator, vSurfaceNormal ); pMostDeadPoint->planarity = POINT_DEAD; { MarkPlanarityControlStruct_t control( pNewPolygon, lineAllocator, pAllLines, pointAllocator, pAllPoints, iPointCount, fOnPlaneEpsilon ); #if defined( DBGFLAG_ASSERT ) GeneratePolyhedronFromPlanes_LineLL *pStartLine = control.pActivePolyLine; control.vCutPlane = pOutwardFacingPlanes[iCurrentPlane]; #endif Recursive_MarkPlanarity_Dead( pMostDeadPoint->pConnectedLines, control ); //it's possible that the crawling algorithm didn't encounter any live points if the plane didn't cut any lines (All intersections between plane and mesh were at an existing vertex) if( control.bAllPointsDead ) { //doublecheck GeneratePolyhedronFromPlanes_UnorderedPointLL *pPointWalk = pAllPoints; while( pPointWalk ) { if( pPointWalk->point.planarity == POINT_ALIVE ) { control.bAllPointsDead = false; break; } pPointWalk = pPointWalk->pNext; } } if( control.bAllPointsDead ) //all the points either died or are on the plane, no polyhedron left at all { #ifdef DBGFLAG_ASSERT GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints->pNext; do { Assert( pActivePointWalk->point.planarity != POINT_ALIVE ); pActivePointWalk = pActivePointWalk->pNext; } while( pActivePointWalk ); #endif #if defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) Assert_DumpPolyhedron( g_bDumpNullPolyhedrons == false ); //if someone set it to true, we'll dump the polyhedron then halt #endif return NULL; } Assert( (control.pActivePolyLine->pNext == pStartLine) || (pStartLine->pLine->pPoints[0] != NULL) ); Assert( (pStartLine->pLine->pPoints[1] != NULL) || (pStartLine == control.pActivePolyLine) ); //search/mark dead polygons, there should be a way to do this in the crawl algorithm { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygonWalk = pAllPolygons; while( pPolygonWalk ) { if( &pPolygonWalk->polygon != control.pNewPolygon ) { bool bDead = true; GeneratePolyhedronFromPlanes_LineLL *pStartLine = pPolygonWalk->polygon.pLines; GeneratePolyhedronFromPlanes_LineLL *pWalkLine = pStartLine; do { Assert( pWalkLine->pLine->pPolygons[pWalkLine->iReferenceIndex] == &pPolygonWalk->polygon ); if( pWalkLine->pLine->planarity & LINE_ALIVE ) { bDead = false; break; } pWalkLine = pWalkLine->pNext; } while ( pWalkLine != pStartLine ); if( bDead ) { pPolygonWalk->polygon.bDead = true; } } pPolygonWalk = pPolygonWalk->pNext; } } GeneratePolyhedronFromPlanes_LineLL *pFinal = control.pActivePolyLine; if( pFinal->pNext->pLine->pPoints[1 - pFinal->pNext->iReferenceIndex] == NULL ) { //last line redundant, copy partial data into first line GeneratePolyhedronFromPlanes_LineLL *pFirst = pFinal->pNext; Assert_DumpPolyhedron( pFinal != pFirst ); Assert_DumpPolyhedron( pFirst->pLine->pPoints[pFirst->iReferenceIndex] != NULL ); Assert_DumpPolyhedron( pFinal->pLine->pPoints[1 - pFinal->iReferenceIndex] != NULL ); //link over redundant last line pFirst->pPrev = pFinal->pPrev; pFinal->pPrev->pNext = pFirst; //fill in first point of first line pFirst->pLine->pPoints[1 - pFirst->iReferenceIndex] = pFinal->pLine->pPoints[1 - pFinal->iReferenceIndex]; pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex].pNext = pFinal->pLine->PointLineLinks[1 - pFinal->iReferenceIndex].pNext; pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex].pPrev = pFinal->pLine->PointLineLinks[1 - pFinal->iReferenceIndex].pPrev; pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex].pNext->pPrev = &pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex]; pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex].pPrev->pNext = &pFirst->pLine->PointLineLinks[1 - pFirst->iReferenceIndex]; control.pNewPolygon->pLines = pFirst; DestructLine( pFinal->pLine, destructors ); #if defined( DBGFLAG_ASSERT ) iPatchMethod = 1; { fltx4 vLineTemp = SubSIMD( pFirst->pLine->pPoints[1]->ptPosition, pFirst->pLine->pPoints[0]->ptPosition ); AssertMsg( (SubFloat( vLineTemp, 0 ) != 0.0f) || (SubFloat( vLineTemp, 1 ) != 0.0f) || (SubFloat( vLineTemp, 2 ) != 0.0f), "Created zero length line" ); } #endif } else { if( pFinal->pLine->pPoints[1 - pFinal->iReferenceIndex] != pFinal->pNext->pLine->pPoints[1 - pFinal->pNext->iReferenceIndex] ) { //link up the last line to the first line GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pFinal->pNext; GeneratePolyhedronFromPlanes_Line *pCompletedPolyLine = pFinal->pLine; //We now should have everything we need to finish constructing pCompletedLine { //pCompletedLine->pLine->pPoints[0] should already have valid values that just need to be linked back in pCompletedPolyLine->PointLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PointLineLinks[0]; pCompletedPolyLine->PointLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PointLineLinks[0]; pCompletedPolyLine->pPoints[1] = pFirstLine->pLine->pPoints[1 - pFirstLine->iReferenceIndex]; pCompletedPolyLine->PointLineLinks[1].pPrev = &pFirstLine->pLine->PointLineLinks[1 - pFirstLine->iReferenceIndex]; pCompletedPolyLine->PointLineLinks[1].pNext = pFirstLine->pLine->PointLineLinks[1 - pFirstLine->iReferenceIndex].pNext; Assert_DumpPolyhedron( pCompletedPolyLine->PointLineLinks[1].pNext->pLine->planarity != LINE_DEAD ); pCompletedPolyLine->PointLineLinks[1].pNext->pPrev = &pCompletedPolyLine->PointLineLinks[1]; pCompletedPolyLine->PointLineLinks[1].pPrev->pNext = &pCompletedPolyLine->PointLineLinks[1]; GeneratePolyhedronFromPlanes_LineLL *pLivingPolySide = pCompletedPolyLine->PointLineLinks[1].pNext; pLivingPolySide = &pLivingPolySide->pLine->PolygonLineLinks[1 - pLivingPolySide->iReferenceIndex]; //convert the link from a point space to polygon space //link outwardly into the patched up polygon pCompletedPolyLine->pPolygons[0] = pLivingPolySide->pLine->pPolygons[pLivingPolySide->iReferenceIndex]; pCompletedPolyLine->PolygonLineLinks[0].pPrev = &pLivingPolySide->pLine->PolygonLineLinks[pLivingPolySide->iReferenceIndex]; pCompletedPolyLine->PolygonLineLinks[0].pNext = pCompletedPolyLine->PolygonLineLinks[0].pPrev->pNext; pCompletedPolyLine->PolygonLineLinks[0].pNext->pPrev = &pCompletedPolyLine->PolygonLineLinks[0]; pCompletedPolyLine->PolygonLineLinks[0].pPrev->pNext = &pCompletedPolyLine->PolygonLineLinks[0]; Assert_DumpPolyhedron( pCompletedPolyLine->pPolygons[0] != pCompletedPolyLine->pPolygons[1] ); #if defined( DBGFLAG_ASSERT ) iPatchMethod = 2; { fltx4 vLineTemp = SubSIMD( pCompletedPolyLine->pPoints[1]->ptPosition, pCompletedPolyLine->pPoints[0]->ptPosition ); AssertMsg( (SubFloat( vLineTemp, 0 ) != 0.0f) || (SubFloat( vLineTemp, 1 ) != 0.0f) || (SubFloat( vLineTemp, 2 ) != 0.0f), "Created zero length line" ); } #endif } } else { //first and last lines were on-plane control.pNewPolygon->pLines = control.pActivePolyLine->pNext; control.pActivePolyLine->pNext->pPrev = control.pActivePolyLine->pPrev; control.pActivePolyLine->pPrev->pNext = control.pActivePolyLine->pNext; DestructLine( control.pActivePolyLine->pLine, destructors ); #if defined( DBGFLAG_ASSERT ) iPatchMethod = 3; #endif } } #if defined( DBGFLAG_ASSERT ) { GeneratePolyhedronFromPlanes_UnorderedLineLL *pLineWalk = pAllLines; do { Assert( pLineWalk->line.pPoints[0] != NULL && pLineWalk->line.pPoints[1] != NULL ); pLineWalk = pLineWalk->pNext; } while (pLineWalk); } #endif } #ifdef DBGFLAG_ASSERT destructors.PlaneCutHistory.AddToTail( (float *)&pOutwardFacingPlanes[iCurrentPlane] ); #endif //remove dead lines { GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; do { if( pActiveLineWalk->line.planarity == LINE_DEAD ) { UnlinkLine( pActiveLineWalk ); //move the line to the dead list pActiveLineWalk = DestructLine( pActiveLineWalk, destructors ); } else { pActiveLineWalk = pActiveLineWalk->pNext; } } while( pActiveLineWalk ); } //remove dead polygons { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; do { if( pActivePolygonWalk->polygon.bDead ) { pActivePolygonWalk = DestructPolygon( pActivePolygonWalk, destructors ); } else { pActivePolygonWalk = pActivePolygonWalk->pNext; } } while( pActivePolygonWalk ); } //remove dead points { GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; do { if( pActivePointWalk->point.planarity == POINT_DEAD ) { pActivePointWalk = DestructPoint( pActivePointWalk, destructors ); } else { pActivePointWalk = pActivePointWalk->pNext; } } while( pActivePointWalk ); } #if defined( DEBUG_POLYHEDRON_CONVERSION ) if( g_pPolyhedronCarvingDebugStepCallback != NULL ) { CPolyhedron *pTestPolyhedron = ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false, vShiftResultPositions ); Assert_DumpPolyhedron( pTestPolyhedron ); if( !g_pPolyhedronCarvingDebugStepCallback( pTestPolyhedron ) ) { VMatrix matScaleCentered; matScaleCentered.Identity(); matScaleCentered[0][0] = matScaleCentered[1][1] = matScaleCentered[2][2] = 10.0f; matScaleCentered.SetTranslation( -pTestPolyhedron->Center() * 10.0f ); DumpPolyhedronToGLView( pTestPolyhedron, "AssertPolyhedron.txt", &matScaleCentered, "wb" ); AssertMsg_DumpPolyhedron( false, "Outside conversion failed" ); } pTestPolyhedron->Release(); } #endif //When clipping a 2D polyhedron, the result of any clipping is a faux 3rd polygon which is degenerate (2 lines, both connected to the same set of points). Remove it now if( pNewPolygon->pLines->pNext == pNewPolygon->pLines->pPrev ) { AssertMsg_DumpPolyhedron( (pAllPolygons->pNext != NULL) && //more than 1 (pAllPolygons->pNext->pNext != NULL) && //more than 2 (pAllPolygons->pNext->pNext->pNext == NULL), //exactly 3 polygons, and no more "This case should only pop up if the input to the last cutting pass was a 2 sided polyhedron" ); RemoveDegeneratePolygon( pNewPolygon, destructors ); pNewPolygon = NULL; } //remove super-short lines { //ideally this behavior should be integrated with the Recursive_MarkPlanarity_Dead() behavior to avoid creating them in the first place. //But choosing safety over performance for now since Recursive_MarkPlanarity_Dead() is stable right now GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; do { if( pActiveLineWalk->line.bNewLengthThisPass ) { fltx4 vDiff = SubSIMD( pActiveLineWalk->line.pPoints[0]->ptPosition, pActiveLineWalk->line.pPoints[1]->ptPosition ); vDiff = MulSIMD( vDiff, vDiff ); float fLengthSqr = SubFloat( vDiff, 0 ) + SubFloat( vDiff, 1 ) + SubFloat( vDiff, 2 ); if( fLengthSqr <= fOnPlaneEpsilonSquared ) //eq for 0.0 epsilon case { //this line needs to go. But removing it might have repercussions GeneratePolyhedronFromPlanes_Polygon *pPolygons[2] = { pActiveLineWalk->line.pPolygons[0], pActiveLineWalk->line.pPolygons[1] }; GeneratePolyhedronFromPlanes_Line *pShortLine = &pActiveLineWalk->line; GeneratePolyhedronFromPlanes_Point *pDeadPoint = pShortLine->pPoints[1]; GeneratePolyhedronFromPlanes_Point *pKeepPoint = pShortLine->pPoints[0]; //reposition point 0 to be the midpoint? In my experience it's best to just leave it alone when dealing with small distances. //relink point 1 links to point 0 { //update point pointers { GeneratePolyhedronFromPlanes_LineLL *pPointLineWalkHead = pDeadPoint->pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pPointLineWalk = pPointLineWalkHead; do { pPointLineWalk->pLine->pPoints[1- pPointLineWalk->iReferenceIndex] = pKeepPoint; pPointLineWalk->pLine->pPolygons[pPointLineWalk->iReferenceIndex]->bMovedExistingPoints = true;//also inform touching polygons that we're changing their geometry pPointLineWalk = pPointLineWalk->pNext; } while( pPointLineWalk != pPointLineWalkHead ); //except the line we're killing pShortLine->pPoints[1] = pDeadPoint; } //insert all lines from point 1 as a fan between point 0 prev and the short line. pShortLine->PointLineLinks[1].pNext->pPrev = pShortLine->PointLineLinks[0].pPrev; pShortLine->PointLineLinks[0].pPrev->pNext = pShortLine->PointLineLinks[1].pNext; pShortLine->PointLineLinks[1].pPrev->pNext = &pShortLine->PointLineLinks[0]; pShortLine->PointLineLinks[0].pPrev = pShortLine->PointLineLinks[1].pPrev; pShortLine->PointLineLinks[1].pNext = &pShortLine->PointLineLinks[1]; pShortLine->PointLineLinks[1].pPrev = &pShortLine->PointLineLinks[1]; } #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pDeadPoint->planarity = POINT_DEAD; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif UnlinkLine( pShortLine ); pActiveLineWalk = DestructLine( pShortLine, destructors ); DestructPoint( pDeadPoint, destructors ); for( int i = 0; i != 2; ++i ) { if( pPolygons[i]->pLines->pNext == pPolygons[i]->pLines->pPrev ) { //this polygon is dead if( pAllPolygons->pNext->pNext == NULL ) { //It's conceivably possible to either start or collapse down to a 2D polyhedron with every line cut save one. //We can't have 1 polygon, and I don't want to mentally run down what would happen below if we tried to collapse down to 1. //So just bail now #if defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) Assert_DumpPolyhedron( g_bDumpNullPolyhedrons == false ); //if someone set it to true, we'll dump the polyhedron then halt #endif return NULL; } if( pPolygons[i] == pNewPolygon ) { pNewPolygon = NULL; } if( pActiveLineWalk ) { GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalkNext = pActiveLineWalk->pNext; GeneratePolyhedronFromPlanes_Line *pTestDelete = &pActiveLineWalk->line; if( pTestDelete == RemoveDegeneratePolygon( pPolygons[i], destructors ) ) { pActiveLineWalk = pActiveLineWalkNext; } } else { RemoveDegeneratePolygon( pPolygons[i], destructors ); } } } continue; //pActiveLineWalk already updated } } pActiveLineWalk = pActiveLineWalk->pNext; } while( pActiveLineWalk ); } #if defined( DEBUG_POLYHEDRON_CONVERSION ) if( g_pPolyhedronCarvingDebugStepCallback != NULL ) { CPolyhedron *pTestPolyhedron = ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false, vShiftResultPositions ); Assert_DumpPolyhedron( pTestPolyhedron ); if( !g_pPolyhedronCarvingDebugStepCallback( pTestPolyhedron ) ) { VMatrix matScaleCentered; matScaleCentered.Identity(); matScaleCentered[0][0] = matScaleCentered[1][1] = matScaleCentered[2][2] = 10.0f; matScaleCentered.SetTranslation( -pTestPolyhedron->Center() * 10.0f ); DumpPolyhedronToGLView( pTestPolyhedron, "AssertPolyhedron.txt", &matScaleCentered, "wb" ); AssertMsg_DumpPolyhedron( false, "Outside conversion failed" ); } pTestPolyhedron->Release(); } #endif AssertMsg( pAllPolygons->pNext != NULL, "A polyhedron must have at least 2 sides to be a 2D Polyhedron, and at least 4 to be a 3D polyhedron" ); //if any polygons had their geometry adjusted (or new poly uses existing on-plane points), we need to recompute the surface normal and check for degenerate points { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; if( pAllPolygons->pNext->pNext != NULL ) { //for 3D polyhedrons, remove degenerate points and recompute normals do { if( pActivePolygonWalk->polygon.bMovedExistingPoints || pActivePolygonWalk->polygon.bHasNewPoints ) { RemoveDegeneratePoints( &pActivePolygonWalk->polygon, destructors ); RecomputePolygonSurfaceNormal( &pActivePolygonWalk->polygon ); } pActivePolygonWalk = pActivePolygonWalk->pNext; } while( pActivePolygonWalk ); } else { //for 2D polyhedrons, only recompute normals. RemoveDegeneratePoints() is not safe for 2D polyhedrons as it assumes the shape is 3 dimensional to avoid angle computations and checks do { if( pActivePolygonWalk->polygon.bMovedExistingPoints || pActivePolygonWalk->polygon.bHasNewPoints ) { RecomputePolygonSurfaceNormal( &pActivePolygonWalk->polygon ); } pActivePolygonWalk = pActivePolygonWalk->pNext; } while( pActivePolygonWalk ); } } #if defined( DEBUG_POLYHEDRON_CONVERSION ) if( g_pPolyhedronCarvingDebugStepCallback != NULL ) { CPolyhedron *pTestPolyhedron = ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false, vShiftResultPositions ); Assert_DumpPolyhedron( pTestPolyhedron ); if( !g_pPolyhedronCarvingDebugStepCallback( pTestPolyhedron ) ) { VMatrix matScaleCentered; matScaleCentered.Identity(); matScaleCentered[0][0] = matScaleCentered[1][1] = matScaleCentered[2][2] = 10.0f; matScaleCentered.SetTranslation( -pTestPolyhedron->Center() * 10.0f ); DumpPolyhedronToGLView( pTestPolyhedron, "AssertPolyhedron.txt", &matScaleCentered, "wb" ); AssertMsg_DumpPolyhedron( false, "Outside conversion failed" ); } pTestPolyhedron->Release(); } #endif //remove points which, although seemingly useful. Actually create concave shapes { GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; do { if( pActivePolygonWalk->polygon.bHasNewPoints ) { Vector vTestNormal = pActivePolygonWalk->polygon.vSurfaceNormal; GeneratePolyhedronFromPlanes_LineLL *pLineWalkHead = pActivePolygonWalk->polygon.pLines; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pLineWalkHead->pPrev; Vector vLastLine; fltx4 vLineTemp = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); pLineWalk = pLineWalk->pNext; vLastLine.Init( SubFloat( vLineTemp, 0 ), SubFloat( vLineTemp, 1 ), SubFloat( vLineTemp, 2 ) ); do { vLineTemp = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); Vector vThisLine( SubFloat( vLineTemp, 0 ), SubFloat( vLineTemp, 1 ), SubFloat( vLineTemp, 2 ) ); Vector vCross = vThisLine.Cross( vLastLine ); if( vCross.Dot( vTestNormal ) <= 0.0f ) { //this point is a troublemaker GeneratePolyhedronFromPlanes_Point *pProblemPoint = pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]; if( pLineWalk->pPrev == pLineWalkHead ) { //about to delete our start/stop point. Avoid infinite loops pLineWalkHead = pLineWalk; } #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pProblemPoint->planarity = POINT_DEAD; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif #if defined( DBGFLAG_ASSERT ) { GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalkHead = pProblemPoint->pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalk = pDebugLineWalkHead; do { Assert( (pDebugLineWalk->pLine->pPolygons[0] == &pActivePolygonWalk->polygon) || (pDebugLineWalk->pLine->pPolygons[1] == &pActivePolygonWalk->polygon) || (pDebugLineWalk->pLine->pPolygons[0]->vSurfaceNormal.Dot( pDebugLineWalk->pLine->pPolygons[1]->vSurfaceNormal ) > 0.95f) ); pDebugLineWalk = pDebugLineWalk->pNext; } while( pDebugLineWalk != pDebugLineWalkHead ); } #endif //what we're going to do is eliminate the problem point and every line connected to it save one. //That one saved line will take the place of both lines on the new poly edge connected to this point //As part of eliminating all the lines connected to this point, we need to merge polygons separated by those lines. //To do that we'll just take the first polygon we encounter and stitch the other polygon lines into it. GeneratePolyhedronFromPlanes_LineLL *pPatchStop = &pLineWalk->pLine->PolygonLineLinks[1 - pLineWalk->iReferenceIndex]; //where the polygon patching algorithm should stop GeneratePolyhedronFromPlanes_LineLL *pPatchWalk = pLineWalk->pPrev; //rewind to prev line pPatchWalk = &pPatchWalk->pLine->PolygonLineLinks[1 - pPatchWalk->iReferenceIndex]; //convert to the non-new polygon walk space, pPatchWalk is the on-polygon line we'll be killing GeneratePolyhedronFromPlanes_Polygon *pPatchPolygon = pPatchWalk->pLine->pPolygons[pPatchWalk->iReferenceIndex]; //pPatchStop doubles as the surviving on-polygon line. Go ahead and do all the fix ups necessary to allow us to clean up the dead lines as we come across them pPatchStop->pLine->pPoints[pPatchStop->iReferenceIndex] = pPatchWalk->pLine->pPoints[pPatchWalk->iReferenceIndex]; { GeneratePolyhedronFromPlanes_LineLL *pRelink = &pPatchStop->pLine->PointLineLinks[pPatchStop->iReferenceIndex]; //unlink pPatchStop from dead point pRelink->pNext->pPrev = pRelink->pPrev; pRelink->pPrev->pNext = pRelink->pNext; //relink into pPatchWalk's living point (insert arbitrarily before pPatchWalk) pRelink->pNext = &pPatchWalk->pLine->PointLineLinks[pPatchWalk->iReferenceIndex]; pRelink->pPrev = pRelink->pNext->pPrev; pRelink->pNext->pPrev = pRelink; pRelink->pPrev->pNext = pRelink; } pPatchWalk = pPatchWalk->pNext; //forward to the next line that won't be dead when we're done. From here on it's a clockwise walk on this polygon until we reach a line we'll kill, then we switch to the next //go ahead and kill the dead on-polygon line { GeneratePolyhedronFromPlanes_Line *pKillLine = pPatchWalk->pPrev->pLine; #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pKillLine->planarity = LINE_DEAD; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif UnlinkLine( pKillLine ); DestructLine( pKillLine, destructors ); } do { if( pPatchWalk->pLine->pPoints[pPatchWalk->iReferenceIndex] == pProblemPoint ) { //done walking this polygon, stitch it to the next GeneratePolyhedronFromPlanes_Line *pKillLine = pPatchWalk->pLine; GeneratePolyhedronFromPlanes_Polygon *pKillPolygon = pPatchWalk->pLine->pPolygons[pPatchWalk->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pPatchPrev = pPatchWalk->pPrev; pPatchWalk = pPatchWalk->pLine->PolygonLineLinks[1 - pPatchWalk->iReferenceIndex].pNext; //flip to the other side's polygon, then forward to the next line we'll keep //patching will be more straightforward if we kill all the dead stuff now #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pKillLine->planarity = LINE_DEAD; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif UnlinkLine( pKillLine ); DestructLine( pKillLine, destructors ); if( pKillPolygon != pPatchPolygon ) { #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pKillPolygon->bDead = true; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif DestructPolygon( pKillPolygon, destructors ); } //link the far end of the polygon to our start (which should continually be the first living pPatchWalk line on pPatchPolygon pPatchWalk->pPrev->pNext = pPatchPrev->pNext; pPatchPrev->pNext->pPrev = pPatchWalk->pPrev; pPatchPrev->pNext = pPatchWalk; pPatchWalk->pPrev = pPatchPrev; //at this point, the two polygons's line linkages are one large polygon except that the new polygon's line are pointing at the dead polygon which will be fixed as we continue to walk it } pPatchWalk->pLine->pPolygons[pPatchWalk->iReferenceIndex] = pPatchPolygon; pPatchWalk = pPatchWalk->pNext; } while( pPatchWalk != pPatchStop ); //final patch is just slightly different than other patches { #if defined( DBGFLAG_ASSERT ) || defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) pPatchStop->pLine->pPolygons[pPatchStop->iReferenceIndex]->bDead = true; //to avoid future asserts, also for coloration. Not useful otherwise as we're about to delete it. #endif DestructPolygon( pPatchStop->pLine->pPolygons[pPatchStop->iReferenceIndex], destructors ); pPatchStop->pLine->pPolygons[pPatchStop->iReferenceIndex] = pPatchPolygon; } DestructPoint( pProblemPoint, destructors ); Assert( pAllPolygons->pNext != NULL ); if( pAllPolygons->pNext->pNext != NULL ) { RemoveDegeneratePoints( pPatchPolygon, destructors ); } //vLastLine and vThisLine are invalidated. Luckily we just need to recompute vThisLine and it'll copy to vLastLine vLineTemp = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); vThisLine.Init( SubFloat( vLineTemp, 0 ), SubFloat( vLineTemp, 1 ), SubFloat( vLineTemp, 2 ) ); } vLastLine = vThisLine; pLineWalk = pLineWalk->pNext; } while ( pLineWalk != pLineWalkHead ); } pActivePolygonWalk = pActivePolygonWalk->pNext; } while( pActivePolygonWalk ); } #ifdef DBGFLAG_ASSERT //verify that repairs are complete { DBG_RESETWORKINGSTATECOLORS(); GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDebugPolygonWalk = pAllPolygons; do { GeneratePolyhedronFromPlanes_LineLL *pLineStart = pDebugPolygonWalk->polygon.pLines; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pLineStart; GeneratePolyhedronFromPlanes_Point *pCheckPoint = pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]; int iCount = 0; //walk around the polygon according to its line links do { Assert_DumpPolyhedron( pLineWalk->pLine->planarity != LINE_DEAD ); Assert_DumpPolyhedron( (pLineWalk->pLine->pPoints[0]->planarity != POINT_DEAD) && (pLineWalk->pLine->pPoints[1]->planarity != POINT_DEAD) ); AssertMsg_DumpPolyhedron( pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex] == pCheckPoint, "Line endpoint mismatch" ); //last line's endpoint does not match up with this lines start point AssertMsg_DumpPolyhedron( pLineWalk->pLine->pPolygons[pLineWalk->iReferenceIndex] == &pDebugPolygonWalk->polygon, "Line links to wrong polygon" ); #if 1 fltx4 f4Diff1 = SubSIMD( pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); fltx4 f4Diff2 = SubSIMD( pLineWalk->pNext->pLine->pPoints[pLineWalk->pNext->iReferenceIndex]->ptPosition, pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition ); Vector vDiff1( SubFloat( f4Diff1, 0 ), SubFloat( f4Diff1, 1 ), SubFloat( f4Diff1, 2 ) ); Vector vDiff2( SubFloat( f4Diff2, 0 ), SubFloat( f4Diff2, 1 ), SubFloat( f4Diff2, 2 ) ); Vector vCross = vDiff2.Cross( vDiff1 ); float fDot = vCross.Dot( pDebugPolygonWalk->polygon.vSurfaceNormal ); AssertMsg_DumpPolyhedron( fDot >= 0.0f, "Concave polygon" ); #endif pCheckPoint = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]; pLineWalk = pLineWalk->pNext; ++iCount; } while( pLineWalk != pLineStart ); AssertMsg_DumpPolyhedron( iCount >= 3, "Degenerate polygon" ); #if 0 fltx4 vSurfaceNormal; SubFloat( vSurfaceNormal, 0 ) = pDebugPolygonWalk->polygon.vSurfaceNormal.x; SubFloat( vSurfaceNormal, 1 ) = pDebugPolygonWalk->polygon.vSurfaceNormal.y; SubFloat( vSurfaceNormal, 2 ) = pDebugPolygonWalk->polygon.vSurfaceNormal.z; SubFloat( vSurfaceNormal, 3 ) = 0.0f; #endif pLineStart = &pLineStart->pLine->PointLineLinks[1 - pLineStart->iReferenceIndex]; pLineWalk = pLineStart; //walk around the polygon again using point traversal do { AssertMsg_DumpPolyhedron( pLineWalk->pLine->pPolygons[pLineWalk->iReferenceIndex] == &pDebugPolygonWalk->polygon, "bad line/polygon linkage" ); #if 0 //at each point of each polygon, make sure every line connected to that point goes away from our normal { GeneratePolyhedronFromPlanes_LineLL *pConcavityWalk = &pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex]; GeneratePolyhedronFromPlanes_LineLL *pConcavityWalkStop = pConcavityWalk->pPrev; pConcavityWalk = pConcavityWalk->pNext; while ( pConcavityWalk != pConcavityWalkStop ) { fltx4 vLine = SubSIMD( pConcavityWalk->pLine->pPoints[pConcavityWalk->iReferenceIndex]->ptPosition, pConcavityWalk->pLine->pPoints[1 - pConcavityWalk->iReferenceIndex]->ptPosition ); fltx4 vMul = MulSIMD( vSurfaceNormal, vLine ); float fDot = SubFloat( vMul, 0 ) + SubFloat( vMul, 1 ) + SubFloat( vMul, 2 ); AssertMsg_DumpPolyhedron( fDot <= kfPointRounding, "Concave polyhedron" ); pConcavityWalk = pConcavityWalk->pNext; } } #endif pLineWalk = pLineWalk->pLine->PointLineLinks[pLineWalk->iReferenceIndex].pPrev; } while( pLineWalk != pLineStart ); pDebugPolygonWalk = pDebugPolygonWalk->pNext; } while( pDebugPolygonWalk ); bool bTwoPolygons = (pAllPolygons->pNext->pNext == NULL); GeneratePolyhedronFromPlanes_UnorderedPointLL *pDebugPointWalk = pAllPoints; do { int iCount = 0; AssertMsg_DumpPolyhedron( pDebugPointWalk->point.pConnectedLines, "Point connected to no lines after cut" ); GeneratePolyhedronFromPlanes_LineLL *pLineStart = pDebugPointWalk->point.pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pLineStart; GeneratePolyhedronFromPlanes_Point *pTestPoint = &pDebugPointWalk->point; do { fltx4 f4Diff = SubSIMD( pTestPoint->ptPosition, pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition ); Vector vDiff( SubFloat( f4Diff, 0 ), SubFloat( f4Diff, 1 ), SubFloat( f4Diff, 2 ) ); float fLengthSqr = vDiff.LengthSqr(); AssertMsg_DumpPolyhedron( fLengthSqr > 0.0f, "Generated a point on top of another" ); AssertMsg_DumpPolyhedron( pLineWalk->pLine->pPolygons[pLineWalk->iReferenceIndex] == pLineWalk->pNext->pLine->pPolygons[1 - pLineWalk->pNext->iReferenceIndex], "Bad line/polygon linkage" ); GeneratePolyhedronFromPlanes_LineLL *pSubLineWalk = pLineWalk->pNext; while( pSubLineWalk != pLineStart ) { AssertMsg_DumpPolyhedron( (pLineWalk->pLine->pPolygons[pLineWalk->iReferenceIndex] != pSubLineWalk->pLine->pPolygons[pSubLineWalk->iReferenceIndex]) && (pLineWalk->pLine->pPolygons[1 - pLineWalk->iReferenceIndex] != pSubLineWalk->pLine->pPolygons[1 - pSubLineWalk->iReferenceIndex]), "Point connected to two other points on the same polygon" ); pSubLineWalk = pSubLineWalk->pNext; } pLineWalk = pLineWalk->pNext; ++iCount; } while( pLineWalk != pLineStart ); AssertMsg_DumpPolyhedron( bTwoPolygons || iCount >= 3, "Degenerate point" ); //there can be two-sided polyhedrons where this is not a degenerate case pDebugPointWalk = pDebugPointWalk->pNext; } while( pDebugPointWalk ); GeneratePolyhedronFromPlanes_UnorderedLineLL *pDebugLineWalk = pAllLines; do { AssertMsg_DumpPolyhedron( (pDebugLineWalk->line.pPolygons[0] != NULL) && (pDebugLineWalk->line.pPolygons[1] != NULL), "There's a polygon missing" ); AssertMsg_DumpPolyhedron( pDebugLineWalk->line.pPoints[0] && pDebugLineWalk->line.pPoints[1], "Line missing a point" ); #if 0 fltx4 f4Line = SubSIMD( pDebugLineWalk->line.pPoints[0]->ptPosition, pDebugLineWalk->line.pPoints[1]->ptPosition ); Vector vLine( SubFloat( f4Line, 0 ), SubFloat( f4Line, 1 ), SubFloat( f4Line, 2 ) ); float fLength = vLine.Length(); AssertMsg_DumpPolyhedron( fLength > FLT_EPSILON, "Ridiculously short line" ); if( fLength > fOnPlaneEpsilon ) { if( fLength > 1.0f ) vLine *= 1.0f / fLength; float fDots[2] = { vLine.Dot( pDebugLineWalk->line.pPolygons[0]->vSurfaceNormal ), vLine.Dot( pDebugLineWalk->line.pPolygons[1]->vSurfaceNormal ) }; AssertMsg_DumpPolyhedron( (fabs( fDots[0] ) < (1.0f/128.0f) ) && (fabs( fDots[1] ) < (1.0f/128.0f) ), "Line is not orthogonal to plane normal it's surrounding" ); } #endif pDebugLineWalk = pDebugLineWalk->pNext; } while( pDebugLineWalk ); pStartPoint = NULL; } CPolyhedron *pHistoryPolyhedron = ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false, vShiftResultPositions ); #if defined( DEBUG_POLYHEDRON_CONVERSION ) //last bit of debugging from whatever outside source wants this stupid thing if( g_pPolyhedronCarvingDebugStepCallback != NULL ) { if( !g_pPolyhedronCarvingDebugStepCallback( pHistoryPolyhedron ) ) { VMatrix matScaleCentered; matScaleCentered.Identity(); matScaleCentered[0][0] = matScaleCentered[1][1] = matScaleCentered[2][2] = 10.0f; matScaleCentered.SetTranslation( -pHistoryPolyhedron->Center() * 10.0f ); DumpPolyhedronToGLView( pHistoryPolyhedron, "AssertPolyhedron.txt", &matScaleCentered, "wb" ); AssertMsg_DumpPolyhedron( false, "Outside conversion failed" ); } } #endif //maintain the cut history destructors.DebugCutHistory.AddToTail( pHistoryPolyhedron ); destructors.DebugCutPlaneIndex.AddToTail( iCurrentPlane ); #endif } return ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, bUseTemporaryMemory, vShiftResultPositions ); } #define STARTPOINTTOLINELINKS(iPointNum, lineindex1, iOtherPointIndex1, lineindex2, iOtherPointIndex2, lineindex3, iOtherPointIndex3 )\ StartingPointList[iPointNum].point.pConnectedLines = &StartingLineList[lineindex1].line.PointLineLinks[1 - iOtherPointIndex1];\ StartingPointList[iPointNum].point.pConnectedLines->pNext = &StartingLineList[lineindex2].line.PointLineLinks[1 - iOtherPointIndex2];\ StartingPointList[iPointNum].point.pConnectedLines->pPrev = &StartingLineList[lineindex3].line.PointLineLinks[1 - iOtherPointIndex3];\ StartingPointList[iPointNum].point.pConnectedLines->pNext->pNext = StartingPointList[iPointNum].point.pConnectedLines->pPrev;\ StartingPointList[iPointNum].point.pConnectedLines->pNext->pPrev = StartingPointList[iPointNum].point.pConnectedLines;\ StartingPointList[iPointNum].point.pConnectedLines->pPrev->pNext = StartingPointList[iPointNum].point.pConnectedLines;\ StartingPointList[iPointNum].point.pConnectedLines->pPrev->pPrev = StartingPointList[iPointNum].point.pConnectedLines->pNext; #define STARTBOXCONNECTION( linenum, point1, point2, poly1, poly2 )\ StartingLineList[linenum].line.pPoints[0] = &StartingPointList[point1].point;\ StartingLineList[linenum].line.pPoints[1] = &StartingPointList[point2].point;\ StartingLineList[linenum].line.pPolygons[0] = &StartingPolygonList[poly1].polygon;\ StartingLineList[linenum].line.pPolygons[1] = &StartingPolygonList[poly2].polygon; #define STARTPOLYGONTOLINELINKS( polynum, lineindex1, iThisPolyIndex1, lineindex2, iThisPolyIndex2, lineindex3, iThisPolyIndex3, lineindex4, iThisPolyIndex4 )\ StartingPolygonList[polynum].polygon.pLines = &StartingLineList[lineindex1].line.PolygonLineLinks[iThisPolyIndex1];\ StartingPolygonList[polynum].polygon.pLines->pNext = &StartingLineList[lineindex2].line.PolygonLineLinks[iThisPolyIndex2];\ StartingPolygonList[polynum].polygon.pLines->pPrev = &StartingLineList[lineindex4].line.PolygonLineLinks[iThisPolyIndex4];\ StartingPolygonList[polynum].polygon.pLines->pNext->pPrev = StartingPolygonList[polynum].polygon.pLines;\ StartingPolygonList[polynum].polygon.pLines->pPrev->pNext = StartingPolygonList[polynum].polygon.pLines;\ StartingPolygonList[polynum].polygon.pLines->pNext->pNext = &StartingLineList[lineindex3].line.PolygonLineLinks[iThisPolyIndex3];\ StartingPolygonList[polynum].polygon.pLines->pPrev->pPrev = StartingPolygonList[polynum].polygon.pLines->pNext->pNext;\ StartingPolygonList[polynum].polygon.pLines->pNext->pNext->pPrev = StartingPolygonList[polynum].polygon.pLines->pNext;\ StartingPolygonList[polynum].polygon.pLines->pPrev->pPrev->pNext = StartingPolygonList[polynum].polygon.pLines->pPrev; CPolyhedron *GeneratePolyhedronFromPlanes( const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ) { //this is version 2 of the polyhedron generator, version 1 made individual polygons and joined points together, some guesswork is involved and it therefore isn't a solid method //this version will start with a cube and hack away at it (retaining point connection information) to produce a polyhedron with no guesswork involved, this method should be rock solid //the polygon clipping functions we're going to use want inward facing planes const size_t kFltX4Align = sizeof( fltx4 ) - 1; uint8 *pAlignedAlloc = (uint8 *)stackalloc( (sizeof( fltx4 ) * iPlaneCount) + kFltX4Align ); pAlignedAlloc = (uint8 *)(((size_t)(pAlignedAlloc + kFltX4Align)) & ~kFltX4Align); fltx4 *pAlteredPlanes = (fltx4 *)pAlignedAlloc; for( int i = 0; i != iPlaneCount; ++i ) { SubFloat( pAlteredPlanes[i], 0 ) = -pOutwardFacingPlanes[(i * 4) + 0]; SubFloat( pAlteredPlanes[i], 1 ) = -pOutwardFacingPlanes[(i * 4) + 1]; SubFloat( pAlteredPlanes[i], 2 ) = -pOutwardFacingPlanes[(i * 4) + 2]; SubFloat( pAlteredPlanes[i], 3 ) = -pOutwardFacingPlanes[(i * 4) + 3]; } //our first goal is to find the size of a cube big enough to encapsulate all points that will be in the final polyhedron Vector vAABBMinsVec, vAABBMaxsVec; if( FindConvexShapeLooseAABB( pAlteredPlanes, iPlaneCount, &vAABBMinsVec, &vAABBMaxsVec ) == false ) return NULL; //no shape to work with apparently fltx4 vAABBMins; SubFloat( vAABBMins, 0 ) = vAABBMinsVec.x; SubFloat( vAABBMins, 1 ) = vAABBMinsVec.y; SubFloat( vAABBMins, 2 ) = vAABBMinsVec.z; SubFloat( vAABBMins, 3 ) = 1.0f; fltx4 vAABBMaxs; SubFloat( vAABBMaxs, 0 ) = vAABBMaxsVec.x; SubFloat( vAABBMaxs, 1 ) = vAABBMaxsVec.y; SubFloat( vAABBMaxs, 2 ) = vAABBMaxsVec.z; SubFloat( vAABBMaxs, 3 ) = 1.0f; #if defined( USE_WORLD_CENTERED_POSITIONS ) fltx4 vPointOffset = MulSIMD( AddSIMD( vAABBMins, vAABBMaxs ), ReplicateX4( -0.5f ) ); vPointOffset = FloorSIMD( vPointOffset ); SubFloat( vPointOffset, 3 ) = -1.0f; #endif for( int i = 0; i != iPlaneCount; ++i ) { pAlteredPlanes[i] = NegSIMD( pAlteredPlanes[i] ); } #if defined( USE_WORLD_CENTERED_POSITIONS ) //shift all the planes toward origin for( int i = 0; i != iPlaneCount; ++i ) { fltx4 vMul = MulSIMD( pAlteredPlanes[i], vPointOffset ); SubFloat( pAlteredPlanes[i], 3 ) = SubFloat( vMul, 0 ) + SubFloat( vMul, 1 ) + SubFloat( vMul, 2 ) - SubFloat( vMul, 3 ); } //shift the AABB (and all starting points) toward the origin vAABBMins = AddSIMD( vAABBMins, vPointOffset ); vAABBMaxs = AddSIMD( vAABBMaxs, vPointOffset ); fltx4 vResultOffset = NegSIMD( vPointOffset ); SubFloat( vResultOffset, 3 ) = 0.0f; #else fltx4 vResultOffset = LoadZeroSIMD(); #endif //grow the bounding box to a larger size since it's probably inaccurate a bit { fltx4 vGrow = MulSIMD( SubSIMD( vAABBMaxs, vAABBMins ), Four_PointFives ); vGrow = AddSIMD( vGrow, ReplicateX4( 10.0f ) ); SubFloat( vGrow, 3 ) = 0.0f; vAABBMins = SubSIMD( vAABBMins, vGrow ); vAABBMaxs = AddSIMD( vAABBMaxs, vGrow ); } vAABBMins = FloorSIMD( vAABBMins ); vAABBMaxs = FloorSIMD( vAABBMaxs ); vAABBMinsVec.x = SubFloat( vAABBMins, 0 ); vAABBMinsVec.y = SubFloat( vAABBMins, 1 ); vAABBMinsVec.z = SubFloat( vAABBMins, 2 ); vAABBMaxsVec.x = SubFloat( vAABBMaxs, 0 ); vAABBMaxsVec.y = SubFloat( vAABBMaxs, 1 ); vAABBMaxsVec.z = SubFloat( vAABBMaxs, 2 ); //generate our starting cube using the 2x AABB so we can start hacking away at it //create our starting box on the stack GeneratePolyhedronFromPlanes_UnorderedPolygonLL StartingPolygonList[6]; //6 polygons GeneratePolyhedronFromPlanes_UnorderedLineLL StartingLineList[12]; //12 lines GeneratePolyhedronFromPlanes_UnorderedPointLL StartingPointList[8]; //8 points for( int i = 0; i != 12; ++i ) { StartingLineList[i].line.InitLineLinks(); } //I had to work all this out on a whiteboard if it seems completely unintuitive. { SubFloat( StartingPointList[0].point.ptPosition, 0 ) = vAABBMinsVec.x; SubFloat( StartingPointList[0].point.ptPosition, 1 ) = vAABBMinsVec.y; SubFloat( StartingPointList[0].point.ptPosition, 2 ) = vAABBMinsVec.z; SubFloat( StartingPointList[0].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 0, 0, 1, 4, 1, 3, 0 ); SubFloat( StartingPointList[1].point.ptPosition, 0 ) = vAABBMinsVec.x; SubFloat( StartingPointList[1].point.ptPosition, 1 ) = vAABBMaxsVec.y; SubFloat( StartingPointList[1].point.ptPosition, 2 ) = vAABBMinsVec.z; SubFloat( StartingPointList[1].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 1, 0, 0, 1, 1, 5, 1 ); SubFloat( StartingPointList[2].point.ptPosition, 0 ) = vAABBMinsVec.x; SubFloat( StartingPointList[2].point.ptPosition, 1 ) = vAABBMinsVec.y; SubFloat( StartingPointList[2].point.ptPosition, 2 ) = vAABBMaxsVec.z; SubFloat( StartingPointList[2].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 2, 4, 0, 8, 1, 11, 0 ); SubFloat( StartingPointList[3].point.ptPosition, 0 ) = vAABBMinsVec.x; SubFloat( StartingPointList[3].point.ptPosition, 1 ) = vAABBMaxsVec.y; SubFloat( StartingPointList[3].point.ptPosition, 2 ) = vAABBMaxsVec.z; SubFloat( StartingPointList[3].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 3, 5, 0, 9, 1, 8, 0 ); SubFloat( StartingPointList[4].point.ptPosition, 0 ) = vAABBMaxsVec.x; SubFloat( StartingPointList[4].point.ptPosition, 1 ) = vAABBMinsVec.y; SubFloat( StartingPointList[4].point.ptPosition, 2 ) = vAABBMinsVec.z; SubFloat( StartingPointList[4].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 4, 2, 0, 3, 1, 7, 1 ); SubFloat( StartingPointList[5].point.ptPosition, 0 ) = vAABBMaxsVec.x; SubFloat( StartingPointList[5].point.ptPosition, 1 ) = vAABBMaxsVec.y; SubFloat( StartingPointList[5].point.ptPosition, 2 ) = vAABBMinsVec.z; SubFloat( StartingPointList[5].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 5, 1, 0, 2, 1, 6, 1 ); SubFloat( StartingPointList[6].point.ptPosition, 0 ) = vAABBMaxsVec.x; SubFloat( StartingPointList[6].point.ptPosition, 1 ) = vAABBMinsVec.y; SubFloat( StartingPointList[6].point.ptPosition, 2 ) = vAABBMaxsVec.z; SubFloat( StartingPointList[6].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 6, 7, 0, 11, 1, 10, 0 ); SubFloat( StartingPointList[7].point.ptPosition, 0 ) = vAABBMaxsVec.x; SubFloat( StartingPointList[7].point.ptPosition, 1 ) = vAABBMaxsVec.y; SubFloat( StartingPointList[7].point.ptPosition, 2 ) = vAABBMaxsVec.z; SubFloat( StartingPointList[7].point.ptPosition, 3 ) = -1.0f; STARTPOINTTOLINELINKS( 7, 6, 0, 10, 1, 9, 0 ); STARTBOXCONNECTION( 0, 0, 1, 0, 5 ); STARTBOXCONNECTION( 1, 1, 5, 1, 5 ); STARTBOXCONNECTION( 2, 5, 4, 2, 5 ); STARTBOXCONNECTION( 3, 4, 0, 3, 5 ); STARTBOXCONNECTION( 4, 0, 2, 3, 0 ); STARTBOXCONNECTION( 5, 1, 3, 0, 1 ); STARTBOXCONNECTION( 6, 5, 7, 1, 2 ); STARTBOXCONNECTION( 7, 4, 6, 2, 3 ); STARTBOXCONNECTION( 8, 2, 3, 4, 0 ); STARTBOXCONNECTION( 9, 3, 7, 4, 1 ); STARTBOXCONNECTION( 10, 7, 6, 4, 2 ); STARTBOXCONNECTION( 11, 6, 2, 4, 3 ); STARTBOXCONNECTION( 0, 0, 1, 5, 0 ); STARTBOXCONNECTION( 1, 1, 5, 5, 1 ); STARTBOXCONNECTION( 2, 5, 4, 5, 2 ); STARTBOXCONNECTION( 3, 4, 0, 5, 3 ); STARTBOXCONNECTION( 4, 0, 2, 0, 3 ); STARTBOXCONNECTION( 5, 1, 3, 1, 0 ); STARTBOXCONNECTION( 6, 5, 7, 2, 1 ); STARTBOXCONNECTION( 7, 4, 6, 3, 2 ); STARTBOXCONNECTION( 8, 2, 3, 0, 4 ); STARTBOXCONNECTION( 9, 3, 7, 1, 4 ); STARTBOXCONNECTION( 10, 7, 6, 2, 4 ); STARTBOXCONNECTION( 11, 6, 2, 3, 4 ); StartingPolygonList[0].polygon.vSurfaceNormal.Init( -1.0f, 0.0f, 0.0f ); //StartingPolygonList[0].polygon.fNormalDist = -vAABBMinsVec.x; StartingPolygonList[1].polygon.vSurfaceNormal.Init( 0.0f, 1.0f, 0.0f ); //StartingPolygonList[1].polygon.fNormalDist = vAABBMaxsVec.y; StartingPolygonList[2].polygon.vSurfaceNormal.Init( 1.0f, 0.0f, 0.0f ); //StartingPolygonList[2].polygon.fNormalDist = vAABBMaxsVec.x; StartingPolygonList[3].polygon.vSurfaceNormal.Init( 0.0f, -1.0f, 0.0f ); //StartingPolygonList[3].polygon.fNormalDist = -vAABBMinsVec.y; StartingPolygonList[4].polygon.vSurfaceNormal.Init( 0.0f, 0.0f, 1.0f ); //StartingPolygonList[4].polygon.fNormalDist = vAABBMaxsVec.z; StartingPolygonList[5].polygon.vSurfaceNormal.Init( 0.0f, 0.0f, -1.0f ); //StartingPolygonList[5].polygon.fNormalDist = -vAABBMinsVec.z; STARTPOLYGONTOLINELINKS( 0, 0, 1, 5, 1, 8, 0, 4, 0 ); STARTPOLYGONTOLINELINKS( 1, 1, 1, 6, 1, 9, 0, 5, 0 ); STARTPOLYGONTOLINELINKS( 2, 2, 1, 7, 1, 10, 0, 6, 0 ); STARTPOLYGONTOLINELINKS( 3, 3, 1, 4, 1, 11, 0, 7, 0 ); STARTPOLYGONTOLINELINKS( 4, 8, 1, 9, 1, 10, 1, 11, 1 ); STARTPOLYGONTOLINELINKS( 5, 0, 0, 3, 0, 2, 0, 1, 0 ); { StartingPolygonList[0].pNext = &StartingPolygonList[1]; StartingPolygonList[0].pPrev = NULL; StartingPolygonList[1].pNext = &StartingPolygonList[2]; StartingPolygonList[1].pPrev = &StartingPolygonList[0]; StartingPolygonList[2].pNext = &StartingPolygonList[3]; StartingPolygonList[2].pPrev = &StartingPolygonList[1]; StartingPolygonList[3].pNext = &StartingPolygonList[4]; StartingPolygonList[3].pPrev = &StartingPolygonList[2]; StartingPolygonList[4].pNext = &StartingPolygonList[5]; StartingPolygonList[4].pPrev = &StartingPolygonList[3]; StartingPolygonList[5].pNext = NULL; StartingPolygonList[5].pPrev = &StartingPolygonList[4]; } { StartingLineList[0].pNext = &StartingLineList[1]; StartingLineList[0].pPrev = NULL; StartingLineList[1].pNext = &StartingLineList[2]; StartingLineList[1].pPrev = &StartingLineList[0]; StartingLineList[2].pNext = &StartingLineList[3]; StartingLineList[2].pPrev = &StartingLineList[1]; StartingLineList[3].pNext = &StartingLineList[4]; StartingLineList[3].pPrev = &StartingLineList[2]; StartingLineList[4].pNext = &StartingLineList[5]; StartingLineList[4].pPrev = &StartingLineList[3]; StartingLineList[5].pNext = &StartingLineList[6]; StartingLineList[5].pPrev = &StartingLineList[4]; StartingLineList[6].pNext = &StartingLineList[7]; StartingLineList[6].pPrev = &StartingLineList[5]; StartingLineList[7].pNext = &StartingLineList[8]; StartingLineList[7].pPrev = &StartingLineList[6]; StartingLineList[8].pNext = &StartingLineList[9]; StartingLineList[8].pPrev = &StartingLineList[7]; StartingLineList[9].pNext = &StartingLineList[10]; StartingLineList[9].pPrev = &StartingLineList[8]; StartingLineList[10].pNext = &StartingLineList[11]; StartingLineList[10].pPrev = &StartingLineList[9]; StartingLineList[11].pNext = NULL; StartingLineList[11].pPrev = &StartingLineList[10]; } { StartingPointList[0].pNext = &StartingPointList[1]; StartingPointList[0].pPrev = NULL; StartingPointList[1].pNext = &StartingPointList[2]; StartingPointList[1].pPrev = &StartingPointList[0]; StartingPointList[2].pNext = &StartingPointList[3]; StartingPointList[2].pPrev = &StartingPointList[1]; StartingPointList[3].pNext = &StartingPointList[4]; StartingPointList[3].pPrev = &StartingPointList[2]; StartingPointList[4].pNext = &StartingPointList[5]; StartingPointList[4].pPrev = &StartingPointList[3]; StartingPointList[5].pNext = &StartingPointList[6]; StartingPointList[5].pPrev = &StartingPointList[4]; StartingPointList[6].pNext = &StartingPointList[7]; StartingPointList[6].pPrev = &StartingPointList[5]; StartingPointList[7].pNext = NULL; StartingPointList[7].pPrev = &StartingPointList[6]; } } CPolyhedron *pRetVal = ClipLinkedGeometry( StartingPolygonList, StartingLineList, StartingPointList, 8, pAlteredPlanes, iPlaneCount, fOnPlaneEpsilon, bUseTemporaryMemory, vResultOffset ); #if defined( DEBUG_POLYHEDRON_CONVERSION ) //last bit of debugging from whatever outside source wants this stupid thing if( (g_pPolyhedronCarvingDebugStepCallback != NULL) && (pRetVal != NULL) ) { AssertMsg( g_pPolyhedronCarvingDebugStepCallback( pRetVal ), "Outside conversion failed" ); } #endif return pRetVal; } #ifdef DBGFLAG_ASSERT void DumpAABBToGLView( const Vector &vCenter, const Vector &vExtents, const Vector &vColor, FILE *pFile ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS Vector vMins = vCenter - vExtents; Vector vMaxs = vCenter + vExtents; //x min side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); //x max side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); //y min side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); //y max side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); //z min side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); //z max side fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); #endif } void DumpLineToGLView( const Vector &vPoint1, const Vector &vColor1, const Vector &vPoint2, const Vector &vColor2, float fThickness, FILE *pFile ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS Vector vDirection = vPoint2 - vPoint1; vDirection.NormalizeInPlace(); Vector vPseudoPerpandicular = vec3_origin; if( vDirection.x != 0.0f ) vPseudoPerpandicular.z = 1.0f; else vPseudoPerpandicular.x = 1.0f; Vector vWidth = vDirection.Cross( vPseudoPerpandicular ); vWidth.NormalizeInPlace(); Vector vHeight = vDirection.Cross( vWidth ); vHeight.NormalizeInPlace(); fThickness *= 0.5f; //we use half thickness in both directions vDirection *= fThickness; vWidth *= fThickness; vHeight *= fThickness; Vector vLinePoints[8]; vLinePoints[0] = vPoint1 - vDirection - vWidth - vHeight; vLinePoints[1] = vPoint1 - vDirection - vWidth + vHeight; vLinePoints[2] = vPoint1 - vDirection + vWidth - vHeight; vLinePoints[3] = vPoint1 - vDirection + vWidth + vHeight; vLinePoints[4] = vPoint2 + vDirection - vWidth - vHeight; vLinePoints[5] = vPoint2 + vDirection - vWidth + vHeight; vLinePoints[6] = vPoint2 + vDirection + vWidth - vHeight; vLinePoints[7] = vPoint2 + vDirection + vWidth + vHeight; const Vector *pLineColors[8] = { &vColor1, &vColor1, &vColor1, &vColor1, &vColor2, &vColor2, &vColor2, &vColor2 }; #define DPTGLV_LINE_WRITEPOINT(index) fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vLinePoints[index].x, vLinePoints[index].y, vLinePoints[index].z, pLineColors[index]->x, pLineColors[index]->y, pLineColors[index]->z ); #define DPTGLV_LINE_DOUBLESIDEDQUAD(index1,index2,index3,index4)\ fprintf( pFile, "4\n" );\ DPTGLV_LINE_WRITEPOINT(index1);\ DPTGLV_LINE_WRITEPOINT(index2);\ DPTGLV_LINE_WRITEPOINT(index3);\ DPTGLV_LINE_WRITEPOINT(index4);\ fprintf( pFile, "4\n" );\ DPTGLV_LINE_WRITEPOINT(index4);\ DPTGLV_LINE_WRITEPOINT(index3);\ DPTGLV_LINE_WRITEPOINT(index2);\ DPTGLV_LINE_WRITEPOINT(index1); DPTGLV_LINE_DOUBLESIDEDQUAD(0,4,6,2); DPTGLV_LINE_DOUBLESIDEDQUAD(3,7,5,1); DPTGLV_LINE_DOUBLESIDEDQUAD(1,5,4,0); DPTGLV_LINE_DOUBLESIDEDQUAD(2,6,7,3); DPTGLV_LINE_DOUBLESIDEDQUAD(0,2,3,1); DPTGLV_LINE_DOUBLESIDEDQUAD(5,7,6,4); #endif } #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS void DumpWorkingStatePolygons( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, bool bIsDeadList, const VMatrix *pTransform, FILE *pFile ) { while( pPolygons ) { GeneratePolyhedronFromPlanes_Polygon *pPoly = &pPolygons->polygon; #ifdef DBGFLAG_ASSERT if( pPoly->pLines == (void *)0xCCCCCCCC ) { //this function gets called when something is already wrong. No need to crash instead of reporting something useful pPolygons = pPolygons->pNext; continue; } #endif if( (pPoly->pLines == NULL) || (pPoly->pLines == pPoly->pLines->pNext) ) { pPolygons = pPolygons->pNext; continue; //less than 3 points in the polygon so far, undrawable } Vector vColor; //dead is dark grey, missing a side is red, complete is green, new is blue if( pPoly->debugdata.vWorkingStateColorOverride != vec3_origin ) { vColor = pPoly->debugdata.vWorkingStateColorOverride; } else { if( bIsDeadList ) { vColor = Vector( 0.25f, 0.25f, 0.25f ); } else { /*if(pPoly->bMissingASide) { vColor = Vector( 1.0f, 0.0f, 0.0f ); Vector vPatchLineColor( 1.0f, 0.0f, 1.0f ); fltx4 f4P1 = pPoly->pLines->pLine->pPoints[pPoly->pLines->iReferenceIndex]->ptPosition; fltx4 f4P2 = pPoly->pLines->pNext->pLine->pPoints[1 - pPoly->pLines->pNext->iReferenceIndex]->ptPosition; Vector vP1( SubFloat( f4P1, 0 ), SubFloat( f4P1, 1 ), SubFloat( f4P1, 2 ) ); Vector vP2( SubFloat( f4P2, 0 ), SubFloat( f4P2, 1 ), SubFloat( f4P2, 2 ) ); Vector vP1 = (*pTransform) * vP1; Vector vP2 = (*pTransform) * vP2; DumpLineToGLView( vP1, vPatchLineColor, vP2, vPatchLineColor, 0.1f, pFile ); } else*/ { if( pPoly->debugdata.bIsNew ) vColor = Vector( 0.0f, 0.0f, 1.0f ); else vColor = Vector( 0.0f, 1.0f, 0.0f ); } } vColor *= 0.33f; } GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pPoly->pLines; GeneratePolyhedronFromPlanes_LineLL *pLineWalkStart = pLineWalk; int iVertexCount = 1; do { ++iVertexCount; pLineWalk = pLineWalk->pNext; } while(pLineWalk != pLineWalkStart); fprintf( pFile, "%i\n", iVertexCount ); fltx4 f4vertex = pLineWalk->pLine->pPoints[1 - pLineWalk->iReferenceIndex]->ptPosition; Vector vertex( SubFloat( f4vertex, 0 ), SubFloat( f4vertex, 1 ), SubFloat( f4vertex, 2 ) ); vertex = (*pTransform) * vertex; fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vertex.x, vertex.y, vertex.z, vColor.x, vColor.y, vColor.z ); do { f4vertex = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->ptPosition; vertex.Init( SubFloat( f4vertex, 0 ), SubFloat( f4vertex, 1 ), SubFloat( f4vertex, 2 ) ); vertex = (*pTransform) * vertex; fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vertex.x, vertex.y, vertex.z, vColor.x, vColor.y, vColor.z ); pLineWalk = pLineWalk->pNext; } while(pLineWalk != pLineWalkStart); pPolygons = pPolygons->pNext; } } void DumpWorkingStateLines( GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, const VMatrix *pTransform, FILE *pFile ) { while( pLines ) { GeneratePolyhedronFromPlanes_Line *pLine = &pLines->line; if( (pLine->pPoints[0] == NULL) || (pLine->pPoints[1] == NULL) ) { pLines = pLines->pNext; continue; } #ifdef DBGFLAG_ASSERT if( (pLine->pPoints[0] == (void *)0xCCCCCCCC) || (pLine->pPoints[1] == (void *)0xCCCCCCCC) ) { //this function gets called when something is already wrong. No need to crash instead of reporting something useful pLines = pLines->pNext; continue; } #endif Vector vColor; //dead is dark grey, cut is red, complete is green, re-lengthened is green+blue, onplane is yellow, new is blue if( pLine->debugdata.vWorkingStateColorOverride != vec3_origin ) { vColor = pLine->debugdata.vWorkingStateColorOverride; } else { if( pLine->debugdata.bIsNew ) { if( pLine->planarity == LINE_DEAD ) { //created and then ditched vColor = Vector( 0.5f, 0.5f, 1.0f ); } else { vColor = Vector( 0.0f, 0.0f, 1.0f ); } } else { switch( pLine->planarity ) { case LINE_ONPLANE: vColor = Vector( 1.0f, 1.0f, 0.0f ); break; case LINE_ALIVE: /*if( pLine->debugdata.initialPlanarity & LINE_DEAD ) { vColor = Vector( 0.0f, 1.0f, 1.0f ); //re-lengthened } else*/ { vColor = Vector( 0.0f, 1.0f, 0.0f ); //always alive } break; case LINE_DEAD: vColor = Vector( 0.25f, 0.25f, 0.25f ); break; case LINE_CUT: vColor = Vector( 1.0f, 0.0f, 0.0f ); break; }; } } fltx4 f4Pos0 = pLine->pPoints[0]->ptPosition; Vector vPos0( SubFloat( f4Pos0, 0 ), SubFloat( f4Pos0, 1 ), SubFloat( f4Pos0, 2 ) ); fltx4 f4Pos1 = pLine->pPoints[1]->ptPosition; Vector vPos1( SubFloat( f4Pos1, 0 ), SubFloat( f4Pos1, 1 ), SubFloat( f4Pos1, 2 ) ); DumpLineToGLView( (*pTransform) * vPos0, vColor, (*pTransform) * vPos1, vColor, 0.1f, pFile ); pLines = pLines->pNext; } } void DumpWorkingStatePoints( GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, const VMatrix *pTransform, FILE *pFile ) { while( pPoints ) { GeneratePolyhedronFromPlanes_Point *pPoint = &pPoints->point; Vector vColor; //dead is red, alive is green, onplane is red+green, new is blue if( pPoint->debugdata.vWorkingStateColorOverride != vec3_origin ) { vColor = pPoint->debugdata.vWorkingStateColorOverride; } else { if( pPoint->debugdata.bIsNew ) { if( pPoint->planarity == POINT_DEAD ) { //created and then merged away vColor = Vector( 0.5f, 0.5f, 1.0f ); } else { vColor = Vector( 0.0f, 0.0f, 1.0f ); } } else { switch( pPoint->planarity ) { case POINT_ONPLANE: vColor = Vector( 1.0f, 1.0f, 0.0f ); break; case POINT_ALIVE: vColor = Vector( 0.0f, 1.0f, 0.0f ); //always alive break; case POINT_DEAD: vColor = Vector( 1.0f, 0.0f, 0.0f ); break; }; } } fltx4 f4Pos = pPoint->ptPosition; Vector vPos( SubFloat( f4Pos, 0 ), SubFloat( f4Pos, 1 ), SubFloat( f4Pos, 2 ) ); DumpAABBToGLView( (*pTransform) * vPos, Vector( 0.15f, 0.15f, 0.15f ), vColor, pFile ); pPoints = pPoints->pNext; } } //dumps color coded information about a polyhedron that's in the middle of a cut (debugging only) void DumpWorkingStatePolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pAllPolygons, GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDeadPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pAllLines, GeneratePolyhedronFromPlanes_UnorderedLineLL *pDeadLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, GeneratePolyhedronFromPlanes_UnorderedPointLL *pDeadPoints, const char *pFilename, const VMatrix *pTransform ) { if( pTransform == NULL ) pTransform = &s_matIdentity; printf("Writing %s...\n", pFilename ); FILE *pFile = fopen( pFilename, "wb" ); if( pFile == NULL ) return; DumpWorkingStatePolygons( pAllPolygons, false, pTransform, pFile ); //DumpWorkingStatePolygons( pDeadPolygons, true, pTransform, pFile ); //they're undrawable, they have less than 3 lines DumpWorkingStateLines( pAllLines, pTransform, pFile ); DumpWorkingStateLines( pDeadLines, pTransform, pFile ); DumpWorkingStatePoints( pAllPoints, pTransform, pFile ); DumpWorkingStatePoints( pDeadPoints, pTransform, pFile ); fclose( pFile ); } #endif void DumpPolyhedronToGLView( const CPolyhedron *pPolyhedron, const char *pFilename, const VMatrix *pTransform, const char *szfileOpenOptions ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS Assert( pPolyhedron && (pPolyhedron->iVertexCount > 2) ); if( pTransform == NULL ) pTransform = &s_matIdentity; printf("Writing %s...\n", pFilename ); FILE *pFile = fopen( pFilename, szfileOpenOptions ); const Vector vOne( 1.0f, 1.0f, 1.0f ); //randomizing an array of colors to help spot shared/unshared vertices Vector *pColors = (Vector *)stackalloc( sizeof( Vector ) * pPolyhedron->iVertexCount ); int counter; for( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) { pColors[counter].Init( rand()/32768.0f, rand()/32768.0f, rand()/32768.0f ); } Vector *pTransformedPoints = (Vector *)stackalloc( pPolyhedron->iVertexCount * sizeof( Vector ) ); for ( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) { pTransformedPoints[counter] = (*pTransform) * pPolyhedron->pVertices[counter]; } for ( counter = 0; counter != pPolyhedron->iPolygonCount; ++counter ) { fprintf( pFile, "%i\n", pPolyhedron->pPolygons[counter].iIndexCount ); int counter2; for( counter2 = 0; counter2 != pPolyhedron->pPolygons[counter].iIndexCount; ++counter2 ) { Polyhedron_IndexedLineReference_t *pLineReference = &pPolyhedron->pIndices[pPolyhedron->pPolygons[counter].iFirstIndex + counter2]; Vector *pVertex = &pTransformedPoints[pPolyhedron->pLines[pLineReference->iLineIndex].iPointIndices[pLineReference->iEndPointIndex]]; Vector *pColor = &pColors[pPolyhedron->pLines[pLineReference->iLineIndex].iPointIndices[pLineReference->iEndPointIndex]]; fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n",pVertex->x, pVertex->y, pVertex->z, pColor->x, pColor->y, pColor->z ); } } for( counter = 0; counter != pPolyhedron->iLineCount; ++counter ) { Vector vPoints[2] = { pTransformedPoints[pPolyhedron->pLines[counter].iPointIndices[0]], pTransformedPoints[pPolyhedron->pLines[counter].iPointIndices[1]] }; const float fShortenEnds = 0.0f; if( fShortenEnds != 0.0f ) { Vector vDiff = vPoints[0] - vPoints[1]; vDiff.NormalizeInPlace(); vPoints[0] -= vDiff * fShortenEnds; vPoints[1] += vDiff * fShortenEnds; } DumpLineToGLView( vPoints[0], vOne - pColors[pPolyhedron->pLines[counter].iPointIndices[0]], vPoints[1], vOne - pColors[pPolyhedron->pLines[counter].iPointIndices[1]], 0.1f, pFile ); } for( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) { const Vector vPointHalfSize(0.15f, 0.15f, 0.15f ); DumpAABBToGLView( pTransformedPoints[counter], vPointHalfSize, pColors[counter], pFile ); } fclose( pFile ); #endif } void DumpPlaneToGlView( const float *pPlane, float fGrayScale, const char *pszFileName, const VMatrix *pTransform ) { #ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS if( pTransform == NULL ) pTransform = &s_matIdentity; FILE *pFile = fopen( pszFileName, "ab" ); //transform the plane Vector vNormal = pTransform->ApplyRotation( *(Vector *)pPlane ); float fDist = pPlane[3] * vNormal.NormalizeInPlace(); //possible scaling going on fDist += vNormal.Dot( pTransform->GetTranslation() ); Vector vPlaneVerts[4]; PolyFromPlane( vPlaneVerts, vNormal, fDist, 100000.0f ); fprintf( pFile, "4\n" ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[0].x, vPlaneVerts[0].y, vPlaneVerts[0].z, fGrayScale, fGrayScale, fGrayScale ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[1].x, vPlaneVerts[1].y, vPlaneVerts[1].z, fGrayScale, fGrayScale, fGrayScale ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[2].x, vPlaneVerts[2].y, vPlaneVerts[2].z, fGrayScale, fGrayScale, fGrayScale ); fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[3].x, vPlaneVerts[3].y, vPlaneVerts[3].z, fGrayScale, fGrayScale, fGrayScale ); fclose( pFile ); #endif } #endif