707 lines
17 KiB
C++
Executable File
707 lines
17 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|