//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "sceneGraph/shadowVolumeBSP.h" #include "math/mPlane.h" ShadowVolumeBSP::ShadowVolumeBSP() : mSVRoot(0), mNodeStore(0), mPolyStore(0), mFirstInteriorNode(0) { } ShadowVolumeBSP::~ShadowVolumeBSP() { for(U32 i = 0; i < mSurfaces.size(); i++) delete mSurfaces[i]; } void ShadowVolumeBSP::insertShadowVolume(SVNode ** root, U32 volume) { SVNode * traverse = mShadowVolumes[volume]; // insert 'em while(traverse) { // copy it *root = createNode(); (*root)->mPlaneIndex = traverse->mPlaneIndex; (*root)->mSurfaceInfo = traverse->mSurfaceInfo; (*root)->mShadowVolume = traverse->mShadowVolume; // do the next root = &(*root)->mFront; traverse = traverse->mFront; } } ShadowVolumeBSP::SVNode::Side ShadowVolumeBSP::whichSide(SVPoly * poly, const PlaneF & plane) const { bool front = false; bool back = false; for(U32 i = 0; i < poly->mWindingCount; i++) { switch(plane.whichSide(poly->mWinding[i])) { case PlaneF::Front: if(back) return(SVNode::Split); front = true; break; case PlaneF::Back: if(front) return(SVNode::Split); back = true; break; default: break; } } AssertFatal(!(front && back), "ShadowVolumeBSP::whichSide - failed to classify poly"); if(!front && !back) return(SVNode::On); return(front ? SVNode::Front : SVNode::Back); } void ShadowVolumeBSP::splitPoly(SVPoly * poly, const PlaneF & plane, SVPoly ** front, SVPoly ** back) { PlaneF::Side sides[SVPoly::MaxWinding]; U32 i; for(i = 0; i < poly->mWindingCount; i++) sides[i] = plane.whichSide(poly->mWinding[i]); // create the polys (*front) = createPoly(); (*back) = createPoly(); // copy the info (*front)->mWindingCount = (*back)->mWindingCount = 0; (*front)->mPlane = (*back)->mPlane = poly->mPlane; (*front)->mTarget = (*back)->mTarget = poly->mTarget; (*front)->mSurfaceInfo = (*back)->mSurfaceInfo = poly->mSurfaceInfo; (*front)->mShadowVolume = (*back)->mShadowVolume = poly->mShadowVolume; // for(i = 0; i < poly->mWindingCount; i++) { U32 j = (i+1) % poly->mWindingCount; if(sides[i] == PlaneF::On) { (*front)->mWinding[(*front)->mWindingCount++] = poly->mWinding[i]; (*back)->mWinding[(*back)->mWindingCount++] = poly->mWinding[i]; } else if(sides[i] == PlaneF::Front) { (*front)->mWinding[(*front)->mWindingCount++] = poly->mWinding[i]; if(sides[j] == PlaneF::Back) { const Point3F & a = poly->mWinding[i]; const Point3F & b = poly->mWinding[j]; F32 t = plane.intersect(a, b); AssertFatal(t >=0 && t <= 1, "ShadowVolumeBSP::splitPoly - bad plane intersection"); Point3F pos; pos.interpolate(a, b, t); // (*front)->mWinding[(*front)->mWindingCount++] = (*back)->mWinding[(*back)->mWindingCount++] = pos; } } else if(sides[i] == PlaneF::Back) { (*back)->mWinding[(*back)->mWindingCount++] = poly->mWinding[i]; if(sides[j] == PlaneF::Front) { const Point3F & a = poly->mWinding[i]; const Point3F & b = poly->mWinding[j]; F32 t = plane.intersect(a, b); AssertFatal(t >=0 && t <= 1, "ShadowVolumeBSP::splitPoly - bad plane intersection"); Point3F pos; pos.interpolate(a, b, t); (*front)->mWinding[(*front)->mWindingCount++] = (*back)->mWinding[(*back)->mWindingCount++] = pos; } } } AssertFatal((*front)->mWindingCount && (*back)->mWindingCount, "ShadowVolume::split - invalid split"); } void ShadowVolumeBSP::addUniqueVolume(SurfaceInfo * surfaceInfo, U32 volume) { if(!surfaceInfo) return; for(U32 i = 0; i < surfaceInfo->mShadowed.size(); i++) if(surfaceInfo->mShadowed[i] == volume) return; // add it surfaceInfo->mShadowed.push_back(volume); } void ShadowVolumeBSP::insertPoly(SVNode ** root, SVPoly * poly) { if(!(*root)) { insertShadowVolume(root, poly->mShadowVolume); if(poly->mSurfaceInfo && !mFirstInteriorNode) mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; if(poly->mTarget) addUniqueVolume(poly->mTarget->mSurfaceInfo, poly->mShadowVolume); recyclePoly(poly); return; } const PlaneF & plane = getPlane((*root)->mPlaneIndex); // switch(whichSide(poly, plane)) { case SVNode::On: case SVNode::Front: insertPolyFront(root, poly); break; case SVNode::Back: insertPolyBack(root, poly); break; case SVNode::Split: { SVPoly * front; SVPoly * back; splitPoly(poly, plane, &front, &back); recyclePoly(poly); insertPolyFront(root, front); insertPolyBack(root, back); break; } } } void ShadowVolumeBSP::insertPolyFront(SVNode ** root, SVPoly * poly) { // POLY type node? if(!(*root)->mFront) { if(poly->mSurfaceInfo && !mFirstInteriorNode) mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; addUniqueVolume(poly->mSurfaceInfo, (*root)->mShadowVolume); recyclePoly(poly); } else insertPoly(&(*root)->mFront, poly); } void ShadowVolumeBSP::insertPolyBack(SVNode ** root, SVPoly * poly) { // list of nodes where an interior has been added if(poly->mSurfaceInfo && !(*root)->mSurfaceInfo && !(*root)->mBack) { if(!mFirstInteriorNode) mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; mParentNodes.push_back(*root); } // POLY type node? if(!(*root)->mFront) { poly->mTarget = (*root); insertPoly(&(*root)->mBack, poly); } else insertPoly(&(*root)->mBack, poly); } //------------------------------------------------------------------------------ ShadowVolumeBSP::SVNode * ShadowVolumeBSP::createNode() { SVNode * node; if(mNodeStore) { node = mNodeStore; mNodeStore = mNodeStore->mFront; } else node = mNodeChunker.alloc(); // node->mFront = node->mBack = 0; node->mShadowVolume = 0; node->mSurfaceInfo = 0; return(node); } void ShadowVolumeBSP::recycleNode(SVNode * node) { if(!node) return; recycleNode(node->mFront); recycleNode(node->mBack); // node->mFront = mNodeStore; node->mBack = 0; mNodeStore = node; } ShadowVolumeBSP::SVPoly * ShadowVolumeBSP::createPoly() { SVPoly * poly; if(mPolyStore) { poly = mPolyStore; mPolyStore = mPolyStore->mNext; } else poly = mPolyChunker.alloc(); // poly->mNext = 0; poly->mTarget = 0; poly->mSurfaceInfo = 0; poly->mShadowVolume = 0; return(poly); } void ShadowVolumeBSP::recyclePoly(SVPoly * poly) { if(!poly) return; recyclePoly(poly->mNext); // poly->mNext = mPolyStore; mPolyStore = poly; } U32 ShadowVolumeBSP::insertPlane(const PlaneF & plane) { mPlanes.push_back(plane); return(mPlanes.size() - 1); } const PlaneF & ShadowVolumeBSP::getPlane(U32 index) const { AssertFatal(index < mPlanes.size(), "ShadowVolumeBSP::getPlane - index out of range"); return(mPlanes[index]); } ShadowVolumeBSP::SVNode * ShadowVolumeBSP::getShadowVolume(U32 index) { AssertFatal(index < mShadowVolumes.size(), "ShadowVolumeBSP::getShadowVolume - index out of range"); return(mShadowVolumes[index]); } bool ShadowVolumeBSP::testPoint(SVNode * root, const Point3F & pnt) { const PlaneF & plane = getPlane(root->mPlaneIndex); switch(plane.whichSide(pnt)) { case PlaneF::On: if(!root->mFront) return(true); else { if(testPoint(root->mFront, pnt)) return(true); else { if(!root->mBack) return(false); else return(testPoint(root->mBack, pnt)); } } break; // case PlaneF::Front: if(root->mFront) return(testPoint(root->mFront, pnt)); else return(true); break; // case PlaneF::Back: if(root->mBack) return(testPoint(root->mBack, pnt)); else return(false); break; } return(false); } //------------------------------------------------------------------------------ bool ShadowVolumeBSP::testPoly(SVNode * root, SVPoly * poly) { const PlaneF & plane = getPlane(root->mPlaneIndex); switch(whichSide(poly, plane)) { case SVNode::On: case SVNode::Front: if(root->mFront) return(testPoly(root->mFront, poly)); recyclePoly(poly); return(true); case SVNode::Back: if(root->mBack) return(testPoly(root->mBack, poly)); recyclePoly(poly); break; case SVNode::Split: { if(!root->mFront) { recyclePoly(poly); return(true); } SVPoly * front; SVPoly * back; splitPoly(poly, plane, &front, &back); recyclePoly(poly); if(testPoly(root->mFront, front)) { recyclePoly(back); return(true); } if(root->mBack) return(testPoly(root->mBack, back)); recyclePoly(back); break; } } return(false); } //------------------------------------------------------------------------------ void ShadowVolumeBSP::buildPolyVolume(SVPoly * poly, LightInfo * light) { if(light->mType != LightInfo::Vector) return; // build the poly Point3F pointOffset = light->mDirection * 10.f; // create the shadow volume mShadowVolumes.increment(); SVNode ** traverse = &mShadowVolumes.last(); U32 shadowVolumeIndex = mShadowVolumes.size() - 1; for(U32 i = 0; i < poly->mWindingCount; i++) { U32 j = (i + 1) % poly->mWindingCount; if(poly->mWinding[i] == poly->mWinding[j]) continue; (*traverse) = createNode(); Point3F & a = poly->mWinding[i]; Point3F & b = poly->mWinding[j]; Point3F c = b + pointOffset; (*traverse)->mPlaneIndex = insertPlane(PlaneF(a,b,c)); (*traverse)->mShadowVolume = shadowVolumeIndex; traverse = &(*traverse)->mFront; } // do the poly node (*traverse) = createNode(); (*traverse)->mPlaneIndex = insertPlane(poly->mPlane); (*traverse)->mShadowVolume = poly->mShadowVolume = shadowVolumeIndex; } ShadowVolumeBSP::SVPoly * ShadowVolumeBSP::copyPoly(SVPoly * src) { SVPoly * poly = createPoly(); dMemcpy(poly, src, sizeof(SVPoly)); poly->mTarget = 0; poly->mNext = 0; return(poly); } //------------------------------------------------------------------------------ void ShadowVolumeBSP::addToPolyList(SVPoly ** store, SVPoly * poly) const { poly->mNext = *store; *store = poly; } //------------------------------------------------------------------------------ void ShadowVolumeBSP::clipPoly(SVNode * root, SVPoly ** store, SVPoly * poly) { if(!root) { recyclePoly(poly); return; } const PlaneF & plane = getPlane(root->mPlaneIndex); switch(whichSide(poly, plane)) { case SVNode::On: case SVNode::Back: if(root->mBack) clipPoly(root->mBack, store, poly); else addToPolyList(store, poly); break; case SVNode::Front: // encountered POLY node? if(!root->mFront) { recyclePoly(poly); return; } else clipPoly(root->mFront, store, poly); break; case SVNode::Split: { SVPoly * front; SVPoly * back; splitPoly(poly, plane, &front, &back); AssertFatal(front && back, "ShadowVolumeBSP::clipPoly: invalid split"); recyclePoly(poly); // front if(!root->mFront) { recyclePoly(front); return; } else clipPoly(root->mFront, store, front); // back if(root->mBack) clipPoly(root->mBack, store, back); else addToPolyList(store, back); break; } } } // clip a poly to it's own shadow volume void ShadowVolumeBSP::clipToSelf(SVNode * root, SVPoly ** store, SVPoly * poly) { if(!root) { addToPolyList(store, poly); return; } const PlaneF & plane = getPlane(root->mPlaneIndex); switch(whichSide(poly, plane)) { case SVNode::Front: clipToSelf(root->mFront, store, poly); break; case SVNode::On: addToPolyList(store, poly); break; case SVNode::Back: recyclePoly(poly); break; case SVNode::Split: { SVPoly * front = 0; SVPoly * back = 0; splitPoly(poly, plane, &front, &back); AssertFatal(front && back, "ShadowVolumeBSP::clipToSelf: invalid split"); recyclePoly(poly); recyclePoly(back); clipToSelf(root->mFront, store, front); break; } } } //------------------------------------------------------------------------------ F32 ShadowVolumeBSP::getPolySurfaceArea(SVPoly * poly) const { if(!poly) return(0.f); Point3F areaNorm(0,0,0); for(U32 i = 0; i < poly->mWindingCount; i++) { U32 j = (i + 1) % poly->mWindingCount; Point3F tmp; mCross(poly->mWinding[i], poly->mWinding[j], &tmp); areaNorm += tmp; } F32 area = mDot(poly->mPlane, areaNorm); if(area < 0.f) area *= -0.5f; else area *= 0.5f; if(poly->mNext) area += getPolySurfaceArea(poly->mNext); return(area); } //------------------------------------------------------------------------------ F32 ShadowVolumeBSP::getClippedSurfaceArea(SVNode * root, SVPoly * poly) { SVPoly * store = 0; clipPoly(root, &store, poly); F32 area = getPolySurfaceArea(store); recyclePoly(store); return(area); } //------------------------------------------------------------------------------- // Class SceneLighting::ShadowVolumeBSP //------------------------------------------------------------------------------- void ShadowVolumeBSP::movePolyList(SVPoly ** dest, SVPoly * list) const { while(list) { SVPoly * next = list->mNext; addToPolyList(dest, list); list = next; } } F32 ShadowVolumeBSP::getLitSurfaceArea(SVPoly * poly, SurfaceInfo * surfaceInfo) { // clip the poly to the shadow volumes SVPoly * polyStore = poly; for(U32 i = 0; polyStore && (i < surfaceInfo->mShadowed.size()); i++) { SVPoly * polyList = 0; SVPoly * traverse = polyStore; while(traverse) { SVPoly * next = traverse->mNext; traverse->mNext = 0; SVPoly * currentStore = 0; clipPoly(mShadowVolumes[surfaceInfo->mShadowed[i]], ¤tStore, traverse); if(currentStore) movePolyList(&polyList, currentStore); traverse = next; } polyStore = polyList; } // get the lit area F32 area = getPolySurfaceArea(polyStore); recyclePoly(polyStore); return(area); } //------------------------------------------------------------------------------ void ShadowVolumeBSP::removeLastInterior() { if(!mSVRoot || !mFirstInteriorNode) return; AssertFatal(mFirstInteriorNode->mSurfaceInfo, "No surface info for first interior node!"); // reset the planes mPlanes.setSize(mFirstInteriorNode->mPlaneIndex); U32 i; // flush the shadow volumes for(i = mFirstInteriorNode->mShadowVolume; i < mShadowVolumes.size(); i++) recycleNode(mShadowVolumes[i]); mShadowVolumes.setSize(mFirstInteriorNode->mShadowVolume); // flush the interior nodes if(!mParentNodes.size() && (mFirstInteriorNode->mShadowVolume == mSVRoot->mShadowVolume)) { recycleNode(mSVRoot); mSVRoot = 0; } else { for(i = 0; i < mParentNodes.size(); i++) { recycleNode(mParentNodes[i]->mBack); mParentNodes[i]->mBack = 0; } } // flush the surfaces for(i = 0; i < mSurfaces.size(); i++) delete mSurfaces[i]; mSurfaces.clear(); mFirstInteriorNode = 0; }