123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #ifndef _MPLANESET_H_
- #define _MPLANESET_H_
- #ifndef _MPLANE_H_
- #include "math/mPlane.h"
- #endif
- #ifndef _MBOX_H_
- #include "math/mBox.h"
- #endif
- #ifndef _MSPHERE_H_
- #include "math/mSphere.h"
- #endif
- #ifndef _MORIENTEDBOX_H_
- #include "math/mOrientedBox.h"
- #endif
- #ifndef _TEMPALLOC_H_
- #include "util/tempAlloc.h"
- #endif
- #ifndef _TALGORITHM_H_
- #include "core/tAlgorithm.h"
- #endif
- /// Set of planes which can be tested against bounding volumes.
- ///
- /// This class is meant as a helper to collect functionality for working with sets
- /// of planes. As a helper, it does not define means to manage the data it uses.
- ///
- /// @warn This class does not manage the memory needed for the set.
- template< typename T >
- class PlaneSet
- {
- protected:
- /// Number of planes in #mPlanes.
- U32 mNumPlanes;
- /// Set of planes. The memory for this array is managed outside
- /// this class.
- const T* mPlanes;
- template< typename P > OverlapTestResult _testOverlap( const P& bounds ) const;
- template< typename P > bool _isContained( const P& bounds ) const;
- public:
- /// @name Constructors
- /// @{
- /// Create an uninitialized set.
- /// @warn None of the members will be initialized.
- PlaneSet() :mNumPlanes(0), mPlanes(NULL) {}
-
- /// Use the given set of planes to initialize the set.
- ///
- /// @param planes The planes. Memory must be valid for the entire
- /// lifetime of the plane set.
- /// @param numPlanes Number of planes.
- PlaneSet( const T* planes, U32 numPlanes )
- : mNumPlanes( numPlanes ), mPlanes( planes ) {}
- /// @}
- /// @name Accessors
- /// @{
- /// Return the number of planes that this set consists of.
- U32 getNumPlanes() const { return mNumPlanes; }
- /// Return the array of planes for this set.
- const T* getPlanes() const { return mPlanes; }
- /// @}
- /// @name Intersection
- /// All of these intersection methods are approximate in that they can produce
- /// false positives on GeometryIntersecting. For precise results, use Intersector.
- /// @{
- /// Test intersection of the given AABB with the volume defined by the plane set.
- ///
- /// @param aabb Axis-aligned bounding box.
- /// @return The type of overlap that the given AABB has with the plane set.
- ///
- /// @note The method will not test for those edge cases where none of the planes
- /// rejects the given AABB yet the AABB is actually outside the volume defined by the planes.
- /// This speeds up the test at the cost of losing precision which is acceptable for
- /// cases where doing the additional tests for all intersecting objects will generally
- /// waste more time than accepting a few additional non-intersecting objects as
- /// intersecting.
- OverlapTestResult testPotentialIntersection( const Box3F& aabb ) const
- {
- return _testOverlap( aabb );
- }
- /// Test intersection of the given sphere with the volume defined by the plane set.
- ///
- /// @param sphere Sphere.
- /// @return The type of overlap that the given sphere has with the plane set.
- ///
- /// @note The method will not test for those edge cases where none of the planes
- /// rejects the given sphere yet the sphere is actually outside the volume defined by the planes.
- /// This speeds up the test at the cost of losing precision which is acceptable for
- /// cases where doing the additional tests for all intersecting objects will generally
- /// waste more time than accepting a few additional non-intersecting objects as
- /// intersecting.
- OverlapTestResult testPotentialIntersection( const SphereF& sphere ) const
- {
- return _testOverlap( sphere );
- }
- /// Test intersection of the given OBB with the volume defined by the plane set.
- ///
- /// @param obb Oriented bounding box.
- /// @return The type of overlap that the given OBB has with the plane set.
- ///
- /// @note The method will not test for those edge cases where none of the planes
- /// rejects the given OBB yet the OBB is actually outside the volume defined by the planes.
- /// This speeds up the test at the cost of losing precision which is acceptable for
- /// cases where doing the additional tests for all intersecting objects will generally
- /// waste more time than accepting a few additional non-intersecting objects as
- /// intersecting.
- OverlapTestResult testPotentialIntersection( const OrientedBox3F& obb ) const
- {
- return _testOverlap( obb );
- }
- /// Returns a bitmask of which planes are hit by the given box.
- U32 testPlanes( const Box3F& bounds, U32 planeMask = 0xFFFFFFFF, F32 expand = 0.0f ) const;
- /// @}
- /// @name Containment
- /// Testing for containment of geometric shapes within the volume enclosed by the planes.
- /// @{
- /// Return true if the given point lies on the positive side of all the planes
- /// in the set.
- bool isContained( const Point3F& point, F32 epsilon = __EQUAL_CONST_F ) const;
- /// Return true if all of the given points lie on the positive side of all the planes
- /// in the set.
- bool isContained( const Point3F* points, U32 numPoints ) const;
- /// Return true if the given AABB lies on the positive side of all the planes
- /// in the set.
- bool isContained( const Box3F& aabb ) const { return _isContained( aabb ); }
- /// Return true if the given sphere lies on the positive side of all the planes
- /// in the set.
- bool isContained( const SphereF& sphere ) const { return _isContained( sphere ); }
- /// Return true if the given OBB lies on the positive side of all the planes
- /// in the set.
- bool isContained( const OrientedBox3F& obb ) const { return _isContained( obb ); }
- /// @}
- /// @name Clipping
- /// @{
- /// Clip the given line segment against the plane set. If the segment
- /// intersects with any of the planes, the points will be clipped at the
- /// intersection.
- ///
- /// @return True if any part of the segment is inside the volume defined by the plane set.
- bool clipSegment( Point3F& pnt0, Point3F& pnt1 ) const;
- /// Clip a convex polygon by all planes in the set.
- ///
- /// @param inVertices Array holding the vertices of the input polygon.
- /// @param inNumVertices Number of vertices in @a inVertices.
- /// @param outVertices Array to hold the vertices of the clipped polygon. Must have spaces for
- /// @a inNumVertices + numberOfPlanesInSet. Must not be the same as @a inVertices.
- /// @param maxOutVertices Maximum number of vertices that can be stored in @a outVertices. If insufficient to
- /// store the clipped polygon, the return value will be 0.
- ///
- /// @return Number of vertices in the clipped polygon, i.e. number of vertices stored in @a outVertices.
- ///
- /// @note Be aware that if the polygon fully lies on the negative side of all planes,
- /// the resulting @a outNumVertices will be zero, i.e. no polygon will result from the clip.
- U32 clipPolygon( const Point3F* inVertices, U32 inNumVertices, Point3F* outVertices, U32 maxOutVertices ) const;
- /// @}
- };
- typedef PlaneSet< PlaneF > PlaneSetF;
- typedef PlaneSet< PlaneD > PlaneSetD;
- //-----------------------------------------------------------------------------
- template< typename T >
- template< typename P >
- inline OverlapTestResult PlaneSet< T >::_testOverlap( const P& bounds ) const
- {
- bool allInside = true;
- // First, find out whether there is any plane for which the bounds completely
- // lie on the negative side. If so, the bounds are clearly outside of the volume.
-
- const U32 numPlanes = getNumPlanes();
- for( U32 nplane = 0; nplane < numPlanes; ++ nplane )
- {
- const PlaneF& plane = getPlanes()[ nplane ];
- const PlaneF::Side side = plane.whichSide( bounds );
- if( side == PlaneF::Back )
- return GeometryOutside;
- if( side != PlaneF::Front )
- allInside = false;
- }
- // Test for containment.
- if( allInside )
- return GeometryInside;
- // Otherwise classify as intersecting.
- return GeometryIntersecting;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- inline bool PlaneSet< T >::isContained( const Point3F& point, F32 epsilon ) const
- {
- epsilon = - epsilon;
- for( U32 i = 0; i < mNumPlanes; ++ i )
- {
- const F32 dist = mPlanes[ i ].distToPlane( point );
- if( dist < epsilon )
- return false;
- }
- return true;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- inline bool PlaneSet< T >::isContained( const Point3F* points, U32 numPoints ) const
- {
- for( U32 i = 0; i < numPoints; ++ i )
- if( !isContained( points[ i ] ) )
- return false;
- return true;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- template< typename P >
- inline bool PlaneSet< T >::_isContained( const P& bounds ) const
- {
- for( U32 i = 0; i < mNumPlanes; ++ i )
- if( mPlanes[ i ].whichSide( bounds ) != PlaneF::Front )
- return false;
- return true;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- U32 PlaneSet< T >::testPlanes( const Box3F& bounds, U32 planeMask, F32 expand ) const
- {
- AssertFatal( mNumPlanes <= 32, "PlaneSet::testPlanes - Too many planes in set!" );
- // This is based on the paper "A Faster Overlap Test for a Plane and a Bounding Box"
- // by Kenny Hoff. See http://www.cs.unc.edu/~hoff/research/vfculler/boxplane.html
- U32 retMask = 0;
- const U32 numPlanes = getMin( mNumPlanes, U32( 32 ) );
- for ( S32 i = 0; i < numPlanes; i++ )
- {
- U32 mask = ( 1 << i );
- if ( !( planeMask & mask ) )
- continue;
- const PlaneF& plane = mPlanes[ i ];
- Point3F minPoint, maxPoint;
- if( plane.x > 0 )
- {
- maxPoint.x = bounds.maxExtents.x;
- minPoint.x = bounds.minExtents.x;
- }
- else
- {
- maxPoint.x = bounds.minExtents.x;
- minPoint.x = bounds.maxExtents.x;
- }
- if( plane.y > 0 )
- {
- maxPoint.y = bounds.maxExtents.y;
- minPoint.y = bounds.minExtents.y;
- }
- else
- {
- maxPoint.y = bounds.minExtents.y;
- minPoint.y = bounds.maxExtents.y;
- }
- if( plane.z > 0 )
- {
- maxPoint.z = bounds.maxExtents.z;
- minPoint.z = bounds.minExtents.z;
- }
- else
- {
- maxPoint.z = bounds.minExtents.z;
- minPoint.z = bounds.maxExtents.z;
- }
- F32 maxDot = mDot( maxPoint, plane );
- if( maxDot <= - ( plane.d + expand ) )
- return -1;
- F32 minDot = mDot( minPoint, plane );
- if( ( minDot + plane.d ) < 0.0f )
- retMask |= mask;
- }
- return retMask;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- bool PlaneSet< T >::clipSegment( Point3F &pnt0, Point3F &pnt1 ) const
- {
- F32 tmin = F32_MAX;
- F32 tmax = -F32_MAX;
- U32 hitCount = 0;
- Point3F tpnt;
- const U32 numPlanes = mNumPlanes;
- for( U32 i = 0; i < numPlanes; ++ i )
- {
- const PlaneF &plane = mPlanes[ i ];
- F32 t = plane.intersect( pnt0, pnt1 );
- if( t >= 0.0f && t <= 1.0f )
- {
- tpnt.interpolate( pnt0, pnt1, t );
- if ( isContained( tpnt, 1.0e-004f ) )
- {
- tmin = getMin( tmin, t );
- tmax = getMax( tmax, t );
- hitCount ++;
- }
- }
- }
- // If we had no intersections then either both points are inside or both are outside.
- if( hitCount == 0 )
- return isContained( pnt0 );
- // If we had one intersection then we have one point inside.
- // tmin and tmax are the same here.
- if( hitCount == 1 )
- {
- if( isContained( pnt0 ) )
- pnt1.interpolate( pnt0, pnt1, tmax );
- else
- pnt0.interpolate( pnt0, pnt1, tmin );
- }
- else
- {
- Point3F prevPnt0( pnt0 );
- Point3F prevPnt1( pnt1 );
- if( tmin < F32_MAX )
- pnt0.interpolate( prevPnt0, prevPnt1, tmin );
- if( tmax > -F32_MAX )
- pnt1.interpolate( prevPnt0, prevPnt1, tmax );
- }
- return true;
- }
- //-----------------------------------------------------------------------------
- template< typename T >
- U32 PlaneSet< T >::clipPolygon( const Point3F* inVertices, U32 inNumVertices, Point3F* outVertices, U32 maxOutVertices ) const
- {
- TempAlloc< Point3F > tempBuffer( inNumVertices + mNumPlanes );
- // We use two buffers as interchanging roles as source and target.
- // For the first iteration, inVertices is the source.
- Point3F* tempPolygon = tempBuffer;
- Point3F* clippedPolygon = const_cast< Point3F* >( inVertices );
- U32 numClippedPolygonVertices = inNumVertices;
- U32 numTempPolygonVertices = 0;
- for( U32 nplane = 0; nplane < mNumPlanes; ++ nplane )
- {
- // Make the output of the last iteration the
- // input of this iteration.
- T3D::swap( tempPolygon, clippedPolygon );
- numTempPolygonVertices = numClippedPolygonVertices;
- if( maxOutVertices < numTempPolygonVertices + 1 )
- return 0;
- // Clip our current remainder of the original polygon
- // against the current plane.
- const PlaneF& plane = mPlanes[ nplane ];
- numClippedPolygonVertices = plane.clipPolygon( tempPolygon, numTempPolygonVertices, clippedPolygon );
- // If the polygon was completely on the backside of the plane,
- // then polygon is outside the frustum. In this case, return false
- // to indicate we haven't clipped anything.
- if( !numClippedPolygonVertices )
- return 0;
- // On first iteration, replace the inVertices with the
- // outVertices buffer.
- if( tempPolygon == inVertices )
- tempPolygon = outVertices;
- }
- // If outVertices isn't the target buffer of the last
- // iteration, copy the vertices over from the temporary
- // buffer.
- if( clippedPolygon != outVertices )
- dMemcpy( outVertices, clippedPolygon, numClippedPolygonVertices * sizeof( Point3F ) );
- return numClippedPolygonVertices;
- }
- #endif // !_MPLANESET_H_
|