client/SphereLib/spherepack.cpp

879 lines
20 KiB
C++

/* Copyright (C) John W. Ratcliff, 2001.
* All rights reserved worldwide.
*
* This software is provided "as is" without express or implied
* warranties. You may freely copy and compile this source into
* applications you distribute provided that the copyright text
* below is included in the resulting source code, for example:
* "Portions Copyright (C) John W. Ratcliff, 2001"
*/
#include "Stdafx.h"
#include "spherepack.h"
#if DEMO
int PrintText(int x, int y, int color, char* output, ...);
int DrawLine(int x1, int y1, int x2, int y2, int color);
int DrawCircle(int locx, int locy, int radius, int color);
#endif
SpherePackFactory::SpherePackFactory(int maxspheres, float rootsize, float leafsize, float gravy)
{
NANOBEGIN
maxspheres *= 4; // include room for both trees, the root node and leaf node tree, and the superspheres
mMaxRootSize = rootsize;
mMaxLeafSize = leafsize;
mSuperSphereGravy = gravy;
mIntegrate = new SpherePackFifo(maxspheres);
mRecompute = new SpherePackFifo(maxspheres);
mSpheres.Set(maxspheres); // init pool to hold all possible SpherePack instances.
Vector3d p(0,0,0);
mRoot = mSpheres.GetFreeLink(); // initially empty
mRoot->Init(this,p,6553600,0, false);
mRoot->SetSpherePackFlag(SpherePackFlag(SPF_SUPERSPHERE | SPF_ROOTNODE | SPF_ROOT_TREE));
#if DEMO
mRoot->SetColor(0x00FFFFFF);
#endif
mLeaf = mSpheres.GetFreeLink();; // initially empty
mLeaf->Init(this,p,1638400,0,false);
mLeaf->SetSpherePackFlag(SpherePackFlag(SPF_SUPERSPHERE | SPF_ROOTNODE | SPF_LEAF_TREE));
#if DEMO
mLeaf->SetColor(0x00FFFFFF);
mColorCount = 0;
mColors[0] = 0x00FF0000;
mColors[1] = 0x0000FF00;
mColors[2] = 0x000000FF;
mColors[3] = 0x00FFFF00;
mColors[4] = 0x00FF00FF;
mColors[5] = 0x0000FFFF;
mColors[6] = 0x00FF8080;
mColors[7] = 0x0000FF80;
mColors[8] = 0x000080FF;
mColors[9] = 0x00FFFF80;
mColors[10] = 0x00FF80FF;
mColors[11] = 0x0080FFFF;
#endif
NANOEND
}
SpherePackFactory::~SpherePackFactory(void)
{
delete mIntegrate; // free up integration fifo
delete mRecompute; // free up recomputation fifo.
}
void SpherePackFactory::Process(void)
{
{
// First recompute anybody that needs to be recomputed!!
// When leaf node spheres exit their parent sphere, then the parent sphere needs to be rebalanced. In fact,it may now be empty and
// need to be removed.
// This is the location where (n) number of spheres in the recomputation FIFO are allowed to be rebalanced in the tree.
int maxrecompute = mRecompute->GetCount();
for (int i = 0; i < maxrecompute; ++i)
{
SpherePack * pack = mRecompute->Pop();
if (!pack) break;
pack->SetFifo1(0); // no longer on the fifo!!
bool kill = pack->Recompute(mSuperSphereGravy);
if (kill) Remove(pack);
}
}
{
// Now, process the integration step.
int maxintegrate = mIntegrate->GetCount();
for (int i = 0; i < maxintegrate; ++i)
{
SpherePack * pack = mIntegrate->Pop();
if (!pack)
break;
pack->SetFifo2(0);
if (pack->HasSpherePackFlag(SPF_ROOT_TREE))
Integrate(pack,mRoot,mMaxRootSize); // integrate this one single dude against the root node.
else
Integrate(pack,mLeaf,mMaxLeafSize); // integrate this one single dude against the root node.
}
}
}
SpherePack * SpherePackFactory::AddSphere_(const Vector3d &pos,
float radius,
void *userdata,
bool isSphere,
int flags)
{
SpherePack *pack = mSpheres.GetFreeLink();
assert(pack);
if (pack)
{
if (flags & SPF_ROOT_TREE)
{
pack->Init(this,pos,radius,userdata, isSphere);
pack->SetSpherePackFlag(SPF_ROOT_TREE); // member of the leaf node tree!
AddIntegrate(pack); // add to integration list.
}
else
{
pack->Init(this,pos,radius,userdata, isSphere);
pack->SetSpherePackFlag(SPF_LEAF_TREE); // member of the leaf node tree!
AddIntegrate(pack); // add to integration list.
}
}
return pack;
}
void SpherePackFactory::AddIntegrate(SpherePack *pack)
{
if (pack->HasSpherePackFlag(SPF_ROOT_TREE))
mRoot->AddChild(pack);
else
mLeaf->AddChild(pack);
pack->SetSpherePackFlag(SPF_INTEGRATE); // still needs to be integrated!
SpherePack **fifo = mIntegrate->Push(pack); // add it to the integration stack.
pack->SetFifo2(fifo);
}
void SpherePackFactory::AddRecompute(SpherePack *recompute)
{
if (!recompute->HasSpherePackFlag(SPF_RECOMPUTE))
{
if (recompute->GetChildCount())
{
recompute->SetSpherePackFlag(SPF_RECOMPUTE); // needs to be recalculated!
SpherePack **fifo = mRecompute->Push(recompute);
recompute->SetFifo1(fifo);
}
else
{
Remove(recompute);
}
}
}
void SpherePackFactory::Render(void)
{
#if DEMO
mRoot->Render(mRoot->GetColor());
mLeaf->Render(mLeaf->GetColor());
#endif
}
void SpherePack::Render(unsigned int /*color*/)
{
#if DEMO
if (!HasSpherePackFlag(SPF_ROOTNODE))
{
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
color = mColor;
}
else
{
if (mParent->HasSpherePackFlag(SPF_ROOTNODE)) color = 0x00FFFFFF;
}
#if DEMO
DrawCircle(int(mCenter.x), int(mCenter.y),int(GetRadius()),color);
#endif
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
if (HasSpherePackFlag(SPF_LEAF_TREE))
{
#if DEMO
DrawCircle(int(mCenter.x), int(mCenter.y),int(GetRadius()),color);
#endif
#ifdef SPHERELIB_STRICT
if (!sphere->IS_SPHERE)
puts("SpherePack::Render");
#endif
SpherePack *link = (SpherePack *) GetUserData();
link = link->GetParent();
if (link && !link->HasSpherePackFlag(SPF_ROOTNODE))
{
DrawLine(int(mCenter.x), int(mCenter.y),
int(link->mCenter.x), int(link->mCenter.y),
link->GetColor());
}
}
else
{
#if DEMO
DrawCircle(int(mCenter.x), int(mCenter.y),int(GetRadius())+3,color);
#endif
}
}
}
if (mChildren)
{
SpherePack *pack = mChildren;
while (pack)
{
pack->Render(color);
pack = pack->_GetNextSibling();
}
}
#endif
}
bool SpherePack::Recompute(float gravy)
{
if (!mChildren) return true; // kill it!
if (HasSpherePackFlag(SPF_ROOTNODE)) return false; // don't recompute root nodes!
#if 1
// recompute bounding sphere!
Vector3d total(0,0,0);
int count=0;
SpherePack *pack = mChildren;
while (pack)
{
total+=pack->mCenter;
count++;
pack = pack->_GetNextSibling();
}
if (count)
{
float recip = 1.0f / float(count);
total*=recip;
Vector3d oldpos = mCenter;
#ifdef __STATIC_RANGE__
assert(total.IsInStaticRange());
#endif
mCenter = total; // new origin!
float maxradius = 0;
pack = mChildren;
while (pack)
{
float dist = DistanceSquared(pack);
float radius = sqrtf(dist) + pack->GetRadius();
if (radius > maxradius)
{
maxradius = radius;
if ((maxradius+gravy) >= GetRadius())
{
#ifdef __STATIC_RANGE__
assert(oldpos.IsInStaticRange());
#endif
mCenter = oldpos;
ClearSpherePackFlag(SPF_RECOMPUTE);
return false;
}
}
pack = pack->_GetNextSibling();
}
maxradius+=gravy;
SetRadius(maxradius);
// now all children have to recompute binding distance!!
pack = mChildren;
while (pack)
{
pack->ComputeBindingDistance(this);
pack = pack->_GetNextSibling();
}
}
#endif
ClearSpherePackFlag(SPF_RECOMPUTE);
return false;
}
void SpherePack::LostChild(SpherePack *t)
{
assert(mChildCount);
assert(mChildren);
#ifdef _DEBUG // debug validation code.
SpherePack *pack = mChildren;
bool found = false;
while (pack)
{
if (pack == t)
{
assert(!found);
found = true;
}
pack = pack->_GetNextSibling();
}
assert(found);
#endif
// first patch old linked list.. his previous now points to his next
SpherePack *prev = t->_GetPrevSibling();
if (prev)
{
SpherePack *next = t->_GetNextSibling();
prev->SetNextSibling(next); // my previous now points to my next
if (next) next->SetPrevSibling(prev);
// list is patched!
}
else
{
SpherePack *next = t->_GetNextSibling();
mChildren = next;
if (mChildren) mChildren->SetPrevSibling(0);
}
mChildCount--;
if (!mChildCount && HasSpherePackFlag(SPF_SUPERSPHERE))
{
mFactory->Remove(this);
}
}
void SpherePackFactory::Remove(SpherePack*pack)
{
if (pack->HasSpherePackFlag(SPF_ROOTNODE)) return; // CAN NEVER REMOVE THE ROOT NODE EVER!!!
if (pack->HasSpherePackFlag(SPF_SUPERSPHERE) && pack->HasSpherePackFlag(SPF_LEAF_TREE))
{
#ifdef SPHERELIB_STRICT
if (!pack->IS_SPHERE)
puts("SpherePackFactory::Remove");
#endif
SpherePack *link = (SpherePack *) pack->GetUserData();
Remove(link);
}
pack->Unlink();
mSpheres.Release(pack);
}
#if DEMO
unsigned int SpherePackFactory::GetColor(void)
{
unsigned int ret = mColors[mColorCount];
mColorCount++;
if (mColorCount == MAXCOLORS) mColorCount = 0;
return ret;
}
#endif
void SpherePackFactory::Integrate(SpherePack *pack,
SpherePack *supersphere,
float node_size)
{
// ok..time to integrate this sphere with the tree
// first find which supersphere we are closest to the center of
SpherePack *search = supersphere->GetChildren();
SpherePack *nearest1 = 0; // nearest supersphere we are completely
float neardist1 = 1e38f; // enclosed within
SpherePack *nearest2 = 0; // supersphere we must grow the least to
float neardist2 = 1e38f; // add ourselves to.
//int scount = 1;
while (search)
{
if (search->HasSpherePackFlag(SPF_SUPERSPHERE) && !search->HasSpherePackFlag(SPF_ROOTNODE) && search->GetChildCount())
{
float dist = pack->DistanceSquared(search);
if (nearest1)
{
if (dist < neardist1)
{
float d = sqrtf(dist)+pack->GetRadius();
if (d <= search->GetRadius())
{
neardist1 = dist;
nearest1 = search;
}
}
}
else
{
float d = (sqrtf(dist) + pack->GetRadius())-search->GetRadius();
if (d < neardist2)
{
if (d < 0)
{
neardist1 = dist;
nearest1 = search;
}
else
{
neardist2 = d;
nearest2 = search;
}
}
}
}
search = search->_GetNextSibling();
}
// ok...now..on exit let's see what we got.
if (nearest1)
{
// if we are inside an existing supersphere, we are all good!
// we need to detach item from wherever it is, and then add it to
// this supersphere as a child.
pack->Unlink();
nearest1->AddChild(pack);
pack->ComputeBindingDistance(nearest1);
nearest1->Recompute(mSuperSphereGravy);
if (nearest1->HasSpherePackFlag(SPF_LEAF_TREE))
{
#ifdef SPHERELIB_STRICT
if (!nearest1->IS_SPHERE)
puts("SpherePackFactory::Integrate1");
#endif
SpherePack *link = (SpherePack *) nearest1->GetUserData();
link->NewPosRadius(nearest1->GetPos(), nearest1->GetRadius());
}
}
else
{
bool newsphere = true;
if (nearest2)
{
float newsize = neardist2 + nearest2->GetRadius() + mSuperSphereGravy;
if (newsize <= node_size)
{
pack->Unlink();
nearest2->SetRadius(newsize);
nearest2->AddChild(pack);
nearest2->Recompute(mSuperSphereGravy);
pack->ComputeBindingDistance(nearest2);
if (nearest2->HasSpherePackFlag(SPF_LEAF_TREE))
{
#ifdef SPHERELIB_STRICT
if (!nearest2->IS_SPHERE)
puts("SpherePackFactory::Integrate2");
#endif
SpherePack *link = (SpherePack *) nearest2->GetUserData();
link->NewPosRadius(nearest2->GetPos(), nearest2->GetRadius());
}
newsphere = false;
}
}
if (newsphere)
{
assert(supersphere->HasSpherePackFlag(SPF_ROOTNODE));
// we are going to create a new superesphere around this guy!
pack->Unlink();
SpherePack *parent = mSpheres.GetFreeLink();
assert(parent);
parent->Init(this, pack->GetPos(), pack->GetRadius()+mSuperSphereGravy, 0, false);
if (supersphere->HasSpherePackFlag(SPF_ROOT_TREE))
parent->SetSpherePackFlag(SPF_ROOT_TREE);
else
parent->SetSpherePackFlag(SPF_LEAF_TREE);
parent->SetSpherePackFlag(SPF_SUPERSPHERE);
#if DEMO
parent->SetColor(GetColor());
#endif
parent->AddChild(pack);
supersphere->AddChild(parent);
parent->Recompute(mSuperSphereGravy);
pack->ComputeBindingDistance(parent);
if (parent->HasSpherePackFlag(SPF_LEAF_TREE))
{
// need to create parent association!
SpherePack *link = AddSphere_(parent->GetPos(), parent->GetRadius(), parent, true, SPF_ROOT_TREE);
parent->SetUserData(link, true); // hook him up!!
}
}
}
pack->ClearSpherePackFlag(SPF_INTEGRATE); // we've been integrated!
}
void SpherePackFactory::FrustumTest(const Frustum &f,SpherePackCallback *callback)
{
// test case here, just traverse children.
mCallback = callback;
mRoot->VisibilityTest(f,this,VS_PARTIAL);
}
void SpherePack::VisibilityTest(const Frustum &f,SpherePackCallback *callback,ViewState state)
{
if (state == VS_PARTIAL)
{
state = f.ViewVolumeTest(mCenter, GetRadius());
#if DEMO
if (state != VS_OUTSIDE)
{
DrawCircle(int(mCenter.x), int(mCenter.y), int(GetRadius()), 0x404040);
}
#endif
}
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
if (state == VS_OUTSIDE)
{
if (HasSpherePackFlag(SPF_HIDDEN)) return; // no state change
ClearSpherePackFlag(SpherePackFlag(SPF_INSIDE | SPF_PARTIAL));
SetSpherePackFlag(SPF_HIDDEN);
}
else
{
if (state == VS_INSIDE)
{
if (HasSpherePackFlag(SPF_INSIDE)) return; // no state change
ClearSpherePackFlag(SpherePackFlag(SPF_PARTIAL | SPF_HIDDEN));
SetSpherePackFlag(SPF_INSIDE);
}
else
{
ClearSpherePackFlag(SpherePackFlag(SPF_HIDDEN | SPF_INSIDE));
SetSpherePackFlag(SPF_PARTIAL);
}
}
SpherePack *pack = mChildren;
while (pack)
{
pack->VisibilityTest(f,callback,state);
pack = pack->_GetNextSibling();
}
}
else
{
switch (state)
{
case VS_INSIDE:
if (!HasSpherePackFlag(SPF_INSIDE))
{
ClearSpherePackFlag(SpherePackFlag(SPF_HIDDEN | SPF_PARTIAL));
SetSpherePackFlag(SPF_INSIDE);
callback->VisibilityCallback(f,this,state);
}
break;
case VS_OUTSIDE:
if (!HasSpherePackFlag(SPF_HIDDEN))
{
ClearSpherePackFlag(SpherePackFlag(SPF_INSIDE | SPF_PARTIAL));
SetSpherePackFlag(SPF_HIDDEN);
callback->VisibilityCallback(f,this,state);
}
break;
case VS_PARTIAL:
//if (!HasSpherePackFlag(SPF_PARTIAL))
{
ClearSpherePackFlag(SpherePackFlag(SPF_INSIDE | SPF_HIDDEN));
SetSpherePackFlag(SPF_PARTIAL);
callback->VisibilityCallback(f,this,state);
}
break;
}
}
}
void SpherePackFactory::RayTrace(const Vector3d &p1,
const Vector3d &p2,
SpherePackCallback *callback)
{
// test case here, just traverse children.
Vector3d dir = p2;
float dist = dir.Normalize();
mCallback = callback;
mRoot->RayTrace(p1,dir,dist,this);
}
#include "../EterBase/Debug.h"
void SpherePackFactory::RangeTest(const Vector3d &center,float radius,SpherePackCallback *callback)
{
#ifdef __STATIC_RANGE__
if (!center.IsInStaticRange())
{
TraceError("SpherePackFactory::RangeTest - RANGE ERROR %f, %f, %f",
center.x, center.y, center.z);
assert("SpherePackFactory::RangeTest - RANGE ERROR");
return;
}
#endif
mCallback = callback;
mRoot->RangeTest(center,radius,this,VS_PARTIAL);
}
void SpherePackFactory::PointTest2d(const Vector3d &center, SpherePackCallback *callback)
{
#ifdef __STATIC_RANGE__
if (!center.IsInStaticRange())
{
TraceError("SpherePackFactory::RangeTest2d - RANGE ERROR %f, %f, %f",
center.x, center.y, center.z);
assert("SpherePackFactory::RangeTest2d - RANGE ERROR");
return;
}
#endif
mCallback = callback;
#ifdef SPHERELIB_STRICT
mRoot->PointTest2d(center, this,VS_PARTIAL);
extern bool MAPOUTDOOR_GET_HEIGHT_TRACE;
if (MAPOUTDOOR_GET_HEIGHT_TRACE)
puts("================================================");
#else
mRoot->PointTest2d(center, this,VS_PARTIAL);
#endif
}
void SpherePack::RangeTest(const Vector3d &p,
float distance,
SpherePackCallback *callback,
ViewState state)
{
if (state == VS_PARTIAL)
{
float d = p.Distance(mCenter);
if ((d-distance) > GetRadius()) return;;
if ((GetRadius()+d) < distance) state = VS_INSIDE;
}
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
#if DEMO
if (state == VS_PARTIAL)
{
DrawCircle(int(mCenter.x), int(mCenter.y), int(GetRadius()), 0x404040);
}
#endif
SpherePack *pack = mChildren;
while (pack)
{
pack->RangeTest(p,distance,callback,state);
pack = pack->_GetNextSibling();
}
}
else
{
callback->RangeTestCallback(p,distance,this,state);
}
}
void SpherePack::PointTest2d(const Vector3d &p,
SpherePackCallback *callback,
ViewState state)
{
if (state == VS_PARTIAL)
{
float dx=p.x-mCenter.x;
float dy=p.y-mCenter.y;
float distSquare = (dx*dx)+(dy*dy);
if (distSquare > GetRadius2()) return;;
if (GetRadius2() < -distSquare) state = VS_INSIDE;
}
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
#if DEMO
if (state == VS_PARTIAL)
{
DrawCircle(int(mCenter.x), int(mCenter.y), int(GetRadius()), 0x404040);
}
#endif
SpherePack *pack = mChildren;
while (pack)
{
pack->PointTest2d(p, callback, state);
pack = pack->_GetNextSibling();
}
}
else
{
#ifdef SPHERELIB_STRICT
extern bool MAPOUTDOOR_GET_HEIGHT_TRACE;
if (MAPOUTDOOR_GET_HEIGHT_TRACE)
{
float dx=p.x-mCenter.x;
float dy=p.y-mCenter.y;
float distSquare = (dx*dx)+(dy*dy);
printf("--- (%f, %f) dist %f radius %f isSphere %d\n", mCenter.x, mCenter.y, distSquare, GetRadius(), IS_SPHERE);
}
#endif
callback->PointTest2dCallback(p, this, state);
}
}
void SpherePackFactory::RangeTestCallback(const Vector3d &p,float distance,SpherePack *sphere,ViewState state)
{
#ifdef SPHERELIB_STRICT
if (!sphere->IS_SPHERE)
puts("SpherePackFactory::RangeTestCallback");
#endif
SpherePack *link = (SpherePack *) sphere->GetUserData();
if (link) link->RangeTest(p,distance,mCallback,state);
};
void SpherePackFactory::PointTest2dCallback(const Vector3d &p, SpherePack *sphere,ViewState state)
{
#ifdef SPHERELIB_STRICT
if (!sphere->IS_SPHERE)
puts("SpherePackFactory::PointTest2dCallback");
#endif
SpherePack *link = (SpherePack *) sphere->GetUserData();
if (link) link->PointTest2d(p, mCallback,state);
};
void SpherePack::RayTrace(const Vector3d &p1,
const Vector3d &dir,
float distance,
SpherePackCallback *callback)
{
bool hit = false;
if (HasSpherePackFlag(SPF_SUPERSPHERE))
{
hit = RayIntersectionInFront(p1,dir,0);
if (hit)
{
#if DEMO
DrawCircle(int(mCenter.x), int(mCenter.y), int(GetRadius()), 0x404040);
#endif
SpherePack *pack = mChildren;
while (pack)
{
pack->RayTrace(p1,dir,distance,callback);
pack = pack->_GetNextSibling();
}
}
}
else
{
Vector3d sect;
hit = RayIntersection(p1,dir,distance,&sect);
if (hit)
{
callback->RayTraceCallback(p1,dir,distance,sect,this);
}
}
}
void SpherePackFactory::RayTraceCallback(const Vector3d &p1, // source pos of ray
const Vector3d &dir, // direction of ray
float distance, // distance of ray
const Vector3d &/*sect*/, // intersection location
SpherePack *sphere)
{
#ifdef SPHERELIB_STRICT
if (!sphere->IS_SPHERE)
puts("SpherePackFactory::RayTraceCallback");
#endif
SpherePack *link = (SpherePack *) sphere->GetUserData();
if (link) link->RayTrace(p1,dir,distance,mCallback);
};
void SpherePackFactory::Reset(void)
{
mRoot->Reset();
mLeaf->Reset();
}
void SpherePack::Reset(void)
{
ClearSpherePackFlag(SpherePackFlag(SPF_HIDDEN | SPF_PARTIAL | SPF_INSIDE));
SpherePack *pack = mChildren;
while (pack)
{
pack->Reset();
pack = pack->_GetNextSibling();
}
}
void SpherePackFactory::VisibilityCallback(const Frustum &f,SpherePack *sphere,ViewState state)
{
#ifdef SPHERELIB_STRICT
if (!sphere->IS_SPHERE)
puts("SpherePackFactory::VisibilityCallback");
#endif
SpherePack *link = (SpherePack *) sphere->GetUserData();
if (link) link->VisibilityTest(f,mCallback,state);
}