void GroundPlane::buildConvex( const Box3F& box, Convex* convex ) { mConvexList->collectGarbage(); Box3F planeBox = getPlaneBox(); if ( !box.isOverlapped( planeBox ) ) return; // See if we already have a convex in the working set. BoxConvex *boxConvex = NULL; CollisionWorkingList &wl = convex->getWorkingList(); CollisionWorkingList *itr = wl.wLink.mNext; for ( ; itr != &wl; itr = itr->wLink.mNext ) { if ( itr->mConvex->getType() == BoxConvexType && itr->mConvex->getObject() == this ) { boxConvex = (BoxConvex*)itr->mConvex; break; } } if ( !boxConvex ) { boxConvex = new BoxConvex; mConvexList->registerObject( boxConvex ); boxConvex->init( this ); convex->addToWorkingList( boxConvex ); } // Update our convex to best match the queried box if ( boxConvex ) { Point3F queryCenter = box.getCenter(); boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF ); boxConvex->mSize = Point3F( box.getExtents().x, box.getExtents().y, GROUND_PLANE_BOX_HEIGHT_HALF ); } }
bool SceneCullingState::createCullingVolume( const Point3F* vertices, U32 numVertices, SceneCullingVolume::Type type, SceneCullingVolume& outVolume ) { const Point3F& viewPos = getCameraState().getViewPosition(); const Point3F& viewDir = getCameraState().getViewDirection(); const bool isOrtho = getCullingFrustum().isOrtho(); //TODO: check if we need to handle penetration of the near plane for occluders specially // Allocate space for the clipping planes we generate. Assume the worst case // of every edge generating a plane and, for includers, all edges meeting at // steep angles so we need to insert extra planes (the latter is not possible, // of course, but it makes things less complicated here). For occluders, add // an extra plane for the near cap. const U32 maxPlanes = ( type == SceneCullingVolume::Occluder ? numVertices + 1 : numVertices * 2 ); PlaneF* planes = allocateData< PlaneF >( maxPlanes ); // Keep track of the world-space bounds of the polygon. We use this later // to derive some metrics. Box3F wsPolyBounds; wsPolyBounds.minExtents = Point3F( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX ); wsPolyBounds.maxExtents = Point3F( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN ); // For occluders, also keep track of the nearest, and two farthest silhouette points. We use // this later to construct a near capping plane. F32 minVertexDistanceSquared = TypeTraits< F32 >::MAX; U32 leastDistantVert = 0; F32 maxVertexDistancesSquared[ 2 ] = { TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN }; U32 mostDistantVertices[ 2 ] = { 0, 0 }; // Generate the extrusion volume. For orthographic projections, extrude // parallel to the view direction whereas for parallel projections, extrude // from the viewpoint. U32 numPlanes = 0; U32 lastVertex = numVertices - 1; bool invert = false; for( U32 i = 0; i < numVertices; lastVertex = i, ++ i ) { AssertFatal( numPlanes < maxPlanes, "SceneCullingState::createCullingVolume - Did not allocate enough planes!" ); const Point3F& v1 = vertices[ i ]; const Point3F& v2 = vertices[ lastVertex ]; // Keep track of bounds. wsPolyBounds.minExtents.setMin( v1 ); wsPolyBounds.maxExtents.setMax( v1 ); // Skip the edge if it's length is really short. const Point3F edgeVector = v2 - v1; const F32 edgeVectorLenSquared = edgeVector.lenSquared(); if( edgeVectorLenSquared < 0.025f ) continue; //TODO: might need to do additional checks here for non-planar polygons used by occluders //TODO: test for colinearity of edge vector with view vector (occluders only) // Create a plane for the edge. if( isOrtho ) { // Compute a plane through the two edge vertices and one // of the vertices extended along the view direction. if( !invert ) planes[ numPlanes ] = PlaneF( v1, v1 + viewDir, v2 ); else planes[ numPlanes ] = PlaneF( v2, v1 + viewDir, v1 ); } else { // Compute a plane going through the viewpoint and the two // edge vertices. if( !invert ) planes[ numPlanes ] = PlaneF( v1, viewPos, v2 ); else planes[ numPlanes ] = PlaneF( v2, viewPos, v1 ); } numPlanes ++; // If this is the first plane that we have created, find out whether // the vertex ordering is giving us the plane orientations that we want // (facing inside). If not, invert vertex order from now on. if( numPlanes == 1 ) { Point3F center( 0, 0, 0 ); for( U32 n = 0; n < numVertices; ++ n ) center += vertices[n]; center /= numVertices; if( planes[numPlanes - 1].whichSide( center ) == PlaneF::Back ) { invert = true; planes[ numPlanes - 1 ].invert(); } } // For occluders, keep tabs of the nearest, and two farthest vertices. if( type == SceneCullingVolume::Occluder ) { const F32 distSquared = ( v1 - viewPos ).lenSquared(); if( distSquared < minVertexDistanceSquared ) { minVertexDistanceSquared = distSquared; leastDistantVert = i; } if( distSquared > maxVertexDistancesSquared[ 0 ] ) { // Move 0 to 1. maxVertexDistancesSquared[ 1 ] = maxVertexDistancesSquared[ 0 ]; mostDistantVertices[ 1 ] = mostDistantVertices[ 0 ]; // Replace 0. maxVertexDistancesSquared[ 0 ] = distSquared; mostDistantVertices[ 0 ] = i; } else if( distSquared > maxVertexDistancesSquared[ 1 ] ) { // Replace 1. maxVertexDistancesSquared[ 1 ] = distSquared; mostDistantVertices[ 1 ] = i; } } } // If the extrusion produced no useful result, abort. if( numPlanes < 3 ) return false; // For includers, test the angle of the edges at the current vertex. // If too steep, add an extra plane to improve culling efficiency. if( false )//type == SceneCullingVolume::Includer ) { const U32 numOriginalPlanes = numPlanes; U32 lastPlaneIndex = numPlanes - 1; for( U32 i = 0; i < numOriginalPlanes; lastPlaneIndex = i, ++ i ) { const PlaneF& currentPlane = planes[ i ]; const PlaneF& lastPlane = planes[ lastPlaneIndex ]; // Compute the cosine of the angle between the two plane normals. const F32 cosAngle = mFabs( mDot( currentPlane, lastPlane ) ); // The planes meet at increasingly steep angles the more they point // in opposite directions, i.e the closer the angle of their normals // is to 180 degrees. Skip any two planes that don't get near that. if( cosAngle > 0.1f ) continue; //TODO const Point3F addNormals = currentPlane + lastPlane; const Point3F crossNormals = mCross( currentPlane, lastPlane ); Point3F newNormal = currentPlane + lastPlane;//addNormals - mDot( addNormals, crossNormals ) * crossNormals; // planes[ numPlanes ] = PlaneF( currentPlane.getPosition(), newNormal ); numPlanes ++; } } // Compute the metrics of the culling volume in relation to the view frustum. // // For this, we are short-circuiting things slightly. The correct way (other than doing // full screen projections) would be to transform all the polygon points into camera // space, lay an AABB around those points, and then find the X and Z extents on the near plane. // // However, while not as accurate, a faster way is to just project the axial vectors // of the bounding box onto both the camera right and up vector. This gives us a rough // estimate of the camera-space size of the polygon we're looking at. const MatrixF& cameraTransform = getCameraState().getViewWorldMatrix(); const Point3F cameraRight = cameraTransform.getRightVector(); const Point3F cameraUp = cameraTransform.getUpVector(); const Point3F wsPolyBoundsExtents = wsPolyBounds.getExtents(); F32 widthEstimate = getMax( mFabs( wsPolyBoundsExtents.x * cameraRight.x ), getMax( mFabs( wsPolyBoundsExtents.y * cameraRight.y ), mFabs( wsPolyBoundsExtents.z * cameraRight.z ) ) ); F32 heightEstimate = getMax( mFabs( wsPolyBoundsExtents.x * cameraUp.x ), getMax( mFabs( wsPolyBoundsExtents.y * cameraUp.y ), mFabs( wsPolyBoundsExtents.z * cameraUp.z ) ) ); // If the current camera is a perspective one, divide the two estimates // by the distance of the nearest bounding box vertex to the camera // to account for perspective distortion. if( !isOrtho ) { const Point3F nearestVertex = wsPolyBounds.computeVertex( Box3F::getPointIndexFromOctant( - viewDir ) ); const F32 distance = ( nearestVertex - viewPos ).len(); widthEstimate /= distance; heightEstimate /= distance; } // If we are creating an occluder, check to see if the estimates fit // our minimum requirements. if( type == SceneCullingVolume::Occluder ) { const F32 widthEstimatePercentage = widthEstimate / getCullingFrustum().getWidth(); const F32 heightEstimatePercentage = heightEstimate / getCullingFrustum().getHeight(); if( widthEstimatePercentage < smOccluderMinWidthPercentage || heightEstimatePercentage < smOccluderMinHeightPercentage ) return false; // Reject. } // Use the area estimate as the volume's sort point. const F32 sortPoint = widthEstimate * heightEstimate; // Finally, if it's an occluder, compute a near cap. The near cap prevents objects // in front of the occluder from testing positive. The same could be achieved by // manually comparing distances before testing objects but since that would amount // to the same checks the plane/AABB tests do, it's easier to just add another plane. // Additionally, it gives the benefit of being able to create more precise culling // results by angling the plane. //NOTE: Could consider adding a near cap for includers too when generating a volume // for the outdoor zone as that may prevent quite a bit of space from being included. // However, given that this space will most likely just be filled with interior // stuff anyway, it's probably not worth it. if( type == SceneCullingVolume::Occluder ) { const U32 nearCapIndex = numPlanes; planes[ nearCapIndex ] = PlaneF( vertices[ mostDistantVertices[ 0 ] ], vertices[ mostDistantVertices[ 1 ] ], vertices[ leastDistantVert ] ); // Invert the plane, if necessary. if( planes[ nearCapIndex ].whichSide( viewPos ) == PlaneF::Front ) planes[ nearCapIndex ].invert(); numPlanes ++; } // Create the volume from the planes. outVolume = SceneCullingVolume( type, PlaneSetF( planes, numPlanes ) ); outVolume.setSortPoint( sortPoint ); // Done. return true; }
void GFXDrawUtil::drawCube( const GFXStateBlockDesc &desc, const Box3F &box, const ColorI &color, const MatrixF *xfm ) { drawCube( desc, box.getExtents(), box.getCenter(), color, xfm ); }