446 lines
12 KiB
C++
Executable File
446 lines
12 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "sceneGraph/detailManager.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "ts/tsPartInstance.h"
|
|
#include "dgl/dgl.h"
|
|
|
|
// bias towards using same detail level as previous frame
|
|
#define MatchPreviousReward 0.99f
|
|
#define NotMatchPreviousPenalty 1.01f
|
|
|
|
// this is the pref value that the user should be able to set
|
|
F32 DetailManager::smDetailScale = 1.0f;
|
|
// this should be fixed at some large upper-bound
|
|
S32 DetailManager::smMaxPolyLimit = 20000;
|
|
// this should be fixed at some small lower-bound
|
|
S32 DetailManager::smMinPolyLimit = 2000;
|
|
// this determines how much we can under-shoot poly limit when reducing
|
|
S32 DetailManager::smLimitRange = 1000;
|
|
|
|
// default detail profile -- delay detailing out 2 times...
|
|
DetailManager::DetailProfile DetailManager::smDefaultProfile = { 0, 0, 2 };
|
|
|
|
DetailManager * DetailManager::smDetailManager = NULL;
|
|
|
|
S32 DetailManager::smPolysDidRender = 0;
|
|
S32 DetailManager::smPolysTriedToRender = 0;
|
|
|
|
S32 QSORT_CALLBACK FN_CDECL compareDetailData( const void * e1, const void * e2 )
|
|
{
|
|
const DetailManager::DetailData * dd1 = *(const DetailManager::DetailData**)e1;
|
|
const DetailManager::DetailData * dd2 = *(const DetailManager::DetailData**)e2;
|
|
|
|
if (dd1->priority < dd2->priority)
|
|
return -1;
|
|
else if (dd2->priority < dd1->priority)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
DetailManager::DetailManager()
|
|
{
|
|
mInPrepRender = false;
|
|
}
|
|
|
|
DetailManager::~DetailManager()
|
|
{
|
|
U32 i;
|
|
for (i=0; i<mDetailData.size(); i++)
|
|
delete mDetailData[i];
|
|
mDetailData.clear();
|
|
for (i=0; i<mFreeDetailData.size(); i++)
|
|
delete mFreeDetailData[i];
|
|
mFreeDetailData.clear();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
|
|
inline void DetailManager::bumpOne(DetailData * dd, S32 bump)
|
|
{
|
|
dd->dl = dd->dls[bump];
|
|
dd->intraDL = 1.0f;
|
|
mPolyCount -= dd->bump[bump];
|
|
for (S32 i=0; i<MAX_BUMP; i++)
|
|
dd->bump[i] -= dd->bump[bump];
|
|
}
|
|
|
|
void DetailManager::bumpAll(S32 bump)
|
|
{
|
|
for (U32 i=0; i<mDetailData.size(); i++)
|
|
bumpOne(mDetailData[i],bump);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void DetailManager::begin()
|
|
{
|
|
// enter prepRender stage
|
|
mInPrepRender = true;
|
|
|
|
// inc tag...
|
|
mTag++;
|
|
|
|
// clear poly count
|
|
mPolyCount = 0;
|
|
|
|
// set poly limit for this frame
|
|
mPolyLimit = (S32)(smMinPolyLimit + smDetailScale * (smMaxPolyLimit-smMinPolyLimit));
|
|
|
|
// clear bump count
|
|
for (U32 i=0; i<MAX_BUMP; i++)
|
|
mBumpPolyCount[i] = 0;
|
|
}
|
|
|
|
void DetailManager::end()
|
|
{
|
|
S32 i;
|
|
|
|
// leave prepRender stage
|
|
mInPrepRender = false;
|
|
|
|
// update poly count for new frame
|
|
smPolysDidRender = mPolyCount;
|
|
smPolysTriedToRender = mPolyCount;
|
|
|
|
// drop detailData for shapes rendered last time but not this time
|
|
for (i=mDetailData.size()-1; i>=0;i--)
|
|
if (mDetailData[i]->tag!=mTag)
|
|
{
|
|
// not rendering this time
|
|
mDetailData[i]->shapeInstance = NULL; // enough to disconnect it from shape instance...
|
|
mDetailData[i]->partInstance = NULL; // enough to disconnect it from part instance...
|
|
mFreeDetailData.push_back(mDetailData[i]);
|
|
mDetailData.erase(i);
|
|
}
|
|
|
|
// we may already be below the poly limit
|
|
if (mPolyCount<getMax(1,mPolyLimit))
|
|
// we accept the preferred details
|
|
return;
|
|
/*
|
|
// do as many full bumps as we can
|
|
S32 bump;
|
|
for (bump=0; bump<MAX_BUMP; bump++)
|
|
if (mPolyCount-mBumpPolyCount[bump]<mPolyLimit)
|
|
break;
|
|
if (bump)
|
|
// bump everyone this many times...
|
|
bumpAll(bump-1);
|
|
|
|
// update poly count for new frame
|
|
smPolysDidRender = mPolyCount;
|
|
|
|
if (bump==MAX_BUMP)
|
|
// can do no more...
|
|
return;
|
|
|
|
// compute priority function...sort
|
|
for (i=0; i<mDetailData.size(); i++)
|
|
computePriority(mDetailData[i],bump);
|
|
dQsort(mDetailData.address(),mDetailData.size(),sizeof(DetailData*),compareDetailData);
|
|
|
|
// keep reducing until done...
|
|
for (i=0; i<mDetailData.size(); i++)
|
|
{
|
|
if (mPolyCount-mDetailData[i]->bump[bump] > mPolyLimit-smLimitRange)
|
|
{
|
|
bumpOne(mDetailData[i],bump);
|
|
if (mPolyCount<mPolyLimit)
|
|
// we're done...
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
// update poly count for new frame
|
|
smPolysDidRender = mPolyCount;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void DetailManager::selectPotential(TSShapeInstance * si, F32 dist, F32 invScale, const DetailProfile * dp)
|
|
{
|
|
AssertFatal(mInPrepRender,"DetailManager::selectPotentialDetails");
|
|
|
|
dist *= invScale;
|
|
S32 dl = si->selectCurrentDetail2(dist);
|
|
if (dl<0)
|
|
// don't render
|
|
return;
|
|
|
|
DetailData * dd;
|
|
if (!si->mData || ((DetailData*)si->mData)->shapeInstance!=si)
|
|
{
|
|
// we weren't rendered last time
|
|
// get new detail data and set prevDL to -2 to encode that we're fresh meat...
|
|
si->mData = getNewDetailData();
|
|
dd = (DetailData*)si->mData;
|
|
dd->prevDL = -2;
|
|
}
|
|
else
|
|
{
|
|
// we were rendered last time
|
|
dd = (DetailData*)si->mData;
|
|
dd->prevDL = dd->dl;
|
|
}
|
|
|
|
dd->shapeInstance = si;
|
|
dd->partInstance = NULL;
|
|
dd->tag = mTag;
|
|
dd->dl = dl;
|
|
dd->pixelSize = -dist; //pixelRadius; BROKEN, but this'll be fine for now
|
|
dd->intraDL = si->getCurrentIntraDetail();
|
|
|
|
// add in poly count for preferred detail level
|
|
S32 polyCount = si->getShape()->details[dl].polyCount;
|
|
mPolyCount += polyCount;
|
|
|
|
S32 countFirst = 0;
|
|
S32 countMiddle = 0;
|
|
S32 countLast = 0;
|
|
for (S32 i=0; i<MAX_BUMP; i++)
|
|
{
|
|
// duplicate first detail...or...
|
|
bool dup = false;
|
|
if (dl==0 && countFirst<dp->skipFirst)
|
|
{
|
|
countFirst++;
|
|
dup = true;
|
|
}
|
|
|
|
// duplicate last detail...or...
|
|
if (dl==si->getShape()->mSmallestVisibleDL && countLast<dp->skipLast)
|
|
{
|
|
countLast++;
|
|
dup = true;
|
|
}
|
|
|
|
// duplicate other details...
|
|
if (countMiddle<dp->skipMiddle)
|
|
{
|
|
countMiddle++;
|
|
dup = true;
|
|
}
|
|
else
|
|
countMiddle = 0;
|
|
|
|
// find the next detail...
|
|
if (!dup)
|
|
{
|
|
if (dl==si->getShape()->mSmallestVisibleDL)
|
|
dl = -1;
|
|
else
|
|
dl++;
|
|
}
|
|
|
|
dd->dls[i] = dl;
|
|
S32 detailPolys = dl>=0 ? si->getShape()->details[dl].polyCount : 0;
|
|
dd->bump[i] = polyCount - detailPolys;
|
|
mBumpPolyCount[i] += dd->bump[i];
|
|
|
|
if (dl<0)
|
|
{
|
|
for (S32 j=i+1; j<MAX_BUMP; j++)
|
|
{
|
|
dd->dls[j] = -1;
|
|
dd->bump[j] = polyCount;
|
|
mBumpPolyCount[j] += dd->bump[j];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void DetailManager::selectPotential(TSPartInstance * pi, F32 dist, F32 invScale, const DetailProfile * dp)
|
|
{
|
|
AssertFatal(mInPrepRender,"DetailManager::selectPotentialDetails");
|
|
|
|
dist *= invScale;
|
|
pi->selectCurrentDetail2(dist);
|
|
S32 dl = pi->getCurrentObjectDetail();
|
|
if (dl<0)
|
|
// don't render
|
|
return;
|
|
|
|
DetailData * dd;
|
|
if (!pi->mData || ((DetailData*)pi->mData)->partInstance!=pi)
|
|
{
|
|
// we weren't rendered last time
|
|
// get new detail data and set prevDL to -2 to encode that we're fresh meat...
|
|
pi->mData = getNewDetailData();
|
|
dd = (DetailData*)pi->mData;
|
|
dd->prevDL = -2;
|
|
}
|
|
else
|
|
{
|
|
// we were rendered last time
|
|
dd = (DetailData*)pi->mData;
|
|
dd->prevDL = dd->dl;
|
|
}
|
|
|
|
dd->shapeInstance = NULL;
|
|
dd->partInstance = pi;
|
|
dd->tag = mTag;
|
|
dd->dl = dl;
|
|
dd->pixelSize = -dist;
|
|
dd->intraDL = pi->getCurrentIntraDetail();
|
|
|
|
// add in poly count for preferred detail level
|
|
S32 polyCount = pi->getPolyCount(dl);
|
|
mPolyCount += polyCount;
|
|
|
|
S32 countFirst = 0;
|
|
S32 countMiddle = 0;
|
|
S32 countLast = 0;
|
|
for (S32 i=0; i<MAX_BUMP; i++)
|
|
{
|
|
// duplicate first detail...or...
|
|
bool dup = false;
|
|
if (dl==0 && countFirst<dp->skipFirst)
|
|
{
|
|
countFirst++;
|
|
dup = true;
|
|
}
|
|
|
|
// duplicate last detail...or...
|
|
if (dl==pi->getNumDetails()-1 && countLast<dp->skipLast)
|
|
{
|
|
countLast++;
|
|
dup = true;
|
|
}
|
|
|
|
// duplicate other details...
|
|
if (countMiddle<dp->skipMiddle)
|
|
{
|
|
countMiddle++;
|
|
dup = true;
|
|
}
|
|
else
|
|
countMiddle = 0;
|
|
|
|
// find the next detail...
|
|
if (!dup)
|
|
{
|
|
if (dl==pi->getNumDetails()-1)
|
|
dl = -1;
|
|
else
|
|
dl++;
|
|
}
|
|
|
|
dd->dls[i] = dl;
|
|
S32 detailPolys = pi->getPolyCount(dl);
|
|
dd->bump[i] = polyCount - detailPolys;
|
|
mBumpPolyCount[i] += dd->bump[i];
|
|
|
|
if (dl<0)
|
|
{
|
|
for (S32 j=i+1; j<MAX_BUMP; j++)
|
|
{
|
|
dd->dls[j] = -1;
|
|
dd->bump[j] = polyCount;
|
|
mBumpPolyCount[j] += dd->bump[j];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
bool DetailManager::selectCurrent(TSShapeInstance * si)
|
|
{
|
|
AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail");
|
|
|
|
DetailData * dd = (DetailData*)si->mData;
|
|
if ( !dd || dd->shapeInstance != si)
|
|
// not rendering...
|
|
return false;
|
|
si->setCurrentDetail(dd->dl,dd->intraDL);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
bool DetailManager::selectCurrent(TSPartInstance * pi)
|
|
{
|
|
AssertFatal(!mInPrepRender,"DetailManager::selectCurrentDetail");
|
|
|
|
DetailData * dd = (DetailData*)pi->mData;
|
|
if ( !dd || dd->partInstance != pi)
|
|
// not rendering...
|
|
return false;
|
|
pi->setCurrentObjectDetail(dd->dl);
|
|
pi->setCurrentIntraDetail(dd->intraDL);
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void DetailManager::computePriority(DetailData * detailData, S32 bump)
|
|
{
|
|
S32 oldSize, newSize;
|
|
if (detailData->shapeInstance)
|
|
{
|
|
// shape instance...
|
|
if (bump>0)
|
|
oldSize = (S32)(detailData->dl>=0 ? detailData->shapeInstance->getShape()->details[detailData->dl].size : 0);
|
|
else
|
|
oldSize = (S32)detailData->pixelSize;
|
|
newSize = (S32)(detailData->dls[bump]>=0 ? detailData->shapeInstance->getShape()->details[detailData->dls[bump]].size : 0);
|
|
}
|
|
else
|
|
{
|
|
// part instance...
|
|
AssertFatal(detailData->partInstance,"DetailManager::computePriority");
|
|
if (bump>0)
|
|
oldSize = (S32)detailData->partInstance->getDetailSize(detailData->dl);
|
|
else
|
|
oldSize = (S32)detailData->pixelSize;
|
|
newSize = (S32)detailData->partInstance->getDetailSize(detailData->dls[bump]);
|
|
}
|
|
|
|
// priority weighted by both total bump and recent bump (total bump component helps break ties
|
|
// when recent bump has the same effect).
|
|
detailData->priority = 0.5f * (detailData->pixelSize - oldSize) + (oldSize - newSize);
|
|
|
|
// try to be consistent between frames...as much as possible
|
|
if (detailData->prevDL<0)
|
|
{
|
|
if (detailData->prevDL==-1)
|
|
{
|
|
// weren't visible last time...penalize for being visible this time, reward for being invisible
|
|
if (detailData->dls[bump]==-1)
|
|
detailData->priority *= MatchPreviousReward;
|
|
else if (detailData->dl!=-1)
|
|
detailData->priority *= NotMatchPreviousPenalty;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reward for bumping us down if we are at a higher detail than last time,
|
|
// penalize for bumping us down if our detail is at or lower than last time
|
|
// Note: higher detail --> smaller dl number
|
|
if (detailData->dl > detailData->prevDL)
|
|
detailData->priority *= MatchPreviousReward;
|
|
else
|
|
detailData->priority *= NotMatchPreviousPenalty;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
DetailManager::DetailData * DetailManager::getNewDetailData()
|
|
{
|
|
DetailData * ret;
|
|
if (mFreeDetailData.size())
|
|
{
|
|
ret = mFreeDetailData.last();
|
|
mFreeDetailData.decrement();
|
|
}
|
|
else
|
|
ret = new DetailData;
|
|
mDetailData.push_back(ret);
|
|
return ret;
|
|
}
|
|
|