//----------------------------------------------------------------------------- // 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; idl = dd->dls[bump]; dd->intraDL = 1.0f; mPolyCount -= dd->bump[bump]; for (S32 i=0; ibump[i] -= dd->bump[bump]; } void DetailManager::bumpAll(S32 bump) { for (U32 i=0; 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 (mPolyCountbump[bump] > mPolyLimit-smLimitRange) { bumpOne(mDetailData[i],bump); if (mPolyCountselectCurrentDetail2(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; iskipFirst) { countFirst++; dup = true; } // duplicate last detail...or... if (dl==si->getShape()->mSmallestVisibleDL && countLastskipLast) { countLast++; dup = true; } // duplicate other details... if (countMiddleskipMiddle) { 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; jdls[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; iskipFirst) { countFirst++; dup = true; } // duplicate last detail...or... if (dl==pi->getNumDetails()-1 && countLastskipLast) { countLast++; dup = true; } // duplicate other details... if (countMiddleskipMiddle) { 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; jdls[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; }