You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1072 lines
31 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "plCullTree.h"
#include "plDrawable/plSpaceTree.h"
#include "hsFastMath.h"
#include "hsColorRGBA.h"
#include "plProfile.h"
#include "plTweak.h"
#define MF_DEBUG_NORM
#ifdef MF_DEBUG_NORM
#define IDEBUG_NORMALIZE( a, b ) { hsScalar len = hsFastMath::InvSqrtAppr((a).MagnitudeSquared()); a *= len; b *= len; }
#else // MF_DEBUG_NORM
#define IDEBUG_NORMALIZE( a, b )
#endif // MF_DEBUG_NORM
//#define CULL_SMALL_TOLERANCE
#ifdef CULL_SMALL_TOLERANCE
//static const hsScalar kTolerance = 1.e-5f;
static const hsScalar kTolerance = 1.e-3f;
#else //CULL_SMALL_TOLERANCE
static const hsScalar kTolerance = 1.e-1f;
#endif // CULL_SMALL_TOLERANCE
plProfile_CreateCounter("Harvest Nodes", "Draw", HarvestNodes);
//////////////////////////////////////////////////////////////////////
// Harvest culling section.
// These are the functions used on a built tree
//////////////////////////////////////////////////////////////////////
plCullNode::plCullStatus plCullNode::ITestBoundsRecur(const hsBounds3Ext& bnd) const
{
plCullNode::plCullStatus retVal = TestBounds(bnd);
// No Children, what we say goes.
if( (fOuterChild < 0) && (fInnerChild < 0) )
return retVal;
// No innerchild. If we cull, it's culled, else we
// hope our outerchild culls it.
if( fInnerChild < 0 )
{
if( retVal == kCulled )
return kCulled;
return IGetNode(fOuterChild)->ITestBoundsRecur(bnd);
}
// No outerchild. If we say it's clear, it's clear (or split), but if
// it's culled, we have to pass it to innerchild, who may pronounce it clear
if( fOuterChild < 0 )
{
if( retVal == kClear )
return kClear;
if( retVal == kSplit )
return kSplit;
return IGetNode(fInnerChild)->ITestBoundsRecur(bnd);
}
// We've got both children to feed.
// We pass the clear ones to the inner child, culled to outer,
// and split to both. Remember, a both children have to agree to cull a split.
if( retVal == kClear )
return IGetNode(fOuterChild)->ITestBoundsRecur(bnd);
if( retVal == kCulled )
return IGetNode(fInnerChild)->ITestBoundsRecur(bnd);
// Here's the split, to be culled, both children have to
// say its culled.
if( kCulled != IGetNode(fOuterChild)->ITestBoundsRecur(bnd) )
return kSplit;
if( kCulled != IGetNode(fInnerChild)->ITestBoundsRecur(bnd) )
return kSplit;
return kCulled;
}
plCullNode::plCullStatus plCullNode::TestBounds(const hsBounds3Ext& bnd) const
{
// Not sure if doing a sphere test will pay off or not. Some circumstantial evidence
// from TrueTime suggests it could very well, but I really need to do some side by
// side timings to be sure. Still looking for some reasonably constructed real data sets. mf
#define MF_TEST_SPHERE_FIRST
#ifdef MF_TEST_SPHERE_FIRST
hsScalar dist = fNorm.InnerProduct(bnd.GetCenter()) + fDist;
hsScalar rad = bnd.GetRadius();
if( dist < -rad )
return kCulled;
if( dist > rad )
return kClear;
#endif // MF_TEST_SPHERE_FIRST
hsPoint2 depth;
bnd.TestPlane(fNorm, depth);
const hsScalar kSafetyDist = -0.1f;
if( depth.fY + fDist < kSafetyDist )
return kCulled;
if( depth.fX + fDist >= 0 )
return kClear;
return kSplit;
}
plCullNode::plCullStatus plCullNode::ITestSphereRecur(const hsPoint3& center, hsScalar rad) const
{
plCullNode::plCullStatus retVal = TestSphere(center, rad);
// No Children, what we say goes.
if( (fOuterChild < 0) && (fInnerChild < 0) )
return retVal;
// No innerchild. If we cull, it's culled, else we
// hope our outerchild culls it.
if( fInnerChild < 0 )
{
if( retVal == kCulled )
return kCulled;
return IGetNode(fOuterChild)->ITestSphereRecur(center, rad);
}
// No outerchild. If we say it's clear, it's clear (or split), but if
// it's culled, we have to pass it to innerchild, who may pronounce it clear
if( fOuterChild < 0 )
{
if( retVal == kClear )
return kClear;
if( retVal == kSplit )
return kSplit;
return IGetNode(fInnerChild)->ITestSphereRecur(center, rad);
}
// We've got both children to feed.
// We pass the clear ones to the inner child, culled to outer,
// and split to both. Remember, a both children have to agree to cull a split.
if( retVal == kClear )
return IGetNode(fOuterChild)->ITestSphereRecur(center, rad);
if( retVal == kCulled )
return IGetNode(fInnerChild)->ITestSphereRecur(center, rad);
// Here's the split, to be culled, both children have to
// say its culled.
if( kCulled != IGetNode(fOuterChild)->ITestSphereRecur(center, rad) )
return kSplit;
if( kCulled != IGetNode(fInnerChild)->ITestSphereRecur(center, rad) )
return kSplit;
return kCulled;
}
plCullNode::plCullStatus plCullNode::TestSphere(const hsPoint3& center, hsScalar rad) const
{
hsScalar dist = fNorm.InnerProduct(center) + fDist;
if( dist < -rad )
return kCulled;
if( dist > rad )
return kClear;
return kSplit;
}
// For this Cull Node, recur down the space hierarchy pruning out who to test for the next Cull Node.
plCullNode::plCullStatus plCullNode::ITestNode(const plSpaceTree* space, Int16 who, hsLargeArray<Int16>& clear, hsLargeArray<Int16>& split, hsLargeArray<Int16>& culled) const
{
if( space->IsDisabled(who) || (space->GetNode(who).fWorldBounds.GetType() != kBoundsNormal) )
{
culled.Append(who);
return kCulled;
}
plCullStatus retVal = kClear;
plCullStatus stat = TestBounds(space->GetNode(who).fWorldBounds);
switch( stat )
{
case kClear:
clear.Append(who);
retVal = kClear;
break;
case kCulled:
culled.Append(who);
retVal = kCulled;
break;
case kSplit:
if( space->GetNode(who).fFlags & plSpaceTreeNode::kIsLeaf )
{
// split.Append(who);
retVal = kPureSplit;
}
else
{
plCullStatus child0 = ITestNode(space, space->GetNode(who).GetChild(0), clear, split, culled);
plCullStatus child1 = ITestNode(space, space->GetNode(who).GetChild(1), clear, split, culled);
if( child0 != child1 )
{
if( child0 == kPureSplit )
split.Append(space->GetNode(who).GetChild(0));
else if( child1 == kPureSplit )
split.Append(space->GetNode(who).GetChild(1));
retVal = kSplit;
}
else if( child0 == kPureSplit )
{
retVal = kPureSplit;
}
}
}
return retVal;
}
// Cycle through the Cull Nodes, paring down the list of who to test (through ITestNode above).
// We reclaim the scratch indices in clear and split when we're done (SetCount(0)), but we can't
// reclaim the culled, because our caller may be looking at who all we culled. See below in split.
// If a node is disabled, we can just ignore we ever got called.
void plCullNode::ITestNode(const plSpaceTree* space, Int16 who, hsBitVector& totList, hsBitVector& outList) const
{
if( space->IsDisabled(who) )
return;
UInt32 myClearStart = ScratchClear().GetCount();
UInt32 mySplitStart = ScratchSplit().GetCount();
UInt32 myCullStart = ScratchCulled().GetCount();
if( kPureSplit == ITestNode(space, who, ScratchClear(), ScratchSplit(), ScratchCulled()) )
ScratchSplit().Append(who);
UInt32 myClearEnd = ScratchClear().GetCount();
UInt32 mySplitEnd = ScratchSplit().GetCount();
UInt32 myCullEnd = ScratchCulled().GetCount();
int i;
// If there's no OuterChild, everything in clear and split is visible. Everything in culled
// goes to innerchild (if any).
if( fOuterChild < 0 )
{
plProfile_IncCount(HarvestNodes, myClearEnd - myClearStart + mySplitEnd - mySplitStart);
// Replace these with a memcopy or something!!!!
for( i = myClearStart; i < myClearEnd; i++ )
{
space->HarvestLeaves(ScratchClear()[i], totList, outList);
}
for( i = mySplitStart; i < mySplitEnd; i++ )
{
space->HarvestLeaves(ScratchSplit()[i], totList, outList);
}
if( fInnerChild >= 0 )
{
for( i = myCullStart; i < myCullEnd; i++ )
{
IGetNode(fInnerChild)->ITestNode(space, ScratchCulled()[i], totList, outList);
}
}
ScratchClear().SetCount(myClearStart);
ScratchSplit().SetCount(mySplitStart);
ScratchCulled().SetCount(myCullStart);
return;
}
// There is an OuterChild, so whether there's an InnerChild or not,
// everything in ClearList is visible soley on the discretion of OuterChild.
for( i = myClearStart; i < myClearEnd; i++ )
{
IGetNode(fOuterChild)->ITestNode(space, ScratchClear()[i], totList, outList);
}
// If there's no InnerChild, then the SplitList is also visible soley
// on the discretion of OuterChild.
if( fInnerChild < 0 )
{
for( i = mySplitStart; i < mySplitEnd; i++ )
{
IGetNode(fOuterChild)->ITestNode(space, ScratchSplit()[i], totList, outList);
}
ScratchClear().SetCount(myClearStart);
ScratchSplit().SetCount(mySplitStart);
ScratchCulled().SetCount(myCullStart);
return;
}
// There is an inner child. Everything in culled list is visible
// soley on its discretion.
for( i = myCullStart; i < myCullEnd; i++ )
{
IGetNode(fInnerChild)->ITestNode(space, ScratchCulled()[i], totList, outList);
}
// Okay, here's the rub.
// Everyone in the split list needs to be tested against InnerChild and OuterChild.
// If either child says it's okay (puts it in OutList), then it's okay.
// The problem is that if both children say it's okay, it will wind up in outList twice.
// This is complicated by the fact that outList is still subTrees at this point,
// so InnerChild adding a subTree and OuterChild adding a child of that subTree isn't
// even appending the same value to the list.
// Sooooo.
// What we do is keep track of every node (interior and leaf) that gets harvested.
// When we go to harvest a subtree, we check in totList for its bit being set. Bits
// set in totList are ENTIRE SUBTREE IS HARVESTED. SpaceTree understands this too in
// its HarvestLeaves. Seems obvious now, but I didn't hear you suggest it.
for( i = mySplitStart; i < mySplitEnd; i++ )
{
IGetNode(fOuterChild)->ITestNode(space, ScratchSplit()[i], totList, outList);
}
for( i = mySplitStart; i < mySplitEnd; i++ )
{
if( !totList.IsBitSet(ScratchSplit()[i]) )
IGetNode(fInnerChild)->ITestNode(space, ScratchSplit()[i], totList, outList);
}
ScratchClear().SetCount(myClearStart);
ScratchSplit().SetCount(mySplitStart);
ScratchCulled().SetCount(myCullStart);
}
void plCullNode::IHarvest(const plSpaceTree* space, hsTArray<Int16>& outList) const
{
ITestNode(space, space->GetRoot(), ScratchTotVec(), ScratchBitVec());
space->BitVectorToList(outList, ScratchBitVec());
ScratchBitVec().Clear();
ScratchTotVec().Clear();
ScratchClear().SetCount(0);
ScratchSplit().SetCount(0);
ScratchCulled().SetCount(0);
}
//////////////////////////////////////////////////////////////////////
// This section builds the tree from the input cullpoly's
//////////////////////////////////////////////////////////////////////
void plCullNode::IBreakPoly(const plCullPoly& poly, const hsTArray<hsScalar>& depths,
hsBitVector& inVerts,
hsBitVector& outVerts,
hsBitVector& onVerts,
plCullPoly& outPoly) const
{
inVerts.Clear();
outVerts.Clear();
onVerts.Clear();
outPoly.Init(poly);
if( depths[0] < -kTolerance )
inVerts.SetBit(0);
else if( depths[0] > kTolerance )
outVerts.SetBit(0);
else
onVerts.SetBit(0);
if( poly.fClipped.IsBitSet(0) )
outPoly.fClipped.SetBit(0);
outPoly.fVerts.Append(poly.fVerts[0]);
int i;
for( i = 1; i < poly.fVerts.GetCount(); i++ )
{
if( depths[i] < -kTolerance )
{
if( outVerts.IsBitSet(outPoly.fVerts.GetCount()-1) )
{
hsPoint3 interp;
hsScalar t = IInterpVert(poly.fVerts[i-1], poly.fVerts[i], interp);
// add interp
onVerts.SetBit(outPoly.fVerts.GetCount());
if( poly.fClipped.IsBitSet(i-1) )
outPoly.fClipped.SetBit(outPoly.fVerts.GetCount());
outPoly.fVerts.Append(interp);
}
inVerts.SetBit(outPoly.fVerts.GetCount());
}
else if( depths[i] > kTolerance )
{
if( inVerts.IsBitSet(outPoly.fVerts.GetCount()-1) )
{
hsPoint3 interp;
hsScalar t = IInterpVert(poly.fVerts[i-1], poly.fVerts[i], interp);
// add interp
onVerts.SetBit(outPoly.fVerts.GetCount());
if( poly.fClipped.IsBitSet(i-1) )
outPoly.fClipped.SetBit(outPoly.fVerts.GetCount());
outPoly.fVerts.Append(interp);
}
outVerts.SetBit(outPoly.fVerts.GetCount());
}
else
{
onVerts.SetBit(outPoly.fVerts.GetCount());
}
if( poly.fClipped.IsBitSet(i) )
outPoly.fClipped.SetBit(outPoly.fVerts.GetCount());
outPoly.fVerts.Append(poly.fVerts[i]);
}
if( (inVerts.IsBitSet(outPoly.fVerts.GetCount()-1) && outVerts.IsBitSet(0))
||(outVerts.IsBitSet(outPoly.fVerts.GetCount()-1) && inVerts.IsBitSet(0)) )
{
hsPoint3 interp;
hsScalar t = IInterpVert(poly.fVerts[poly.fVerts.GetCount()-1], poly.fVerts[0], interp);
onVerts.SetBit(outPoly.fVerts.GetCount());
if( poly.fClipped.IsBitSet(poly.fVerts.GetCount()-1) )
outPoly.fClipped.SetBit(outPoly.fVerts.GetCount());
outPoly.fVerts.Append(interp);
}
}
void plCullNode::ITakeHalfPoly(const plCullPoly& srcPoly,
const hsTArray<int>& vtxIdx,
const hsBitVector& onVerts,
plCullPoly& outPoly) const
{
if( vtxIdx.GetCount() > 2 )
{
int i;
for( i = 0; i < vtxIdx.GetCount(); i++ )
{
int next = i < vtxIdx.GetCount()-1 ? i+1 : 0;
int last = i ? i-1 : vtxIdx.GetCount()-1;
// If these 3 verts are all on the plane, we may have created a collinear vertex (the middle one)
// which we now want to skip.
if( onVerts.IsBitSet(vtxIdx[i]) && onVerts.IsBitSet(vtxIdx[last]) && onVerts.IsBitSet(vtxIdx[next]) )
{
#if 0 // FISH
hsScalar dot = hsVector3(&srcPoly.fVerts[vtxIdx[last]], &srcPoly.fVerts[vtxIdx[i]]).InnerProduct(hsVector3(&srcPoly.fVerts[vtxIdx[next]], &srcPoly.fVerts[vtxIdx[i]]));
if( dot <= 0 )
#endif // FISH
continue;
}
if( srcPoly.fClipped.IsBitSet(vtxIdx[i])
||(onVerts.IsBitSet(vtxIdx[i]) && onVerts.IsBitSet(vtxIdx[next])) )
outPoly.fClipped.SetBit(outPoly.fVerts.GetCount());
outPoly.fVerts.Append(srcPoly.fVerts[vtxIdx[i]]);
}
}
else
{
// Just need a break point
hsStatusMessage("Under 2"); // FISH
}
}
void plCullNode::IMarkClipped(const plCullPoly& poly, const hsBitVector& onVerts) const
{
int i;
for( i = 1; i < poly.fVerts.GetCount(); i++ )
{
int last = i-1;
if( onVerts[i] && onVerts[last] )
poly.fClipped.SetBit(last);
}
if( onVerts[i] && onVerts[0] )
poly.fClipped.SetBit(0);
}
plCullNode::plCullStatus plCullNode::ISplitPoly(const plCullPoly& poly,
plCullPoly*& innerPoly,
plCullPoly*& outerPoly) const
{
static hsTArray<hsScalar> depths;
depths.SetCount(poly.fVerts.GetCount());
static hsBitVector onVerts;
onVerts.Clear();
hsBool someInner = false;
hsBool someOuter = false;
hsBool someOn = false;
int i;
for( i = 0; i < poly.fVerts.GetCount(); i++ )
{
depths[i] = fNorm.InnerProduct(poly.fVerts[i]) + fDist;
if( depths[i] < -kTolerance )
someInner = true;
else if( depths[i] > kTolerance )
someOuter = true;
else
{
someOn = true;
onVerts.SetBit(i);
}
}
if( !(someInner || someOuter) )
{
(innerPoly = ScratchPolys().Push())->Init(poly);
(outerPoly = ScratchPolys().Push())->Init(poly);
return kSplit;
}
else if( !someInner )
{
IMarkClipped(poly, onVerts);
return kClear;
}
else if( !someOuter )
{
IMarkClipped(poly, onVerts);
return kCulled;
}
// Okay, it's split, now break it into the two polys
(innerPoly = ScratchPolys().Push())->Init(poly);
(outerPoly = ScratchPolys().Push())->Init(poly);
static plCullPoly scrPoly;
static hsBitVector inVerts;
static hsBitVector outVerts;
IBreakPoly(poly, depths,
inVerts,
outVerts,
onVerts,
scrPoly);
static hsTArray<int> inPolyIdx;
inPolyIdx.SetCount(0);
static hsTArray<int> outPolyIdx;
outPolyIdx.SetCount(0);
for( i = 0; i < scrPoly.fVerts.GetCount(); i++ )
{
if( inVerts.IsBitSet(i) )
{
inPolyIdx.Append(i);
}
else if( outVerts.IsBitSet(i) )
{
outPolyIdx.Append(i);
}
else
{
inPolyIdx.Append(i);
outPolyIdx.Append(i);
}
}
ITakeHalfPoly(scrPoly, inPolyIdx, onVerts, *innerPoly);
ITakeHalfPoly(scrPoly, outPolyIdx, onVerts, *outerPoly);
return kSplit;
}
hsScalar plCullNode::IInterpVert(const hsPoint3& p0, const hsPoint3& p1, hsPoint3& out) const
{
hsVector3 oneToOh;
oneToOh.Set(&p0, &p1);
hsScalar t = -(fNorm.InnerProduct(p1) + fDist) / fNorm.InnerProduct(oneToOh);
if( t >= 1.f )
{
out = p0;
return 1.f;
}
if( t <= 0 )
{
out = p1;
return 0;
}
out = p1;
out += oneToOh * t;
return t;
}
// We use indices so our tree can actually be an array, which may be
// resized at any time, which would invalidate any pointers we have.
// But debugging a large tree is hard enough when stepping through pointers,
// it's pretty much impossible with indices. So when debugging we can
// setup these pointers for stepping through the tree. We just need to
// reset them every time we add a poly, because that's when the tree
// may have been resized invalidating the old pointers.
#ifdef DEBUG_POINTERS
void plCullNode::ISetPointersRecur() const
{
if( fInnerPtr = IGetNode(fInnerChild) )
fInnerPtr->ISetPointersRecur();
if( fOuterPtr = IGetNode(fOuterChild) )
fOuterPtr->ISetPointersRecur();
}
#endif // DEBUG_POINTERS
//////////////////////////////////////////////////////////////////////
// Now the tree proper
//////////////////////////////////////////////////////////////////////
// Build the tree
plCullTree::plCullTree()
: fRoot(-1),
fCapturePolys(false)
{
}
plCullTree::~plCullTree()
{
}
void plCullTree::AddPoly(const plCullPoly& poly)
{
const plCullPoly* usePoly = &poly;
hsVector3 cenToEye(&fViewPos, &poly.fCenter);
hsFastMath::NormalizeAppr(cenToEye);
hsScalar camDist = cenToEye.InnerProduct(poly.fNorm);
plConst(hsScalar) kTol(0.1f);
hsBool backFace = camDist < -kTol;
if( !backFace && (camDist < kTol) )
return;
plCullPoly scratchPoly;
if( poly.IsHole() )
{
if( !backFace )
return;
}
else
if( backFace )
{
plConst(hsBool) kAllowTwoSided(true);
if( !kAllowTwoSided || !poly.IsTwoSided() )
return;
scratchPoly.Flip(poly);
usePoly = &scratchPoly;
}
if( !SphereVisible(usePoly->GetCenter(), usePoly->GetRadius()) )
return;
usePoly->fClipped.Clear();
usePoly->Validate();
// Make sure we have enough scratch polys. Each node
// can potentially split this poly, so...
ISetupScratch(fNodeList.GetCount());
#if 1
if( IGetRoot() && IGetNode(IGetRoot()->fOuterChild) )
{
IAddPolyRecur(*usePoly, IGetRoot()->fOuterChild);
}
else
#endif
{
fRoot = IAddPolyRecur(*usePoly, fRoot);
}
#ifdef DEBUG_POINTERS
if( IGetRoot() )
IGetRoot()->ISetPointersRecur();
#endif // DEBUG_POINTERS
}
Int16 plCullTree::IAddPolyRecur(const plCullPoly& poly, Int16 iNode)
{
if( poly.fVerts.GetCount() < 3 )
return iNode;
if( iNode < 0 )
return IMakePolySubTree(poly);
hsBool addInner = (IGetNode(iNode)->fInnerChild >= 0)
|| ((iNode > 5) && poly.IsHole());
hsBool addOuter = !poly.IsHole() || (IGetNode(iNode)->fOuterChild >= 0);
plCullPoly* innerPoly = nil;
plCullPoly* outerPoly = nil;
plCullNode::plCullStatus test = IGetNode(iNode)->ISplitPoly(poly, innerPoly, outerPoly);
switch( test )
{
case plCullNode::kClear:
if( addOuter )
{
int child = IAddPolyRecur(poly, IGetNode(iNode)->fOuterChild);
IGetNode(iNode)->fOuterChild = child;
}
break;
case plCullNode::kCulled:
if( addInner )
{
int child = IAddPolyRecur(poly, IGetNode(iNode)->fInnerChild);
IGetNode(iNode)->fInnerChild = child;
}
break;
case plCullNode::kSplit:
hsAssert(innerPoly && outerPoly, "Poly should have been split into inner and outer in SplitPoly");
if( addOuter )
{
int child = IAddPolyRecur(*outerPoly, IGetNode(iNode)->fOuterChild);
IGetNode(iNode)->fOuterChild = child;
}
if( addInner )
{
int child = IAddPolyRecur(*innerPoly, IGetNode(iNode)->fInnerChild);
IGetNode(iNode)->fInnerChild = child;
}
break;
}
return iNode;
}
Int16 plCullTree::IMakePolyNode(const plCullPoly& poly, int i0, int i1) const
{
Int16 retINode = fNodeList.GetCount();
plCullNode* nextNode = fNodeList.Push();
hsVector3 a;
hsVector3 b;
a.Set(&poly.fVerts[i0], &fViewPos);
b.Set(&poly.fVerts[i1], &fViewPos);
hsVector3 n = a % b;
hsScalar d = -n.InnerProduct(fViewPos);
IDEBUG_NORMALIZE(n, d);
nextNode->Init(this, n, d);
return retINode;
}
Int16 plCullTree::IMakeHoleSubTree(const plCullPoly& poly) const
{
if( fCapturePolys )
IVisPoly(poly, true);
int firstNode = fNodeList.GetCount();
Int16 iNode = -1;
int i;
for( i = 0; i < poly.fVerts.GetCount()-1; i++ )
{
if( !poly.fClipped.IsBitSet(i) )
{
Int16 child = IMakePolyNode(poly, i, i+1);
if( iNode >= 0 )
IGetNode(iNode)->fOuterChild = child;
iNode = child;
}
}
if( !poly.fClipped.IsBitSet(i) )
{
Int16 child = IMakePolyNode(poly, i, 0);
if( iNode >= 0 )
IGetNode(iNode)->fOuterChild = child;
iNode = child;
}
plCullNode* child = fNodeList.Push();
child->Init(this, poly.fNorm, poly.fDist);
if( iNode >= 0 )
IGetNode(iNode)->fOuterChild = fNodeList.GetCount()-1;
return firstNode;
}
Int16 plCullTree::IMakePolySubTree(const plCullPoly& poly) const
{
poly.Validate();
if( poly.IsHole() )
return IMakeHoleSubTree(poly);
if( fCapturePolys )
IVisPoly(poly, false);
int firstNode = fNodeList.GetCount();
Int16 iNode = -1;
int i;
for( i = 0; i < poly.fVerts.GetCount()-1; i++ )
{
if( !poly.fClipped.IsBitSet(i) )
{
Int16 child = IMakePolyNode(poly, i, i+1);
if( iNode >= 0 )
IGetNode(iNode)->fInnerChild = child;
iNode = child;
}
}
if( !poly.fClipped.IsBitSet(i) )
{
Int16 child = IMakePolyNode(poly, i, 0);
if( iNode >= 0 )
IGetNode(iNode)->fInnerChild = child;
iNode = child;
}
plCullNode* child = fNodeList.Push();
child->Init(this, poly.fNorm, poly.fDist);
child->fIsFace = true;
if( iNode >= 0 )
IGetNode(iNode)->fInnerChild = fNodeList.GetCount()-1;
return firstNode;
}
///////////////////////////////////////////////////////////////////
// Begin visualization section of the program
///////////////////////////////////////////////////////////////////
void plCullTree::IVisPolyShape(const plCullPoly& poly, hsBool dark) const
{
int i;
int vertStart = fVisVerts.GetCount();
hsColorRGBA color;
if( dark )
color.Set(0.2f, 0.2f, 0.2f, 1.f);
else
color.Set(1.f, 1.f, 1.f, 1.f);
hsVector3 norm = dark ? -poly.fNorm : poly.fNorm;
for( i = 0; i < poly.fVerts.GetCount(); i++ )
{
fVisVerts.Append(poly.fVerts[i]);
fVisNorms.Append(poly.fNorm);
fVisColors.Append(color);
}
if( !dark )
{
for( i = 2; i < poly.fVerts.GetCount(); i++ )
{
fVisTris.Append(vertStart);
fVisTris.Append(vertStart + i-1);
fVisTris.Append(vertStart + i);
}
}
else
{
for( i = 2; i < poly.fVerts.GetCount(); i++ )
{
fVisTris.Append(vertStart);
fVisTris.Append(vertStart + i);
fVisTris.Append(vertStart + i-1);
}
}
}
void plCullTree::IVisPolyEdge(const hsPoint3& p0, const hsPoint3& p1, hsBool dark) const
{
hsColorRGBA color;
if( dark )
color.Set(0.2f, 0.2f, 0.2f, 1.f);
else
color.Set(1.f, 1.f, 1.f, 1.f);
int vertStart = fVisVerts.GetCount();
hsVector3 dir0(&p0, &fViewPos);
hsFastMath::NormalizeAppr(dir0);
dir0 *= fVisYon;
hsVector3 dir1(&p1, &fViewPos);
hsFastMath::NormalizeAppr(dir1);
dir1 *= fVisYon;
hsPoint3 p3 = fViewPos;
p3 += dir0;
hsPoint3 p2 = fViewPos;
p2 += dir1;
hsVector3 norm = hsVector3(&p0, &fViewPos) % hsVector3(&p1, &fViewPos);
hsFastMath::NormalizeAppr(norm);
fVisVerts.Append(p0);
fVisNorms.Append(norm);
fVisColors.Append(color);
fVisVerts.Append(p1);
fVisNorms.Append(norm);
fVisColors.Append(color);
fVisVerts.Append(p2);
fVisNorms.Append(norm);
fVisColors.Append(color);
fVisVerts.Append(p3);
fVisNorms.Append(norm);
fVisColors.Append(color);
fVisTris.Append(vertStart + 0);
fVisTris.Append(vertStart + 2);
fVisTris.Append(vertStart + 1);
fVisTris.Append(vertStart + 0);
fVisTris.Append(vertStart + 3);
fVisTris.Append(vertStart + 2);
}
void plCullTree::IVisPoly(const plCullPoly& poly, hsBool dark) const
{
IVisPolyShape(poly, dark);
int i;
for( i = 0; i < poly.fVerts.GetCount()-1; i++ )
{
if( !poly.fClipped.IsBitSet(i) )
IVisPolyEdge(poly.fVerts[i], poly.fVerts[i+1], dark);
}
if( !poly.fClipped.IsBitSet(i) )
IVisPolyEdge(poly.fVerts[i], poly.fVerts[0], dark);
}
void plCullTree::ReleaseCapture() const
{
fVisVerts.Reset();
fVisNorms.Reset();
fVisColors.Reset();
fVisTris.Reset();
}
///////////////////////////////////////////////////////////////////
// End visualization section of the program
///////////////////////////////////////////////////////////////////
void plCullTree::ISetupScratch(UInt16 nNodes)
{
ScratchPolys().SetCount(nNodes << 1);
ScratchPolys().SetCount(0);
}
void plCullTree::Reset()
{
// Using NodeList as scratch will only work if we use indices,
// because a push invalidates any pointers we've stored away.
fNodeList.SetCount(0);
fRoot = -1;
ScratchPolys().SetCount(0);
}
void plCullTree::InitFrustum(const hsMatrix44& world2NDC)
{
Reset();
fNodeList.SetCount(6);
fNodeList.SetCount(0);
Int16 lastIdx = -1;
plCullNode* node;
hsVector3 norm;
hsScalar dist;
int i;
for( i = 0; i < 2; i++ )
{
norm.Set(world2NDC.fMap[3][0] - world2NDC.fMap[i][0], world2NDC.fMap[3][1] - world2NDC.fMap[i][1], world2NDC.fMap[3][2] - world2NDC.fMap[i][2]);
dist = world2NDC.fMap[3][3] - world2NDC.fMap[i][3];
IDEBUG_NORMALIZE( norm, dist );
node = fNodeList.Push();
node->Init(this, norm, dist);
node->fOuterChild = lastIdx;
lastIdx = fNodeList.GetCount()-1;
norm.Set(world2NDC.fMap[3][0] + world2NDC.fMap[i][0], world2NDC.fMap[3][1] + world2NDC.fMap[i][1], world2NDC.fMap[3][2] + world2NDC.fMap[i][2]);
dist = world2NDC.fMap[3][3] + world2NDC.fMap[i][3];
IDEBUG_NORMALIZE( norm, dist );
node = fNodeList.Push();
node->Init(this, norm, dist);
node->fOuterChild = lastIdx;
lastIdx = fNodeList.GetCount()-1;
}
norm.Set(world2NDC.fMap[3][0] - world2NDC.fMap[2][0], world2NDC.fMap[3][1] - world2NDC.fMap[2][1], world2NDC.fMap[3][2] - world2NDC.fMap[2][2]);
dist = world2NDC.fMap[3][3] - world2NDC.fMap[2][3];
IDEBUG_NORMALIZE( norm, dist );
node = fNodeList.Push();
node->Init(this, norm, dist);
node->fOuterChild = lastIdx;
lastIdx = fNodeList.GetCount()-1;
#ifdef SYMMET
norm.Set(world2NDC.fMap[3][0] + world2NDC.fMap[2][0], world2NDC.fMap[3][1] + world2NDC.fMap[2][1], world2NDC.fMap[3][2] + world2NDC.fMap[2][2]);
dist = world2NDC.fMap[3][3] + world2NDC.fMap[2][3];
#else // SYMMET
norm.Set(world2NDC.fMap[2][0], world2NDC.fMap[2][1], world2NDC.fMap[2][2]);
dist = world2NDC.fMap[2][3];
#endif // SYMMET
IDEBUG_NORMALIZE( norm, dist );
node = fNodeList.Push();
node->Init(this, norm, dist);
node->fOuterChild = lastIdx;
lastIdx = fNodeList.GetCount()-1;
fRoot = fNodeList.GetCount()-1;
#ifdef DEBUG_POINTERS
if( IGetRoot() )
IGetRoot()->ISetPointersRecur();
#endif // DEBUG_POINTERS
}
void plCullTree::SetViewPos(const hsPoint3& p)
{
fViewPos = p;
}
//////////////////////////////////////////////////////////////////////
// Use the tree
//////////////////////////////////////////////////////////////////////
void plCullTree::Harvest(const plSpaceTree* space, hsTArray<Int16>& outList) const
{
outList.SetCount(0);
if (!space->IsEmpty())
IGetRoot()->IHarvest(space, outList);
}
hsBool plCullTree::BoundsVisible(const hsBounds3Ext& bnd) const
{
return plCullNode::kCulled != IGetRoot()->ITestBoundsRecur(bnd);
}
hsBool plCullTree::SphereVisible(const hsPoint3& center, hsScalar rad) const
{
return plCullNode::kCulled != IGetRoot()->ITestSphereRecur(center, rad);
}