/*==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 . 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 "plSpaceTreeMaker.h" #include "plMath/hsRadixSort.h" #include "plDrawable/plSpaceTree.h" #include "hsUtils.h" // for testing, get hsRand() #include "hsTimer.h" #include "plIntersect/plVolumeIsect.h" //#define MF_DO_TIMES enum mfTimeTypes { kMakeTree = 0, kMakeFatTree, kSortList, kHarvest, kMakeSpaceTree, kMakeTreeAll, kHarvestSphere, kHarvestCone, kHarvestCapped, kNumTimes }; #ifdef MF_DO_TIMES double times[kNumTimes]; #define StartTimer(i) { times[(i)] -= hsTimer::GetSeconds(); } #define StopTimer(i) { times[(i)] += hsTimer::GetSeconds(); } #define InitTimers() { for( int i = 0; i < kNumTimes; i++ )times[i] = 0; } #else // MF_DO_TIMES #define StartTimer(i) #define StopTimer(i) #define InitTimers() #endif // MF_DO_TIMES // Create the tree // more temp testing garbage #if 0 plSpaceCullResult mySpaceCullFunction(const hsBounds3Ext& bnd) { const hsPoint3& maxs = bnd.GetMaxs(); const hsPoint3& mins = bnd.GetMins(); if( maxs.fX < 0.25f ) return kSpaceCulled; if( maxs.fY < 0.25f ) return kSpaceCulled; if( maxs.fZ < 0.25f ) return kSpaceCulled; if( mins.fX > 0.75f ) return kSpaceCulled; if( mins.fY > 0.75f ) return kSpaceCulled; if( mins.fZ > 0.75f ) return kSpaceCulled; if( maxs.fX > 0.75f ) return kSpaceSplit; if( maxs.fY > 0.75f ) return kSpaceSplit; if( maxs.fZ > 0.75f ) return kSpaceSplit; if( mins.fX < 0.25f ) return kSpaceSplit; if( mins.fY < 0.25f ) return kSpaceSplit; if( mins.fZ < 0.25f ) return kSpaceSplit; return kSpaceClear; } #endif void plSpaceTreeMaker::ISortList(hsTArray& nodes, const hsVector3& axis) { StartTimer(kSortList); hsRadixSort::Elem* list = fSortScratch; hsRadixSort::Elem* listTrav = list; Int32 n = nodes.GetCount(); while( n-- ) { listTrav->fKey.fFloat = axis.InnerProduct(nodes[n]->fWorldBounds.GetCenter()); listTrav->fBody = (void*)nodes[n]; listTrav->fNext = listTrav+1; listTrav++; } list[nodes.GetCount()-1].fNext = nil; UInt32 sortFlags = 0; hsRadixSort rad; hsRadixSort::Elem* sortedList = rad.Sort(list, sortFlags); listTrav = sortedList; int i; for( i = 0; i < nodes.GetCount(); i++ ) { nodes[i] = (plSpacePrepNode*)(listTrav->fBody); listTrav = listTrav->fNext; } StopTimer(kSortList); return; } void plSpaceTreeMaker::ISplitList(hsTArray& nodes, const hsVector3& axis, hsTArray& lower, hsTArray& upper) { ISortList(nodes, axis); int lowerCount = nodes.GetCount() / 2; int upperCount = nodes.GetCount() - lowerCount; lower.SetCount(lowerCount); upper.SetCount(upperCount); int i; for( i = 0; i < lowerCount; i++ ) lower[i] = nodes[i]; for( i = 0; i < upperCount; i++ ) upper[i] = nodes[i + lowerCount]; } hsBounds3Ext plSpaceTreeMaker::IFindDistToCenterAxis(hsTArray& nodes, hsScalar& length, hsVector3& axis) { hsBounds3Ext bnd; bnd.MakeEmpty(); hsAssert(nodes.GetCount() > 1, "Degenerate case"); int i; for( i = 0; i < nodes.GetCount(); i++ ) { bnd.Union(&nodes[i]->fWorldBounds); } length = 0; for( i = 0; i < nodes.GetCount(); i++ ) { hsVector3 sep; sep.Set(&bnd.GetCenter(), &nodes[i]->fWorldBounds.GetCenter()); hsScalar len = sep.MagnitudeSquared(); if( len > length ) { axis = sep; length = len; } } length = hsSquareRoot(length); if( length > 1.e-3f ) axis /= length; else return IFindSplitAxis(nodes, length, axis); return bnd; } plSpacePrepNode* plSpaceTreeMaker::IMakeFatTreeRecur(hsTArray& nodes) { if( !nodes.GetCount() ) return nil; StartTimer(kMakeFatTree); plSpacePrepNode* subRoot = TRACKED_NEW plSpacePrepNode; fTreeSize++; if( nodes.GetCount() == 1 ) { *subRoot = *nodes[0]; subRoot->fChildren[0] = nil; subRoot->fChildren[1] = nil; StopTimer(kMakeFatTree); return subRoot; } // Find the overall bounds of the list. // Find the maximum length vector from nodes[i] center to list center. // If that length is zero, just use the maximum dimension of overall bounds. hsScalar length; hsVector3 axis; hsBounds3Ext bnd = IFindDistToCenterAxis(nodes, length, axis); hsTArray list0; hsTArray list1; ISplitList(nodes, axis, list0, list1); subRoot->fChildren[0] = IMakeTreeRecur(list0); subRoot->fChildren[1] = IMakeTreeRecur(list1); subRoot->fWorldBounds = bnd; StopTimer(kMakeFatTree); return subRoot; } hsBounds3Ext plSpaceTreeMaker::IFindSplitAxis(hsTArray& nodes, hsScalar& length, hsVector3& axis) { hsBounds3Ext bnd; bnd.MakeEmpty(); int i; for( i = 0; i < nodes.GetCount(); i++ ) { bnd.Union(&nodes[i]->fWorldBounds); } hsScalar maxLen = bnd.GetMaxs()[0] - bnd.GetMins()[0]; int maxAxis = 0; if( bnd.GetMaxs()[1] - bnd.GetMins()[1] > maxLen ) { maxLen = bnd.GetMaxs()[1] - bnd.GetMins()[1]; maxAxis = 1; } if( bnd.GetMaxs()[2] - bnd.GetMins()[2] > maxLen ) { maxLen = bnd.GetMaxs()[2] - bnd.GetMins()[2]; maxAxis = 2; } length = maxLen; switch( maxAxis ) { case 0: axis.Set(1.f, 0, 0); break; case 1: axis.Set(0, 1.f, 0); break; case 2: axis.Set(0, 0, 1.f); break; } return bnd; } void plSpaceTreeMaker::IFindBigList(hsTArray& nodes, hsScalar length, const hsVector3& axis, hsTArray& giants, hsTArray& strimps) { const hsScalar kCutoffFrac = 0.5f; giants.SetCount(0); strimps.SetCount(0); int i; for( i = 0; i < nodes.GetCount(); i++ ) { hsPoint2 depth; nodes[i]->fWorldBounds.TestPlane(axis, depth); if( depth.fY - depth.fX > length * kCutoffFrac ) giants.Append(nodes[i]); else strimps.Append(nodes[i]); } } plSpacePrepNode* plSpaceTreeMaker::INewSubRoot(const hsBounds3Ext& bnd) { plSpacePrepNode* subRoot = TRACKED_NEW plSpacePrepNode; subRoot->fDataIndex = Int16(-1); fTreeSize++; subRoot->fWorldBounds = bnd; return subRoot; } plSpacePrepNode* plSpaceTreeMaker::IMakeTreeRecur(hsTArray& nodes) { if( !nodes.GetCount() ) return nil; if( nodes.GetCount() == 1 ) { return IMakeFatTreeRecur(nodes); } StartTimer(kMakeTree); // Find the maximum bounds dimension hsScalar length; hsVector3 axis; hsBounds3Ext bnd = IFindSplitAxis(nodes, length, axis); // Find everyone with bounds over half that size in the same dimension as list0. hsTArray list0; hsTArray list1; IFindBigList(nodes, length, axis, list0, list1); plSpacePrepNode* subRoot = nil; // If list0 not empty, put them in first child, recur on remainder, if( list0.GetCount() && list1.GetCount() ) { subRoot = INewSubRoot(bnd); subRoot->fChildren[0] = IMakeFatTreeRecur(list0); // too big subRoot->fChildren[1] = IMakeTreeRecur(list1); // remainder } else if( list0.GetCount() ) { subRoot = IMakeFatTreeRecur(list0); } // Else sort along axis by bounds center, recur separately on lower and upper halves. else { ISplitList(nodes, axis, list0, list1); subRoot = INewSubRoot(bnd); subRoot->fChildren[0] = IMakeTreeRecur(list0); subRoot->fChildren[1] = IMakeTreeRecur(list1); } StopTimer(kMakeTree); return subRoot; } void plSpaceTreeMaker::IMakeTree() { fSortScratch = TRACKED_NEW hsRadixSort::Elem[fLeaves.GetCount()]; fPrepTree = IMakeTreeRecur(fLeaves); delete [] fSortScratch; fSortScratch = nil; } void plSpaceTreeMaker::Reset() { fLeaves.Reset(); fPrepTree = nil; fTreeSize = 0; fSortScratch = nil; } void plSpaceTreeMaker::IDeleteTreeRecur(plSpacePrepNode* node) { if( node ) { IDeleteTreeRecur(node->fChildren[0]); IDeleteTreeRecur(node->fChildren[1]); delete node; } } void plSpaceTreeMaker::Cleanup() { IDeleteTreeRecur(fPrepTree); fPrepTree = nil; int i; for( i = 0; i < fLeaves.GetCount(); i++ ) delete fLeaves[i]; fLeaves.Reset(); fDisabled.Reset(); } Int32 plSpaceTreeMaker::AddLeaf(const hsBounds3Ext& worldBnd, hsBool disable) { plSpacePrepNode* leaf = TRACKED_NEW plSpacePrepNode; fLeaves.Append(leaf); leaf->fDataIndex = fLeaves.GetCount()-1; leaf->fChildren[0] = nil; leaf->fChildren[1] = nil; leaf->fWorldBounds = worldBnd; if( leaf->fWorldBounds.GetType() != kBoundsNormal ) { static const hsPoint3 zero(0.f, 0.f, 0.f); leaf->fWorldBounds.Reset(&zero); } fDisabled.SetBit(leaf->fDataIndex, disable); return leaf->fDataIndex; } //#define MF_DO_RAND #define MF_DO_3D #ifdef MF_DO_RAND #define MF_SETPOINT(pt,a,b,c) pt.Set(hsRand()/32767.f, hsRand()/32767.f, hsRand()/32767.f) #else // MF_DO_RAND #define MF_SETPOINT(pt,a,b,c) pt.Set(a,b,c) #endif // MF_DO_RAND void plSpaceTreeMaker::TestTree() { Reset(); const int kTestSize = 10; int i; for( i = 0; i < kTestSize; i++ ) { int j; for( j = 0; j < kTestSize; j++ ) { int k; #ifdef MF_DO_3D for( k = 0; k < kTestSize; k++ ) #else // MF_DO_3D k = 0; #endif // MF_DO_3D { hsBounds3Ext bnd; hsPoint3 pt; MF_SETPOINT(pt, float(i-1)/kTestSize, float(j-1)/kTestSize, float(k-1)/kTestSize); bnd.Reset(&pt); MF_SETPOINT(pt, float(i)/kTestSize, float(j)/kTestSize, float(k)/kTestSize); bnd.Union(&pt); AddLeaf(bnd); } } } hsBitVector list; plSpaceTree* tree = MakeTree(); #if 0 // HACK TESTING MOVE TO VOLUMECULL hsMatrix44 liX; hsMatrix44 invLiX; liX.MakeTranslateMat(&hsVector3(0.5f, 0.5f, 0)); liX.GetInverse(&invLiX); plSphereIsect sphere; sphere.SetRadius(0.2); sphere.SetTransform(liX, invLiX); tree->SetViewPos(*hsPoint3().Set(0,0,0)); plConeIsect cone; cone.SetAngle(hsScalarPI*0.25f); cone.SetTransform(liX, invLiX); StartTimer(kHarvestCone); list.Clear(); tree->HarvestLeaves(&cone, list); StopTimer(kHarvestCone); plConeIsect capped; capped.SetAngle(hsScalarPI*0.25f); capped.SetLength(0.5f); capped.SetTransform(liX, invLiX); StartTimer(kHarvestCapped); list.Clear(); tree->HarvestLeaves(&capped, list); StopTimer(kHarvestCapped); StartTimer(kHarvestSphere); list.Clear(); tree->HarvestLeaves(&sphere, list); StopTimer(kHarvestSphere); #endif // HACK TESTING MOVE TO VOLUMECULL delete tree; } plSpaceTree* plSpaceTreeMaker::MakeTree() { // DEBUG FISH InitTimers(); // DEBUG FISH StartTimer(kMakeTreeAll); if( !fLeaves.GetCount() ) return IMakeEmptyTree(); if( fLeaves.GetCount() < 2 ) return IMakeDegenerateTree(); IMakeTree(); plSpaceTree* retVal = IMakeSpaceTree(); Cleanup(); StopTimer(kMakeTreeAll); return retVal; } plSpaceTree* plSpaceTreeMaker::IMakeEmptyTree() { plSpaceTree* tree = TRACKED_NEW plSpaceTree; tree->fTree.SetCount(1); tree->fTree[0].fWorldBounds.Reset(&hsPoint3(0,0,0)); tree->fTree[0].fFlags = plSpaceTreeNode::kEmpty; tree->fRoot = 0; tree->fNumLeaves = 0; Cleanup(); return tree; } plSpaceTree* plSpaceTreeMaker::IMakeDegenerateTree() { plSpaceTree* tree = TRACKED_NEW plSpaceTree; tree->fTree.Push(); tree->fRoot = 0; tree->fTree[0].fWorldBounds = fLeaves[0]->fWorldBounds; tree->fTree[0].fFlags = plSpaceTreeNode::kIsLeaf; tree->fTree[0].fLeafIndex = 0; tree->fTree[0].fParent = plSpaceTree::kRootParent; tree->fNumLeaves = 1; if( fDisabled.IsBitSet(0) ) tree->SetLeafFlag(0, plSpaceTreeNode::kDisabled, true); Cleanup(); return tree; } int plSpaceTreeMaker::ITreeDepth(plSpacePrepNode* subRoot) { if( !subRoot ) return 0; int dep0 = ITreeDepth(subRoot->fChildren[0]); int dep1 = ITreeDepth(subRoot->fChildren[1]); int dep = hsMaximum(dep0, dep1); return dep+1; } plSpaceTree* plSpaceTreeMaker::IMakeSpaceTree() { StartTimer(kMakeSpaceTree); plSpaceTree* tree = TRACKED_NEW plSpaceTree; plSpacePrepNode* head = fPrepTree; tree->fTree.SetCount(fLeaves.GetCount()); IGatherLeavesRecur(head, tree); int level = ITreeDepth(head); while( level > 0 ) IMakeSpaceTreeRecur(head, tree, --level, 0); tree->fRoot = tree->fTree.GetCount()-1; tree->fTree[tree->fRoot].fParent = plSpaceTree::kRootParent; tree->fNumLeaves = fLeaves.GetCount(); int i; for( i = 0; i < fLeaves.GetCount(); i++ ) { if( fDisabled.IsBitSet(i) ) tree->SetLeafFlag(i, plSpaceTreeNode::kDisabled, true); } StopTimer(kMakeSpaceTree); return tree; } // The following goofy cache-friendly tree set up slows down the tree build by 10%, but speeds up the runtime by 9%. // Sounds fair. #if 0 // Leaves first Int16 plSpaceTreeMaker::IMakeSpaceTreeRecur(plSpacePrepNode* sub, plSpaceTree* tree, const int targetLevel, int currLevel) { if( currLevel == targetLevel ) { Int16 nodeIdx = tree->fTree.GetCount(); tree->fTree.Push(); tree->fTree[nodeIdx].fWorldBounds = sub->fWorldBounds; sub->fIndex = nodeIdx; if( !sub->fChildren[0] ) { hsAssert(!sub->fChildren[1], "Unsupported unbalance of tree"); tree->fTree[nodeIdx].fFlags = plSpaceTreeNode::kIsLeaf; tree->fTree[nodeIdx].fLeafIndex = sub->fDataIndex; return nodeIdx; } hsAssert(sub->fChildren[1], "Unsupported unbalance of tree"); tree->fTree[nodeIdx].fFlags = plSpaceTreeNode::kNone; return nodeIdx; } Int16 nodeIdx = sub->fIndex; if( !sub->fChildren[0] ) { hsAssert(!sub->fChildren[1] , "Unsupported unbalance of tree"); return nodeIdx; } hsAssert(sub->fChildren[1] , "Unsupported unbalance of tree"); tree->fTree[nodeIdx].fChildren[0] = IMakeSpaceTreeRecur(sub->fChildren[0], tree, targetLevel, currLevel+1); tree->fTree[tree->fTree[nodeIdx].fChildren[0]].fParent = nodeIdx; tree->fTree[nodeIdx].fChildren[1] = IMakeSpaceTreeRecur(sub->fChildren[1], tree, targetLevel, currLevel+1); tree->fTree[tree->fTree[nodeIdx].fChildren[1]].fParent = nodeIdx; return nodeIdx; } #else // Leaves first void plSpaceTreeMaker::IGatherLeavesRecur(plSpacePrepNode* sub, plSpaceTree* tree) { // if it's a leaf, stuff it in the right slot, else recur if( !sub->fChildren[0] ) { hsAssert(!sub->fChildren[1], "Unsupported unbalance of tree"); plSpaceTreeNode& leaf = tree->fTree[sub->fDataIndex]; Int16 nodeIdx = sub->fDataIndex; leaf.fWorldBounds = sub->fWorldBounds; sub->fIndex = nodeIdx; leaf.fFlags = plSpaceTreeNode::kIsLeaf; leaf.fLeafIndex = nodeIdx; return; } hsAssert(sub->fChildren[1], "Unsupported unbalance of tree"); IGatherLeavesRecur(sub->fChildren[0], tree); IGatherLeavesRecur(sub->fChildren[1], tree); } void plSpaceTreeMaker::IMakeSpaceTreeRecur(plSpacePrepNode* sub, plSpaceTree* tree, const int targetLevel, int currLevel) { // If it's a leaf, we've already done it. if( !sub->fChildren[0] ) { hsAssert(!sub->fChildren[1], "Unsupported unbalance of tree"); return; } hsAssert(sub->fChildren[0] && sub->fChildren[1], "Shouldn't get this deep, already got the leaves"); if( currLevel == targetLevel ) { Int16 nodeIdx = tree->fTree.GetCount(); tree->fTree.Push(); tree->fTree[nodeIdx].fWorldBounds = sub->fWorldBounds; sub->fIndex = nodeIdx; tree->fTree[nodeIdx].fFlags = plSpaceTreeNode::kNone; tree->fTree[nodeIdx].fChildren[0] = sub->fChildren[0]->fIndex; tree->fTree[sub->fChildren[0]->fIndex].fParent = nodeIdx; tree->fTree[nodeIdx].fChildren[1] = sub->fChildren[1]->fIndex; tree->fTree[sub->fChildren[1]->fIndex].fParent = nodeIdx; return; } IMakeSpaceTreeRecur(sub->fChildren[0], tree, targetLevel, currLevel+1); IMakeSpaceTreeRecur(sub->fChildren[1], tree, targetLevel, currLevel+1); } #endif // Leaves first