Torque3D/Engine/source/math/mPolyhedron.impl.h
2012-09-19 11:15:01 -04:00

506 lines
15 KiB
C++

//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "math/mPlaneTransformer.h"
//-----------------------------------------------------------------------------
template< typename Base >
Point3F PolyhedronImpl< Base >::getCenterPoint() const
{
const U32 numPoints = this->getNumPoints();
if( numPoints == 0 )
return Point3F( 0.f, 0.f, 0.f );
const typename Base::PointType* pointList = this->getPoints();
Point3F center( pointList[ 0 ] );
for( U32 i = 1; i < numPoints; ++ i )
center += pointList[ i ];
center /= ( F32 ) numPoints;
return center;
}
//-----------------------------------------------------------------------------
template< typename Base >
void PolyhedronImpl< Base >::transform( const MatrixF& matrix, const Point3F& scale )
{
// Transform points.
const U32 numPoints = this->getNumPoints();
typename Base::PointType* points = this->getPoints();
for( U32 i = 0; i < numPoints; ++ i )
{
matrix.mulP( points[ i ] );
points[ i ].convolve( scale );
}
// Transform planes.
const U32 numPlanes = this->getNumPlanes();
typename Base::PlaneType* planes = this->getPlanes();
PlaneTransformer transformer;
transformer.set( matrix, scale );
for( U32 i = 0; i < numPlanes; ++ i )
{
const typename Base::PlaneType& plane = planes[ i ];
PlaneF result;
transformer.transform( plane, result );
planes[ i ] = result;
}
}
//-----------------------------------------------------------------------------
template< typename Base >
U32 PolyhedronImpl< Base >::constructIntersection( const PlaneF& plane, Point3F* outPoints, U32 maxOutPoints ) const
{
// The assumption here is that the polyhedron is entirely composed
// of convex polygons implicitly described by the edges, points, and planes.
// So, any polygon can only have one of three relations to the given plane
//
// 1) None of its edges intersect with the plane, i.e. the polygon can be ignored.
// 2) One of its edges lies on the plane.
// 2) Two of its edges intersect with the plane.
//
// Conceptually, we need to find the first face with an intersecting edge, then find
// another edge on the same face that is also intersecting, and then continue with the
// face on the opposite side of the edge.
const U32 numEdges = this->getNumEdges();
const typename Base::EdgeType* edges = this->getEdges();
const typename Base::PointType* points = this->getPoints();
U32 numOutPoints = 0;
#define ADD_POINT( p ) \
if( numOutPoints >= maxOutPoints ) \
return 0; \
outPoints[ numOutPoints ++ ] = p;
Point3F intersection;
S32 firstEdge = -1;
S32 firstFace = -1;
U32 v1 = 0;
U32 v2 = 0;
PlaneF::Side v1Side = PlaneF::Front;
PlaneF::Side v2Side = PlaneF::Front;
// Find an edge to start with. This is the first edge
// in the polyhedron that intersects the plane.
for( U32 i = 0; i < numEdges; ++ i )
{
const typename Base::EdgeType& edge = edges[ i ];
v1 = edge.vertex[ 0 ];
v2 = edge.vertex[ 1 ];
const Point3F& p1 = points[ v1 ];
const Point3F& p2 = points[ v2 ];
v1Side = plane.whichSide( p1 );
v2Side = plane.whichSide( p2 );
if( v1Side == PlaneF::On || v2Side == PlaneF::On ||
plane.clipSegment( p1, p2, intersection ) )
{
firstEdge = i;
firstFace = edge.face[ 0 ];
break;
}
}
if( firstEdge == -1 )
return 0;
// Slice around the faces of the polyhedron until
// we get back to the starting face.
U32 currentEdge = firstEdge;
U32 currentFace = firstFace;
do
{
// Handle the current edge.
if( v1Side == PlaneF::On && v2Side == PlaneF::On )
{
// Both points of the edge lie on the plane. Add v2
// and then look for an edge that is also connected to v1
// *and* is connected to our current face. The other face
// of that edge is the one we need to continue with.
ADD_POINT( points[ v2 ] );
for( U32 n = 0; n < numEdges; ++ n )
{
const typename Base::EdgeType& e = edges[ n ];
// Skip current edge.
if( n == currentEdge )
continue;
// Skip edges not belonging to current face.
if( e.face[ 0 ] != currentFace && e.face[ 1 ] != currentFace )
continue;
// Skip edges not connected to the current point.
if( e.vertex[ 0 ] != edges[ currentEdge ].vertex[ 0 ] &&
e.vertex[ 1 ] != edges[ currentEdge ].vertex[ 0 ] )
continue;
// It's our edge. Continue on with the face that
// isn't our current one.
if( e.face[ 0 ] == currentFace )
currentFace = e.face[ 1 ];
else
currentFace = e.face[ 0 ];
currentEdge = n;
break;
}
}
else if( v1Side == PlaneF::On || v2Side == PlaneF::On )
{
// One of the points of the current edge is on the plane.
// Add that point.
if( v1Side == PlaneF::On )
{
ADD_POINT( points[ v1 ] );
}
else
{
ADD_POINT( points[ v2 ] );
}
// Find edge to continue with.
for( U32 n = 0; n < numEdges; ++ n )
{
const typename Base::EdgeType& e = edges[ n ];
// Skip current edge.
if( n == currentEdge )
continue;
// Skip edges not belonging to current face.
if( e.face[ 0 ] != currentFace && e.face[ 1 ] != currentFace )
continue;
// Skip edges connected to point that is on the plane.
if( v1Side == PlaneF::On )
{
if( e.vertex[ 0 ] == v1 || e.vertex[ 1 ] == v1 )
continue;
}
else
{
if( e.vertex[ 0 ] == v2 || e.vertex[ 1 ] == v2 )
continue;
}
// Skip edges not intersecting the plane.
U32 v1new = e.vertex[ 0 ];
U32 v2new = e.vertex[ 1 ];
const Point3F& p1 = points[ v1new ];
const Point3F& p2 = points[ v2new ];
PlaneF::Side v1SideNew = plane.whichSide( p1 );
PlaneF::Side v2SideNew = plane.whichSide( p2 );
if( v1SideNew != PlaneF::On && v2SideNew != PlaneF::On && !plane.clipSegment( p1, p2, intersection ) )
continue;
// It's our edge. Continue with the face on the
// opposite side.
if( e.face[ 0 ] == currentFace )
currentFace = e.face[ 1 ];
else
currentFace = e.face[ 0 ];
currentEdge = n;
v1 = v1new;
v2 = v2new;
v1Side = v1SideNew;
v2Side = v2SideNew;
break;
}
// Already have computed all the data.
continue;
}
// It's a clean intersecting somewhere along the edge. Add it.
else
{
ADD_POINT( intersection );
}
// Find edge to continue with.
for( U32 n = 0; n < numEdges; ++ n )
{
const typename Base::EdgeType& e = edges[ n ];
// Skip current edge.
if( n == currentEdge )
continue;
// Skip edges not belonging to current face.
if( e.face[ 0 ] != currentFace && e.face[ 1 ] != currentFace )
continue;
// Skip edges not intersecting the plane.
v1 = e.vertex[ 0 ];
v2 = e.vertex[ 1 ];
const Point3F& p1 = points[ v1 ];
const Point3F& p2 = points[ v2 ];
PlaneF::Side v1SideNew = plane.whichSide( p1 );
PlaneF::Side v2SideNew = plane.whichSide( p2 );
if( v1SideNew != PlaneF::On && v2SideNew != PlaneF::On && !plane.clipSegment( p1, p2, intersection ) )
continue;
// It's our edge. Make it the current one.
if( e.face[ 0 ] == currentFace )
currentFace = e.face[ 1 ];
else
currentFace = e.face[ 0 ];
currentEdge = n;
break;
}
}
//TODO: I guess this is insufficient; edges with vertices on the plane may lead us to take different
// paths depending on edge order
while( currentFace != firstFace &&
currentEdge != firstEdge );
return numOutPoints;
#undef ADD_POINT
}
//-----------------------------------------------------------------------------
template< typename Base >
template< typename IndexType >
U32 PolyhedronImpl< Base >::extractFace( U32 plane, IndexType* outIndices, U32 maxOutIndices ) const
{
AssertFatal( plane < this->getNumPlanes(), "PolyhedronImpl::extractFace - Plane index out of range!" );
// This method relies on the fact that vertices on the edges must be CW ordered
// for face[0]. If that is not the case, it is still possible to infer the correct
// ordering by finding one edge and a point not on the edge but still on
// the polygon. By constructing a plane through that edge (simple cross product) and
// then seeing which side the point is on, we know which direction is the right one
// for the polygon. The implicit CW ordering spares us from having to do that, though.
const U32 numEdges = this->getNumEdges();
const Edge* edges = this->getEdges();
// Find first edge that belongs to the plane.
const Edge* firstEdge = 0;
for( U32 i = 0; i < numEdges; ++ i )
{
const Edge& edge = edges[ i ];
if( edge.face[ 0 ] == plane || edge.face[ 1 ] == plane )
{
firstEdge = &edge;
break;
}
}
// If we have found no edge, the polyhedron is degenerate,
// so abort.
if( !firstEdge )
return 0;
// Choose vertex that begins a CCW traversal for this plane.
//
// Note that we expect the planes to be facing inwards by default so we
// go the opposite direction to yield a polygon facing the other way.
U32 idx = 0;
U32 currentVertex;
const Edge* currentEdge = firstEdge;
if( firstEdge->face[ 0 ] == plane )
currentVertex = firstEdge->vertex[ 0 ];
else
currentVertex = firstEdge->vertex[ 1 ];
// Now spider along the edges until we have gathered all indices
// for the plane in the right order.
//
// For larger edge sets, it would be more efficient to first extract
// all edges for the plane and then loop only over this subset to
// spider along the indices. However, we tend to have small sets
// so it should be sufficiently fast to just loop over the original
// set.
do
{
// Add the vertex for the current edge.
if( idx >= maxOutIndices )
return 0;
outIndices[ idx ++ ] = currentVertex;
// Look for next edge.
for( U32 i = 0; i < numEdges; ++ i )
{
const Edge& edge = edges[ i ];
// Skip if we hit the edge that we are looking to continue from.
if( &edge == currentEdge )
continue;
// Skip edge if it doesn't belong to the current plane.
if( edge.face[ 0 ] != plane && edge.face[ 1 ] != plane )
continue;
// If edge connects to vertex we are looking for, make it
// the current edge and push its other vertex.
if( edge.vertex[ 0 ] == currentVertex )
currentVertex = edge.vertex[ 1 ];
else if( edge.vertex[ 1 ] == currentVertex )
currentVertex = edge.vertex[ 0 ];
else
continue; // Skip edge.
currentEdge = &edge;
break;
}
}
while( currentEdge != firstEdge );
// Done.
return idx;
}
//-----------------------------------------------------------------------------
template< typename Polyhedron >
void PolyhedronData::buildBoxData( Polyhedron& poly, const MatrixF& mat, const Box3F& box, bool invertNormals )
{
AssertFatal( poly.getNumPoints() == 8, "PolyhedronData::buildBox - Incorrect point count!" );
AssertFatal( poly.getNumEdges() == 12, "PolyhedronData::buildBox - Incorrect edge count!" );
AssertFatal( poly.getNumPlanes() == 6, "PolyhedronData::buildBox - Incorrect plane count!" );
// Box is assumed to be axis aligned in the source space.
// Transform into geometry space.
Point3F xvec = mat.getRightVector() * box.len_x();
Point3F yvec = mat.getForwardVector() * box.len_y();
Point3F zvec = mat.getUpVector() * box.len_z();
Point3F min;
mat.mulP( box.minExtents, &min );
// Corner points.
typename Polyhedron::PointListType& pointList = poly.pointList;
pointList[ 0 ] = min; // near left bottom
pointList[ 1 ] = min + yvec; // far left bottom
pointList[ 2 ] = min + xvec + yvec; // far right bottom
pointList[ 3 ] = min + xvec; // near right bottom
pointList[ 4 ] = pointList[ 0 ] + zvec; // near left top
pointList[ 5 ] = pointList[ 1 ] + zvec; // far left top
pointList[ 6 ] = pointList[ 2 ] + zvec; // far right top
pointList[ 7 ] = pointList[ 3 ] + zvec; // near right top
// Side planes.
typename Polyhedron::PlaneListType& planeList = poly.planeList;
const F32 pos = invertNormals ? -1.f : 1.f;
const F32 neg = - pos;
planeList[ 0 ].set( pointList[ 0 ], xvec * pos ); // left
planeList[ 1 ].set( pointList[ 2 ], yvec * neg ); // far
planeList[ 2 ].set( pointList[ 2 ], xvec * neg ); // right
planeList[ 3 ].set( pointList[ 0 ], yvec * pos ); // front
planeList[ 4 ].set( pointList[ 0 ], zvec * pos ); // bottom
planeList[ 5 ].set( pointList[ 4 ], zvec * neg ); // top
// The edges are constructed so that the vertices
// are oriented clockwise for face[0].
typename Polyhedron::EdgeType* edge = &poly.edgeList[ 0 ];
for( U32 i = 0; i < 4; ++ i )
{
S32 n = ( i == 3 ) ? 0: i + 1;
S32 p = ( i == 0 ) ? 3: i - 1;
edge->vertex[ 0 ] = !invertNormals ? n : i;
edge->vertex[ 1 ] = !invertNormals ? i : n;
edge->face[ 0 ] = i;
edge->face[ 1 ] = 4;
edge ++;
edge->vertex[ 0 ] = !invertNormals ? 4 + n : 4 + i;
edge->vertex[ 1 ] = !invertNormals ? 4 + i : 4 + n;
edge->face[ 0 ] = 5;
edge->face[ 1 ] = i;
edge ++;
edge->vertex[ 0 ] = !invertNormals ? 4 + i : i;
edge->vertex[ 1 ] = !invertNormals ? i : 4 + i;
edge->face[ 0 ] = p;
edge->face[ 1 ] = i;
edge ++;
}
}