/******************************************************************/ /* NaiveBVH.cpp */ /* ----------------------- */ /* */ /* The file defines an object container (i.e., an object that */ /* contains numerous others). This container creates a */ /* bounding volume hierarchy of the geometry stored inside. */ /* This allows for significantly improved traversal speeds. */ /* However, this may or may not be the most efficient */ /* acceleration structure, and the heuristic for dividing */ /* the geometry may need improvement! */ /* */ /* Chris Wyman (02/23/2007) */ /******************************************************************/ #include "DataTypes/Vector.h" #include "DataTypes/Point.h" #include "Core/BBox.h" #include "Core/Ray.h" #include "NaiveBVH.h" #include "Group.h" #include using namespace std; NaiveBVH::NaiveBVH( bool useMinimalNodeCount ) : bvhBuildComplete( false ), useMinimalNodeCount( useMinimalNodeCount ) { } NaiveBVH::~NaiveBVH() { } // Intersect a ray with a bounding box bool NaiveBVH::IntersectBBox( Ray &ray, Point *bounds ) const { float tmin = (bounds[ray.invDirSign[0]].X() - ray.origin.X()) * ray.invDir.X(); float tymax = (bounds[1-ray.invDirSign[1]].Y() - ray.origin.Y()) * ray.invDir.Y(); if ( tmin > tymax ) return false; float tmax = (bounds[1-ray.invDirSign[0]].X() - ray.origin.X()) * ray.invDir.X(); float tymin = (bounds[ray.invDirSign[1]].Y() - ray.origin.Y()) * ray.invDir.Y(); if ( tymin > tmax ) return false; if ( tymax < tmax ) tmax = tymax; float tzmin = (bounds[ray.invDirSign[2]].Z() - ray.origin.Z()) * ray.invDir.Z(); if ( tzmin > tmax ) return false; if ( tymin > tmin ) tmin = tymin; float tzmax = (bounds[1-ray.invDirSign[2]].Z() - ray.origin.Z()) * ray.invDir.Z(); if ( tmin > tzmax ) return false; if ( tzmin > tmin ) tmin = tzmin; if ( tzmax < tmax ) tmax = tzmax; return ( (tmin < ray.hitDist) && (tmax > EPSILON) ); } void NaiveBVH::IntersectNode( Ray &ray, unsigned int index ) { // Check if we intersect the parent node if (!IntersectBBox( ray, nodes[index].bounds )) return; // Check if the parent is a leaf. If so, intersect the stored geometry if (nodes[index].IsLeaf()) { nodes[index].ptr->Intersect(ray); return; } // Check if we intersect the children nodes IntersectNode( ray, NaiveBVHNode::GetLeftChild( index ) ); IntersectNode( ray, NaiveBVHNode::GetRightChild( index ) ); } void NaiveBVH::Intersect( Ray &ray ) { // Intersect the parent node... IntersectNode( ray, 0 ); } void NaiveBVH::Preprocess( void ) { // Determine the number of nodes to use if (useMinimalNodeCount) nodes.SetSize( 2*objs.Size()-1 ); else { int logVal = (int)(logf( (float)(objs.Size()) )/logf( 2.0 )); nodes.SetSize( 4*(int)pow(2.0f,(float)logVal) ); } printf(" (-) Preprocessing NaiveBVH.... (%d prims => %d nodes)\n", objs.Size(), nodes.Size() ); // Create some temporary data to make the build more efficient (and my coding/debugging easier) NaiveBVHSortable *sortableData = new NaiveBVHSortable[ objs.Size() ]; // Make sure all the internal objects have been processed before we build the heirarchy, then // populate the temporary array with information about all our primitives for (unsigned int i=0; iPreprocess(); sortableData[i].bbox.Reset(); objs[i]->ExpandBounds( sortableData[i].bbox ); sortableData[i].ptr = objs[i]; sortableData[i].finalNodeIdx = i; } // Go ahead an build the hierarchy BuildHierarchy( 0, 0, (unsigned int)objs.Size(), sortableData ); // Clean up the memory. delete [] sortableData; // Done with the build bvhBuildComplete = true; //PrintHierarchy(); printf(" (-) Done building NaiveBVH...\n"); } void NaiveBVH::BuildHierarchy( unsigned int parentIdx, unsigned int startIdx, unsigned int endIdx, NaiveBVHSortable *data ) { // Initialize the node flag data... nodes[parentIdx].flags = 0x00000000; // Figure out the bounding volume for the current parent (look at all children primitives) BBox bounds; for (unsigned int i=startIdx; i < endIdx; i++) bounds.Expand( data[i].bbox ); // This bounds array is used for more efficient processing in IntersectBBox. It could be // coded more cleanly, without making a copy of the min/max... Oh well. nodes[parentIdx].bounds[0] = bounds.GetMin(); nodes[parentIdx].bounds[1] = bounds.GetMax(); // Check if we're done building (if we've only got 1 object in our list, we're a leaf node!) if (startIdx == endIdx-1) { // Note: We don't explicitly need to set the "leaf" flag, as it's implicitly set by // the pointer assignment. A leaf has a 0 in the smallest bit of the pointer. // Since the object must be aligned to at least, 4-byte memory boundaries, the // small bit is guaranteed to be 0! nodes[parentIdx].ptr = data[startIdx].ptr; return; } // We're not a leaf... nodes[parentIdx].flags = NAIVEBVHNODE_NOTLEAF; // We need to split the primitves. We're doing this in a very naive manner, splitting // the longest axis. We sort along this axis so we can split the geometry into 2 sets. Vector delta = nodes[parentIdx].bounds[1] - nodes[parentIdx].bounds[0]; if (delta.Y() > delta.X() && delta.Y() > delta.Z()) qsort( &data[startIdx], endIdx-startIdx, sizeof( NaiveBVHSortable ), NaiveBVH::SortObjectsByY ); else if (delta.Z() > delta.X() && delta.Z() > delta.X()) qsort( &data[startIdx], endIdx-startIdx, sizeof( NaiveBVHSortable ), NaiveBVH::SortObjectsByZ ); else qsort( &data[startIdx], endIdx-startIdx, sizeof( NaiveBVHSortable ), NaiveBVH::SortObjectsByX ); // We need to pick out our split position. unsigned int splitIdx; if (useMinimalNodeCount) { // If we're not wasting any nodes, we can't split the group *exactly* in half, because // we use implicit indexing (instead of storing pointer to the two children), this means // that we must either use a level in the tree *entirely*, or only use nodes on the // left-most side. This means we need to preferentially let the left side of the tree // become bigger so our last tree level reflects this circumstance. float logVal = logf( (float)(endIdx-startIdx) )/logf( 2.0 ); unsigned int maxChildSize = (unsigned int)pow( 2.0f, (float)((int)logVal) ); unsigned int minChildSize = maxChildSize/2; splitIdx = endIdx-minChildSize; if (splitIdx-startIdx > maxChildSize) splitIdx = startIdx+maxChildSize; } else // We're using a tree with extra space, so we don't need to worry about where in the // tree each node will be located -- just split the darn list in half. splitIdx = (endIdx+startIdx)/2; // Now that we've sorted the data, recursively build the subhierarchies BuildHierarchy( NaiveBVHNode::GetLeftChild( parentIdx ), startIdx, splitIdx, data ); BuildHierarchy( NaiveBVHNode::GetRightChild( parentIdx ), splitIdx, endIdx, data ); } // The following three functions are the comparison functions used by qsort above. If you want // to modify how the geometry is sorted, these are the functions to change. I'd really like // to make it cleaner (so I only need 1 sort function), but it's not obvious to me how to do // it, so I've skipped that part. // // Right now, these are sorting by the centroid of the bounding box. It should be obvious how // to change these to use the min point or the max point instead. int NaiveBVH::SortObjectsByX( const void *A, const void *B ) { Point aVal = (((NaiveBVHSortable *)A)->bbox.GetMin()+((NaiveBVHSortable *)A)->bbox.GetMax())*0.5; Point bVal = (((NaiveBVHSortable *)B)->bbox.GetMin()+((NaiveBVHSortable *)B)->bbox.GetMax())*0.5; float valA = aVal.X(); float valB = bVal.X(); if (valA > valB) return 1; if (valA == valB) return 0; return -1; } int NaiveBVH::SortObjectsByY( const void *A, const void *B ) { Point aVal = (((NaiveBVHSortable *)A)->bbox.GetMin()+((NaiveBVHSortable *)A)->bbox.GetMax())*0.5; Point bVal = (((NaiveBVHSortable *)B)->bbox.GetMin()+((NaiveBVHSortable *)B)->bbox.GetMax())*0.5; float valA = aVal.Y(); float valB = bVal.Y(); if (valA > valB) return 1; if (valA == valB) return 0; return -1; } int NaiveBVH::SortObjectsByZ( const void *A, const void *B ) { Point aVal = (((NaiveBVHSortable *)A)->bbox.GetMin()+((NaiveBVHSortable *)A)->bbox.GetMax())*0.5; Point bVal = (((NaiveBVHSortable *)B)->bbox.GetMin()+((NaiveBVHSortable *)B)->bbox.GetMax())*0.5; float valA = aVal.Z(); float valB = bVal.Z(); if (valA > valB) return 1; if (valA == valB) return 0; return -1; }