//============ Copyright (c) Valve Corporation, All rights reserved. ============ // // Utility functions for polygon simplification / convex decomposition. // //=============================================================================== #include "polygon.h" #include "quadric.h" // Assumes that points are ordered clockwise when looking at the face of the polygon void SimplifyPolygon( CUtlVector< Vector > *pPoints, const Vector &vNormal, float flMaximumDeviation ) { if ( pPoints->Count() < 3 ) { return; } int nVertices = pPoints->Count(); Vector vPrevious = pPoints->Element( nVertices - 1 ); Vector vCurrent = pPoints->Element( 0 ); Vector vNext; // Walk around the polygon removing redundant vertices for ( int i = 0; i < nVertices; ++ i ) { vNext = pPoints->Element( ( i + 1 ) % nVertices ); // Is the current vertex redundant? Vector vCurrentToNext = vNext - vCurrent; Vector vCurrentToPrevious = vPrevious - vCurrent; // Compute the area of of the error triangle added (if negative) or removed (if positive) with the removal of this vertex float flAreaDeviation = 0.5f * vCurrentToPrevious.Cross( vCurrentToNext ).Dot( vNormal ); // Only consider a point for removal if it would decrease the polygon area (be conservative) if ( flAreaDeviation >= 0.0f && flAreaDeviation < flMaximumDeviation ) { // redundant pPoints->Remove( i ); -- nVertices; -- i; } else { vPrevious = vCurrent; } vCurrent = vNext; } } static void ComputeQuadric( const CUtlVector< Vector > &points, int nVertex, const Vector &vNormal, CQuadricError *pError ) { int nVertices = points.Count(); Vector vPrevious = points[( nVertex + nVertices - 1 ) % nVertices]; Vector vCurrent = points[nVertex]; Vector vNext = points[( nVertex + 1 ) % nVertices]; Vector vAbove = vCurrent + vNormal; pError->InitFromPlane( vNormal, -DotProduct( vNormal, vCurrent ), 1.0f ); CQuadricError line1, line2; line1.InitFromTriangle( vPrevious, vCurrent, vAbove, 0.0f ); *pError += line1; line2.InitFromTriangle( vNext, vCurrent, vAbove, 0.0f ); *pError += line2; } void SimplifyPolygonQEM( CUtlVector< Vector > *pPoints, const Vector &vNormal, float flMaximumSquaredError, bool bUseOptimalPointPlacement ) { if ( pPoints->Count() < 3 ) { return; } CUtlVector< CQuadricError > quadrics; quadrics.EnsureCapacity( pPoints->Count() ); int nVertices = pPoints->Count(); Vector vPrevious = pPoints->Element( nVertices - 1 ); Vector vCurrent = pPoints->Element( 0 ); Vector vNext; // Compute quadric error functions for each vertex for ( int i = 0; i < nVertices; ++ i ) { vNext = pPoints->Element( ( i + 1 ) % nVertices ); Vector vAbove = vCurrent + vNormal; quadrics.AddToTail(); quadrics[i].InitFromPlane( vNormal, -DotProduct( vNormal, vCurrent ), 1.0f ); CQuadricError line1, line2; line1.InitFromTriangle( vPrevious, vCurrent, vAbove, 0.0f ); quadrics[i] += line1; line2.InitFromTriangle( vNext, vCurrent, vAbove, 0.0f ); quadrics[i] += line2; vPrevious = vCurrent; vCurrent = vNext; } // @TODO: use a sorted heap instead of pseudo-bubble-sort // We like quadrilaterals...don't try to go simpler than that while ( pPoints->Count() > 4 ) { float flLowestError = flMaximumSquaredError; Vector vBestCollapse; int nCollapseIndex = -1; for ( int i = 0; i < nVertices; ++ i ) { int nNextIndex = ( i + 1 ) % nVertices; CQuadricError sum = quadrics[i] + quadrics[nNextIndex]; if ( bUseOptimalPointPlacement ) { // Solve for optimal point and collapse to it Vector vOptimalPoint = sum.SolveForMinimumError(); float flError = sum.ComputeError( vOptimalPoint ); if ( flError < flLowestError ) { flLowestError = flError; vBestCollapse = vOptimalPoint; nCollapseIndex = i; } } else { // Only collapse to endpoints Vector vA = pPoints->Element( i ); float flErrorA = sum.ComputeError( vA ); if ( flErrorA < flLowestError ) { flLowestError = flErrorA; vBestCollapse = vA; nCollapseIndex = i; } Vector vB = pPoints->Element( nNextIndex ); float flErrorB = sum.ComputeError( vB ); if ( flErrorB < flLowestError ) { flLowestError = flErrorB; vBestCollapse = vB; nCollapseIndex = i; } } } if ( nCollapseIndex != -1 ) { pPoints->Element( nCollapseIndex ) = vBestCollapse; int nNextIndex = ( nCollapseIndex + 1 ) % nVertices; pPoints->Remove( nNextIndex ); quadrics.Remove( nNextIndex ); -- nVertices; if ( nNextIndex < nCollapseIndex ) -- nCollapseIndex; int nPrevIndex = ( nCollapseIndex + nVertices - 1 ) % nVertices; nNextIndex = ( nCollapseIndex + 1 ) % nVertices; ComputeQuadric( *pPoints, nPrevIndex, vNormal, &quadrics[nPrevIndex] ); ComputeQuadric( *pPoints, nCollapseIndex, vNormal, &quadrics[nCollapseIndex] ); ComputeQuadric( *pPoints, nNextIndex, vNormal, &quadrics[nNextIndex] ); } else { // we're done break; } } } bool IsConcave( const Vector &v0, const Vector &v1, const Vector &v2, const Vector &vNormal ) { Vector vRay1 = v2 - v1; Vector vRay2 = v0 - v1; float flSign = vRay2.Cross( vRay1 ).Dot( vNormal ); return ( flSign < 0 ); } bool IsConcave( const Vector *pPolygonPoints, int nPointCount, int nVertex, const Vector &vNormal ) { Assert( nPointCount >= 3 ); int nPrevVertex = ( nVertex + nPointCount - 1 ) % nPointCount; int nNextVertex = ( nVertex + 1 ) % nPointCount; return IsConcave( pPolygonPoints[nPrevVertex], pPolygonPoints[nVertex], pPolygonPoints[nNextVertex], vNormal ); } bool IsConcave( const Vector *pPolygonPoints, int nPointCount, const Vector &vNormal ) { Assert( nPointCount >= 3 ); Vector vPrevVertex = pPolygonPoints[nPointCount - 2]; Vector vVertex = pPolygonPoints[nPointCount - 1]; Vector vNextVertex; for ( int i = 0; i < nPointCount; ++ i ) { vNextVertex = pPolygonPoints[i]; if ( IsConcave( vPrevVertex, vVertex, vNextVertex, vNormal ) ) { return true; } vPrevVertex = vVertex; vVertex = vNextVertex; } return false; } static bool IsPointInPolygon( const CUtlVector< Vector > &polygonPoints, const SubPolygon_t &convexRegion, const Vector &vNormal, const Vector &vPoint ) { int nIndex = convexRegion.m_Indices[convexRegion.m_Indices.Count() - 1]; Vector v0 = SubPolygon_t::GetPoint( polygonPoints, nIndex ); for ( int i = 0; i < convexRegion.m_Indices.Count(); ++ i ) { nIndex = convexRegion.m_Indices[i]; Vector v1 = SubPolygon_t::GetPoint( polygonPoints, nIndex ); Vector vRay = v1 - v0; Vector vRight = vRay.Cross( vNormal ); if ( vRight.Dot( vPoint - v0 ) < -POINT_IN_POLYGON_EPSILON ) { return false; } v0 = v1; } return true; } static bool PointsInsideConvexArea( const CUtlVector< Vector > &polygonPoints, const SubPolygon_t &originalPolygon, const Vector &vNormal, int nFirstIndex, int nLastIndex, const SubPolygon_t &convexRegion ) { nFirstIndex %= originalPolygon.m_Indices.Count(); nLastIndex %= originalPolygon.m_Indices.Count(); if ( nFirstIndex < 0 ) nFirstIndex += originalPolygon.m_Indices.Count(); if ( nLastIndex < 0 ) nLastIndex += originalPolygon.m_Indices.Count(); for ( int i = nFirstIndex; i != nLastIndex; i = ( i + 1 ) % originalPolygon.m_Indices.Count() ) { int nVertex = originalPolygon.GetVertexIndex( i ); Vector vPoint = SubPolygon_t::GetPoint( polygonPoints, nVertex ); if ( IsPointInPolygon( polygonPoints, convexRegion, vNormal, vPoint ) ) { bool bIsDoubleVertex = false; // Allow points on the boundary of the convex region if they are coincident with one of the points of the region itself for ( int j = 0; j < convexRegion.m_Indices.Count(); ++ j ) { Vector vConvexRegionTestPoint = SubPolygon_t::GetPoint( polygonPoints, convexRegion.GetVertexIndex( j ) ); if ( VectorsAreEqual( vConvexRegionTestPoint, vPoint, POINT_IN_POLYGON_EPSILON ) ) { bIsDoubleVertex = true; break; } } if ( !bIsDoubleVertex ) { return true; } } } return false; } static const float LINE_INTERSECT_EPSILON = 1e-3f; // superceded by CalcLineToLineIntersectionSegment // bool LineSegmentsIntersect( const Vector &vNormal, const Vector &v1a, const Vector &v1b, const Vector &v2a, const Vector &v2b, float flEpsilon, float *pTimeOfIntersection1, float *pTimeOfIntersection2 ) // { // Vector vDir1 = v1b - v1a; // Vector vDir2 = v2b - v2a; // Vector v1Perpendicular = vDir1.Cross( vNormal ); // Vector vStartDiff = v1a - v2a; // float flNumerator = vStartDiff.Dot( v1Perpendicular ); // float flDenominator = vDir2.Dot( v1Perpendicular ); // // @TODO: we should probably use a different epsilon since the denominator is in different units, but this works well so far // if ( fabsf( flDenominator ) < flEpsilon ) // { // return false; // } // float t2 = flNumerator / flDenominator; // if ( t2 >= -flEpsilon && t2 <= ( 1.0f + flEpsilon ) ) // { // Vector vClosestPoint2 = t2 * vDir2 + v2a; // float flLength1 = vDir1.NormalizeInPlace(); // // Can't be 0 otherwise flDenominator would have been 0 // float t1 = ( vClosestPoint2 - v1a ).Dot( vDir1 ) / flLength1; // // if ( t1 >= -flEpsilon && t1 <= ( 1.0f + flEpsilon ) ) // { // *pTimeOfIntersection1 = t1; // *pTimeOfIntersection2 = t2; // return true; // } // } // return false; // } //----------------------------------------------------------------------------- // Note: this is not a general purpose intersection test; // it makes some assumptions that the winding of the polygon is // counter-clockwise (because it's expected to be a hole) and that, // if the line segment vA-vB intersects a line segment in the hole // (denoted by vHoleA-vHoleB, in counter-clockwise ordering), // then the line segment vA-vHoleB must not intersect any other part of // the hole polygon. //----------------------------------------------------------------------------- static bool LineSegmentIntersectsPolygon( const CUtlVector< Vector > &polygonPoints, const Vector &vNormal, const Vector &vA, const Vector &vB, const SubPolygon_t &hole, float *pLowestTimeOfIntersection, Vector *pA, Vector *pB, int *pHoleVertexIndex ) { *pLowestTimeOfIntersection = 2.0f; // a valid time is between 0 and 1, anything outside the range is considered invalid float flTimeOfIntersection; Vector vPrev = SubPolygon_t::GetPoint( polygonPoints, hole.m_Indices[hole.m_Indices.Count() - 1] ); bool bIntersect = false; Vector vInside = ( vB - vA ).Cross( vNormal ); for ( int i = 0; i < hole.m_Indices.Count(); ++ i ) { float flOtherTimeOfIntersection; Vector vCurrent = SubPolygon_t::GetPoint( polygonPoints, hole.m_Indices[i] ); // @TODO: make sure the replacement is ok before deleting //if ( LineSegmentsIntersect( vNormal, vA, vB, vPrev, vCurrent, &flTimeOfIntersection, &flOtherTimeOfIntersection ) ) CalcLineToLineIntersectionSegment( vA, vB, vPrev, vCurrent, NULL, NULL, &flTimeOfIntersection, &flOtherTimeOfIntersection ); if ( flTimeOfIntersection >= -LINE_INTERSECT_EPSILON && flTimeOfIntersection <= 1.0f + LINE_INTERSECT_EPSILON && flOtherTimeOfIntersection >= -LINE_INTERSECT_EPSILON && flTimeOfIntersection <= 1.0f + LINE_INTERSECT_EPSILON ) { // If the line segment intersection occurs right at the beginning of the hole line segment, ignore it because we'll catch it as an intersection at the end of // another hole line segment. // This is required because we want to guarantee that a line segment from vA to vCurrent does not intersect the polygon at any point. if ( flTimeOfIntersection < *pLowestTimeOfIntersection && flOtherTimeOfIntersection > LINE_INTERSECT_EPSILON ) { *pLowestTimeOfIntersection = flTimeOfIntersection; *pA = vPrev; *pB = vCurrent; float flPrevInsideDistance = ( vPrev - vA ).Dot( vInside ); float flCurrentInsideDistance = ( vCurrent - vA ).Dot( vInside ); if ( flCurrentInsideDistance > flPrevInsideDistance ) { *pHoleVertexIndex = i; } else { *pHoleVertexIndex = ( i + hole.m_Indices.Count() - 1 ) % hole.m_Indices.Count(); } bIntersect = true; } } vPrev = vCurrent; } return bIntersect; } void FindLineSegmentIntersectingDiagonal( const CUtlVector< Vector > &polygonPoints, const Vector &vNormal, const CUtlVector< SubPolygon_t > &holes, const Vector &vA, const Vector &vB, Vector *pHoleSegmentA, Vector *pHoleSegmentB, int *pHoleIndex, int *pHoleVertexIndex ) { float flLowestTimeOfIntersection = 2.0f; int nHoleVertexIndex; *pHoleIndex = -1; // Test for holes for ( int i = 0; i < holes.Count(); ++ i ) { float flTimeOfIntersection; Vector vTempA, vTempB; if ( LineSegmentIntersectsPolygon( polygonPoints, vNormal, vA, vB, holes[i], &flTimeOfIntersection, &vTempA, &vTempB, &nHoleVertexIndex ) ) { if ( flTimeOfIntersection < flLowestTimeOfIntersection ) { flLowestTimeOfIntersection = flTimeOfIntersection; *pHoleSegmentA = vTempA; *pHoleSegmentB = vTempB; *pHoleIndex = i; *pHoleVertexIndex = nHoleVertexIndex; } } } } void DecomposePolygon_Step( const CUtlVector< Vector > &polygonPoints, const Vector &vNormal, CUtlVector< SubPolygon_t > *pHoles, SubPolygon_t *pNewPartition, SubPolygon_t *pOriginalPolygon, int *pFirstIndex ) { Assert( *pFirstIndex >= 0 && *pFirstIndex < pOriginalPolygon->m_Indices.Count() ); // Always start decomposition on a notch Vector vPrev = SubPolygon_t::GetPoint( polygonPoints, pOriginalPolygon->GetVertexIndex( *pFirstIndex - 1 ) ); Vector vCurrent = SubPolygon_t::GetPoint( polygonPoints, pOriginalPolygon->GetVertexIndex( *pFirstIndex ) ); Vector vNext = SubPolygon_t::GetPoint( polygonPoints, pOriginalPolygon->GetVertexIndex( *pFirstIndex + 1 ) ); for ( int i = 0; i < pOriginalPolygon->m_Indices.Count(); ++ i ) { if ( IsConcave( vPrev, vCurrent, vNext, vNormal) ) { *pFirstIndex = ( *pFirstIndex + i ) % pOriginalPolygon->m_Indices.Count(); break; } else { vPrev = vCurrent; vCurrent = vNext; vNext = SubPolygon_t::GetPoint( polygonPoints, pOriginalPolygon->GetVertexIndex( *pFirstIndex + i + 2 ) ); } } // On termination of the loop, pOriginalPolygon->m_Indices[*pFirstIndex] is the vertex index (in polygonPoints) from // which to begin decomposition // Attempt decomposition (first clockwise, then counter-clockwise if that fails) for ( int i = 0; i < 2; ++ i ) { bool bClockwise = ( i == 0 ); pNewPartition->m_Indices.RemoveAll(); // Grab the first 2 vertices from the remaining polygon and add to the new potential convex partition int nFirstVertex = pOriginalPolygon->GetVertexIndex( *pFirstIndex ); int nSecondVertex = pOriginalPolygon->GetVertexIndex( *pFirstIndex + ( bClockwise ? 1 : -1 ) ); if ( bClockwise ) { pNewPartition->m_Indices.AddToTail( nFirstVertex ); pNewPartition->m_Indices.AddToTail( nSecondVertex ); } else { pNewPartition->m_Indices.AddToTail( nSecondVertex ); pNewPartition->m_Indices.AddToTail( nFirstVertex ); } Vector vFirst = SubPolygon_t::GetPoint( polygonPoints, nFirstVertex ); Vector vSecond = SubPolygon_t::GetPoint( polygonPoints, nSecondVertex ); Vector vPrevPrev = vFirst; vPrev = vSecond; int nNextIndex = *pFirstIndex + ( bClockwise ? 2 : -2 ); int nNextVertex = pOriginalPolygon->GetVertexIndex( nNextIndex ); // At the start of each iteration, *pFirstIndex refers to the index of the first vertex from the original polygon that is in the partition // and nNextIndex refers to 1 past the index of the last vertex from the original polygon that is in the partition. // If clockwise, you can find the new convex partition by iterating indices in original polygon from [*pFirstIndex, nNextIndex-1], // if counter-clockwise, iterate from [nNextIndex+1, *pFirstIndex]. while ( pNewPartition->m_Indices.Count() < pOriginalPolygon->m_Indices.Count() ) { vCurrent = SubPolygon_t::GetPoint( polygonPoints, nNextVertex ); bool bConcave; if ( bClockwise ) { bConcave = IsConcave( vPrevPrev, vPrev, vCurrent, vNormal ) || IsConcave( vPrev, vCurrent, vFirst, vNormal ) || IsConcave( vCurrent, vFirst, vSecond, vNormal ); } else { bConcave = IsConcave( vCurrent, vPrev, vPrevPrev, vNormal ) || IsConcave( vFirst, vCurrent, vPrev, vNormal ) || IsConcave( vSecond, vFirst, vCurrent, vNormal ); } if ( bConcave ) { // Shape is no longer convex with the addition of vCurrent break; } else { if ( bClockwise ) { pNewPartition->m_Indices.AddToTail( nNextVertex ); ++ nNextIndex; } else { pNewPartition->m_Indices.AddToHead( nNextVertex ); -- nNextIndex; } nNextVertex = pOriginalPolygon->GetVertexIndex( nNextIndex ); vPrevPrev = vPrev; vPrev = vCurrent; } } int nFirstIndexInRemainingPolygon = bClockwise ? nNextIndex : *pFirstIndex + 1; int nLastIndexInRemainingPolygon = bClockwise ? *pFirstIndex : nNextIndex + 1; // Test to see if any points in the remaining polygon are within the bounds of the convex polygon we're about to peel off. while ( pNewPartition->m_Indices.Count() >= 3 && PointsInsideConvexArea( polygonPoints, *pOriginalPolygon, vNormal, nFirstIndexInRemainingPolygon, nLastIndexInRemainingPolygon, *pNewPartition ) ) { if ( bClockwise ) { pNewPartition->m_Indices.RemoveMultipleFromTail( 1 ); -- nNextIndex; } else { pNewPartition->m_Indices.RemoveMultipleFromHead( 1 ); ++ nNextIndex; } } // Found a convex chunk of the original concave polygon, now check for // holes or concavities which intersect this convex chunk. if ( pNewPartition->m_Indices.Count() >= 3 ) { Vector vA = SubPolygon_t::GetPoint( polygonPoints, pNewPartition->m_Indices[pNewPartition->m_Indices.Count() - 1] ); Vector vB = SubPolygon_t::GetPoint( polygonPoints, pNewPartition->m_Indices[0] ); Vector vHoleSegmentA, vHoleSegmentB; int nHoleIndex = -1; int nLastHoleIndex; int nHoleVertexIndex; // See if the diagonal which closes this convex region intersects any holes FindLineSegmentIntersectingDiagonal( polygonPoints, vNormal, *pHoles, vA, vB, &vHoleSegmentA, &vHoleSegmentB, &nHoleIndex, &nHoleVertexIndex ); // If there was an intersection, keep refining the diagonal until it no longer changes if ( nHoleIndex != -1 ) { do { nLastHoleIndex = nHoleIndex; vB = SubPolygon_t::GetPoint( polygonPoints, pHoles->Element( nHoleIndex ).GetVertexIndex( nHoleVertexIndex ) ); FindLineSegmentIntersectingDiagonal( polygonPoints, vNormal, *pHoles, vA, vB, &vHoleSegmentA, &vHoleSegmentB, &nHoleIndex, &nHoleVertexIndex ); Assert( nHoleIndex != -1 ); } while ( nHoleIndex != nLastHoleIndex ); } // If there was no intersection, check to see if this convex region completely encloses any holes if ( nHoleIndex == -1 ) { Vector vHolePoint; int nHoleInRegionIndex = -1; for ( int i = 0; i < pHoles->Count(); ++ i ) { vHolePoint = SubPolygon_t::GetPoint( polygonPoints, pHoles->Element( i ).m_Indices[0] ); if ( IsPointInPolygon( polygonPoints, *pNewPartition, vNormal, vHolePoint ) ) { // hole is within the region nHoleInRegionIndex = i; break; } } if ( nHoleInRegionIndex != -1 ) { // If any holes are enclosed, "fix" the diagonal to connect to an arbitrary vertex on one of the enclosed holes vB = vHolePoint; // Now test to see if this new diagonal intersects any holes FindLineSegmentIntersectingDiagonal( polygonPoints, vNormal, *pHoles, vA, vB, &vHoleSegmentA, &vHoleSegmentB, &nHoleIndex, &nHoleVertexIndex ); // If there was an intersection, keep refining the diagonal until it no longer changes if ( nHoleIndex != -1 ) { do { nLastHoleIndex = nHoleIndex; vB = SubPolygon_t::GetPoint( polygonPoints, pHoles->Element( nHoleIndex ).GetVertexIndex( nHoleVertexIndex ) ); FindLineSegmentIntersectingDiagonal( polygonPoints, vNormal, *pHoles, vA, vB, &vHoleSegmentA, &vHoleSegmentB, &nHoleIndex, &nHoleVertexIndex ); Assert( nHoleIndex != -1 ); } while ( nHoleIndex != nLastHoleIndex ); } } } // At this point, we should either have the original convex region which contains no holes and intersects with nothing, // or a refined diagonal which connects to a hole without intersecting any other holes or convex region points if ( nHoleIndex != -1 ) { // We have a refined diagonal; absorb the hole into the original polygon. // Reject this partition but add the hole's vertices to the original polygon. int nInsertAfterIndex = ( bClockwise ? nNextIndex + pOriginalPolygon->m_Indices.Count() - 1 : *pFirstIndex ) % pOriginalPolygon->m_Indices.Count(); const SubPolygon_t *pHolePolygon = &pHoles->Element( nHoleIndex ); int nConnectBackToVertex = pOriginalPolygon->m_Indices[nInsertAfterIndex]; for ( int i = 0; i < pHolePolygon->m_Indices.Count(); ++ i ) { pOriginalPolygon->m_Indices.InsertAfter( nInsertAfterIndex, pHolePolygon->m_Indices[( i + nHoleVertexIndex ) % pHolePolygon->m_Indices.Count()] ); ++ nInsertAfterIndex; } pOriginalPolygon->m_Indices.InsertAfter( nInsertAfterIndex, pHolePolygon->m_Indices[nHoleVertexIndex] ); ++ nInsertAfterIndex; pOriginalPolygon->m_Indices.InsertAfter( nInsertAfterIndex, nConnectBackToVertex ); pNewPartition->m_Indices.RemoveAll(); pHoles->Remove( nHoleIndex ); } else { // We have the original, valid diagonal. // Remove the corresponding indices from the original polygon to peel off the new convex region int nIndexToRemove = ( ( bClockwise ? *pFirstIndex : nNextIndex + 1 ) + 1 ) % pOriginalPolygon->m_Indices.Count(); if ( nIndexToRemove < 0 ) nIndexToRemove += pOriginalPolygon->m_Indices.Count(); for ( int i = 1; i < pNewPartition->m_Indices.Count() - 1; ++ i ) { nIndexToRemove = nIndexToRemove % pOriginalPolygon->m_Indices.Count(); Assert( pOriginalPolygon->m_Indices[nIndexToRemove] == pNewPartition->m_Indices[i] ); pOriginalPolygon->m_Indices.Remove( nIndexToRemove ); } *pFirstIndex = nIndexToRemove % pOriginalPolygon->m_Indices.Count(); } // Done! return; } } // Couldn't find a match either clockwise or counter-clockwise *pFirstIndex = ( *pFirstIndex + 1 ) % pOriginalPolygon->m_Indices.Count(); } void DecomposePolygon( const CUtlVector< Vector > &polygonPoints, const Vector &vNormal, SubPolygon_t *pOriginalPolygon, CUtlVector< SubPolygon_t > *pHoles, CUtlVector< SubPolygon_t > *pPartitions ) { int nFirstIndex = 0; // The Nth vertex in the remaining polygon SubPolygon_t *pNewPartition = NULL; while ( pOriginalPolygon->m_Indices.Count() >= 3 ) { if ( !pNewPartition ) { pNewPartition = &pPartitions->Element( pPartitions->AddToTail() ); } DecomposePolygon_Step( polygonPoints, vNormal, pHoles, pNewPartition, pOriginalPolygon, &nFirstIndex ); if ( pNewPartition->m_Indices.Count() >= 3 ) { pNewPartition = NULL; } else { pNewPartition->m_Indices.RemoveAll(); } } } bool IsPointInPolygonPrism( const Vector *pPolygonPoints, int nPointCount, const Vector &vPoint, float flThreshold, float *pHeight ) { Assert( nPointCount >= 3 ); Vector vNormal = ( pPolygonPoints[0] - pPolygonPoints[1] ).Cross( pPolygonPoints[2] - pPolygonPoints[1] ); Vector vPrev = pPolygonPoints[nPointCount - 1]; for ( int nVertexIndex = 0; nVertexIndex < nPointCount; ++ nVertexIndex ) { Vector vCurrent = pPolygonPoints[nVertexIndex]; Vector vAbove = vPrev + vNormal; Vector vOutwardPlaneNormal = ( vPrev - vCurrent ).Cross( vAbove - vCurrent ); if ( ( vPoint - vCurrent ).Dot( vOutwardPlaneNormal ) > flThreshold ) { // Outside the prism return false; } vPrev = vCurrent; } if ( pHeight != NULL ) { vNormal.NormalizeInPlace(); *pHeight = ( vPoint - pPolygonPoints[0] ).Dot( vNormal ); } return true; }