tge/engine/ts/tsShapeInstance.cc
2025-02-17 23:17:30 -06:00

2083 lines
67 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "ts/tsShapeInstance.h"
#include "dgl/dgl.h"
#include "ts/tsLastDetail.h"
#include "console/consoleTypes.h"
#include "ts/tsDecal.h"
#include "platform/profiler.h"
#include "core/frameAllocator.h"
TSShapeInstance::RenderData TSShapeInstance::smRenderData;
MatrixF * TSShapeInstance::ObjectInstance::smTransforms = NULL;
S32 TSShapeInstance::smMaxSnapshotScale = 2;
bool TSShapeInstance::smNoRenderTranslucent = false;
bool TSShapeInstance::smNoRenderNonTranslucent = false;
F32 TSShapeInstance::smDetailAdjust = 1.0f;
F32 TSShapeInstance::smScreenError = 5.0f;
bool TSShapeInstance::smFogExemptionOn = false;
S32 TSShapeInstance::smNumSkipRenderDetails = 0;
bool TSShapeInstance::smSkipFirstFog = false;
bool TSShapeInstance::smSkipFog = false;
Vector<QuatF> TSShapeInstance::smNodeCurrentRotations(__FILE__, __LINE__);
Vector<Point3F> TSShapeInstance::smNodeCurrentTranslations(__FILE__, __LINE__);
Vector<F32> TSShapeInstance::smNodeCurrentUniformScales(__FILE__, __LINE__);
Vector<Point3F> TSShapeInstance::smNodeCurrentAlignedScales(__FILE__, __LINE__);
Vector<TSScale> TSShapeInstance::smNodeCurrentArbitraryScales(__FILE__, __LINE__);
Vector<TSThread*> TSShapeInstance::smRotationThreads(__FILE__, __LINE__);
Vector<TSThread*> TSShapeInstance::smTranslationThreads(__FILE__, __LINE__);
Vector<TSThread*> TSShapeInstance::smScaleThreads(__FILE__, __LINE__);
namespace {
void tsShapeTextureEventCB(const U32 eventCode, void *userData)
{
TSShape* pShape = reinterpret_cast<TSShape*>(userData);
if (eventCode == TextureManager::BeginZombification &&
pShape->mVertexBuffer != -1)
{
// ideally we would de-register the callback here, but that would screw up the loop
if (dglDoesSupportVertexBuffer())
glFreeVertexBufferEXT(pShape->mVertexBuffer);
else
AssertFatal(false,"Vertex buffer should have already been freed!");
pShape->mVertexBuffer = -1;
for (S32 i = 0; i < pShape->objects.size(); ++i)
pShape->mPreviousMerge[i] = -1;
}
}
}
//-------------------------------------------------------------------------------------
// constructors, destructors, initialization
//-------------------------------------------------------------------------------------
TSShapeInstance::TSShapeInstance(const Resource<TSShape> & shape, bool loadMaterials)
{
VECTOR_SET_ASSOCIATION(mMeshObjects);
VECTOR_SET_ASSOCIATION(mDecalObjects);
VECTOR_SET_ASSOCIATION(mIflMaterialInstances);
VECTOR_SET_ASSOCIATION(mNodeTransforms);
VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
VECTOR_SET_ASSOCIATION(mThreadList);
VECTOR_SET_ASSOCIATION(mTransitionThreads);
hShape = shape;
mShape = hShape;
buildInstanceData(mShape, loadMaterials);
}
TSShapeInstance::TSShapeInstance(TSShape * _shape, bool loadMaterials)
{
VECTOR_SET_ASSOCIATION(mMeshObjects);
VECTOR_SET_ASSOCIATION(mDecalObjects);
VECTOR_SET_ASSOCIATION(mIflMaterialInstances);
VECTOR_SET_ASSOCIATION(mNodeTransforms);
VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
VECTOR_SET_ASSOCIATION(mThreadList);
VECTOR_SET_ASSOCIATION(mTransitionThreads);
mShape = _shape;
buildInstanceData(mShape, loadMaterials);
}
TSShapeInstance::~TSShapeInstance()
{
S32 i;
for (i=0; i<mMeshObjects.size(); i++)
destructInPlace(&mMeshObjects[i]);
for (i=0; i<mDecalObjects.size(); i++)
destructInPlace(&mDecalObjects[i]);
while (mThreadList.size())
destroyThread(mThreadList.last());
setMaterialList(NULL);
delete [] mDirtyFlags;
}
void TSShapeInstance::init()
{
smRenderData.fogTexture = false;
smRenderData.fogBitmap = NULL;
smRenderData.fogHandle = NULL;
smRenderData.fogMapHandle = NULL;
smRenderData.renderDecals = true;
Con::addVariable("$pref::TS::fogTexture", TypeBool, &smRenderData.fogTexture);
Con::addVariable("$pref::TS::detailAdjust", TypeF32, &smDetailAdjust);
Con::addVariable("$pref::TS::skipLoadDLs", TypeS32, &TSShape::smNumSkipLoadDetails);
Con::addVariable("$pref::TS::skipRenderDLs", TypeS32, &smNumSkipRenderDetails);
Con::addVariable("$pref::TS::skipFirstFog", TypeBool, &smSkipFirstFog);
Con::addVariable("$pref::TS::screenError", TypeF32, &smScreenError);
Con::addVariable("$pref::TS::UseTriangles", TypeBool, &TSMesh::smUseTriangles);
}
void TSShapeInstance::destroy()
{
delete smRenderData.fogHandle;
}
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
{
S32 i,dl;
mShape = _shape;
debrisRefCount = 0;
mEnvironmentMapOn = false;
mEnvironmentMapAlpha = 0.f;
mAllowTwoPassEnvironmentMap = false;
mAlphaIsReflectanceMap = true; // turn this off below if we find an exception
mAllowTwoPassDetailMap = true;
mMaxEnvironmentMapDL = 1; // for shapes < version 23
mMaxDetailMapDL = 0;
// clear callback function and data
mCallback = NULL;
mCallbackData = 0;
mCurrentDetailLevel = 0;
mCurrentIntraDetailLevel = 1.0f;
// all triggers off at start
mTriggerStates = 0;
//
mAlphaAlways = false;
mAlphaAlwaysValue = 1.0f;
mBalloonShape = false;
mBalloonValue = 1.0f;
mUseOverrideTexture = false;
// if never set, never draw fog -- do this here just in case
smRenderData.fogOn = false;
// material list...
mMaterialList = NULL;
mOwnMaterialList = false;
//
mData = 0;
mScaleCurrentlyAnimated = false;
if(loadMaterials)
{
setMaterialList(mShape->materialList);
}
// set up node data
S32 numNodes = mShape->nodes.size();
mNodeTransforms.setSize(numNodes);
// add objects to trees
S32 numObjects = mShape->objects.size();
mMeshObjects.setSize(numObjects);
for (i=0; i<numObjects; i++)
{
const TSObject * obj = &mShape->objects[i];
MeshObjectInstance * objInst = &mMeshObjects[i];
// call objInst constructor
constructInPlace(objInst);
// hook up the object to it's node
objInst->nodeIndex = obj->nodeIndex;
// set up list of meshes
if (obj->numMeshes)
objInst->meshList = &mShape->meshes[obj->startMeshIndex];
else
objInst->meshList = NULL;
objInst->object = obj;
}
// set up decal objects
mDecalObjects.setSize(mShape->decals.size());
for (i=0; i<mShape->decals.size(); i++)
{
const TSShape::Decal * decal = &mShape->decals[i];
DecalObjectInstance * decalInst = &mDecalObjects[i];
// call constructor
constructInPlace(decalInst);
decalInst->decalObject = decal;
// hook up to node
decalInst->targetObject = &mMeshObjects[decal->objectIndex];
decalInst->nodeIndex = decalInst->targetObject->nodeIndex;
// set up list of decal meshes
if (decal->numMeshes)
{
decalInst->decalList = (TSDecalMesh**)&mShape->meshes[decal->startMeshIndex];
for (S32 j=0; j<decal->numMeshes; j++)
if (decalInst->getDecalMesh(j))
{
// point the decal mesh at it's target...
// this is safe since meshes aren't shared between shapes
TSDecalMesh * decalMesh = const_cast<TSDecalMesh*>(decalInst->getDecalMesh(j));
decalMesh->targetMesh = decalInst->targetObject->getMesh(j);
if (!decalMesh->targetMesh)
{
// detecting this a little late, but we don't need this decal since it isn't doing anything
// should only happen on shapes exported before dtsexp 1.18
delete decalMesh;
TSDecalMesh ** dm = const_cast<TSDecalMesh**>(decalInst->decalList+j);
*dm = NULL;
}
}
}
else
decalInst->decalList = NULL;
decalInst->frame = mShape->decalStates[i].frameIndex;
}
// construct ifl material objects
if(loadMaterials)
{
for (i=0; i<mShape->iflMaterials.size(); i++)
{
mIflMaterialInstances.increment();
mIflMaterialInstances.last().iflMaterial = &mShape->iflMaterials[i];
mIflMaterialInstances.last().frame = -1;
}
}
// check to see which dl's have detail texturing
mMaxDetailMapDL = -1;
if(loadMaterials)
{
for (dl=0; dl<mShape->details.size(); dl++)
{
// check meshes on this detail level...
S32 ss = mShape->details[dl].subShapeNum;
S32 od = mShape->details[dl].objectDetailNum;
if (ss<0)
continue; // this is a billboard detail level
S32 start = mShape->subShapeFirstObject[ss];
S32 end = mShape->subShapeNumObjects[ss] + start;
for (i=start; i<end; i++)
{
TSMesh * mesh = mMeshObjects[i].getMesh(od);
if (!mesh)
continue;
for (S32 j=0; j<mesh->primitives.size(); j++)
{
if (mesh->primitives[j].matIndex & TSDrawPrimitive::NoMaterial)
continue;
if (mMaterialList->getDetailMap(mesh->primitives[j].matIndex & TSDrawPrimitive::MaterialMask))
{
mesh->setFlags(TSMesh::HasDetailTexture);
if (dl>mMaxDetailMapDL)
mMaxDetailMapDL = dl;
}
}
}
}
}
// set up subtree data
S32 ss = mShape->subShapeFirstNode.size(); // we have this many subtrees
mDirtyFlags = new U32[ss];
mGroundThread = NULL;
mCurrentDetailLevel = 0;
animateSubtrees();
// Construct billboards if not done already
if(loadMaterials)
((TSShape *) mShape)->setupBillboardDetails(this);
// Scan out the collision hulls...
for (U32 i = 0; i < 16; i++)
{
char buff[128];
dSprintf(buff, sizeof(buff), "Collision-%d", i + 1);
S32 colDetail = mShape->findDetail(buff);
if (colDetail != -1)
{
S32 dl = colDetail;
// get subshape and object detail
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
S32 start = mShape->subShapeFirstObject[ss];
S32 end = mShape->subShapeNumObjects[ss] + start;
if (start<end)
{
// run through objects and validate names...
for (S32 i=start; i<end; i++)
{
MeshObjectInstance * mesh = &mMeshObjects[i];
if (od >= mesh->object->numMeshes)
continue;
// Yell at them if they named something!
if (dStrnicmp(mShape->names[mesh->object->nameIndex], "Col", 3) != 0 && dStrnicmp(mShape->names[mesh->object->nameIndex], "LOSCol", 3) != 0)
{
Con::errorf("%s.dts - Collision mesh names should start with Col or LOSCol, encountered '%s' in detail level %d", mShape->mSourceResource->path,mShape->names[mesh->object->nameIndex], dl);
continue;
}
}
}
}
}
}
void TSShapeInstance::setMaterialList(TSMaterialList * ml)
{
// get rid of old list
if (mOwnMaterialList)
delete mMaterialList;
mMaterialList = ml;
mOwnMaterialList = false;
if (mMaterialList && StringTable) // material lists need the string table to load...
{
// read ifl materials if necessary -- this is here rather than in shape because we can't open 2 files at once :(
if (mShape->materialList == mMaterialList)
((TSShape*)mShape)->readIflMaterials(hShape.getFilePath());
mMaterialList->load(MeshTexture,hShape.getFilePath(),true);
// check for reflectance map not in alpha of texture -- will require more work to emap
for (U32 i=0; i<mMaterialList->getMaterialCount(); i++)
{
if (mMaterialList->getFlags(i) & (TSMaterialList::AuxiliaryMap|TSMaterialList::NeverEnvMap))
continue;
if (!mMaterialList->reflectionInAlpha(i))
{
mAlphaIsReflectanceMap = false;
break; // found our exception
}
}
}
}
void TSShapeInstance::cloneMaterialList()
{
if (mOwnMaterialList)
return;
mMaterialList = new TSMaterialList(mMaterialList);
mOwnMaterialList = true;
}
static bool makeSkinPath(char* buffer, U32 bufferLength, const char* resourcePath,
const char* oldSkin, const char* oldRoot, const char* newRoot)
{
bool replacedRoot = true;
dsize_t oldRootLen = 0;
char* rootStart = NULL;
if (oldRoot == NULL) {
// Not doing any replacing.
replacedRoot = false;
}
else {
// See if original name has the old root in it.
oldRootLen = dStrlen(oldRoot);
AssertFatal((oldRootLen + 1) < bufferLength, "makeSkinPath: Error, skin root name too long");
dStrcpy(buffer, oldRoot);
dStrcat(buffer, ".");
rootStart = dStrstr(oldSkin, buffer);
if (rootStart == NULL) {
replacedRoot = false;
}
}
// Find out how long the total pathname will be.
const dsize_t oldLen = dStrlen(oldSkin);
dsize_t pathLen = 0;
if (resourcePath != NULL) {
pathLen = dStrlen(resourcePath);
}
if (replacedRoot) {
const dsize_t newRootLen = dStrlen(newRoot);
AssertFatal((pathLen + 1 + oldLen + newRootLen - oldRootLen) < bufferLength, "makeSkinPath: Error, pathname too long");
}
else {
AssertFatal((pathLen + 1 + oldLen) < bufferLength, "makeSkinPath: Error, pathname too long");
}
// OK, now make the pathname.
// Start with the resource path:
if (resourcePath != NULL) {
dStrcpy(buffer, resourcePath);
dStrcat(buffer, "/");
}
else {
buffer[0] = '\0';
}
if (replacedRoot) {
// Then the pre-root part of the old name:
dsize_t rootStartPos = rootStart - oldSkin;
if (rootStartPos != 0) {
dStrncat(buffer, oldSkin, rootStartPos);
}
// Then the new root:
dStrcat(buffer, newRoot);
dStrcat(buffer, ".");
// Then the post-root part of the old name:
dStrcat(buffer, oldSkin + rootStartPos + oldRootLen + 1);
}
else {
// Then the old name:
dStrcat(buffer, oldSkin);
}
return replacedRoot;
}
void TSShapeInstance::reSkin(StringHandle& newBaseHandle)
{
#define NAME_BUFFER_LENGTH 256
static char pathName[NAME_BUFFER_LENGTH];
const char* defaultBaseName = "base";
const char* newBaseName;
if (newBaseHandle.isValidString()) {
newBaseName = newBaseHandle.getString();
if (newBaseName == NULL) {
return;
}
}
else {
newBaseName = defaultBaseName;
}
// Make our own copy of the materials list from the resource
// if necessary.
if (ownMaterialList() == false) {
cloneMaterialList();
}
const char* resourcePath = hShape.getFilePath();
// Cycle through the materials.
TSMaterialList* pMatList = getMaterialList();
for (S32 j = 0; j < pMatList->mMaterialNames.size(); j++) {
// Get the name of this material.
const char* pName = pMatList->mMaterialNames[j];
// Bail if no name.
if (pName == NULL) {
continue;
}
// Make a texture file pathname with the new root if this name
// has the old root in it; otherwise just make a path with the
// original name.
bool replacedRoot = makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath,
pName, defaultBaseName, newBaseName);
if (!replacedRoot) {
// If this wasn't in the desired format, set the material's
// texture handle (since that wasn't copied over in the
// cloning) and continue.
pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
continue;
}
// OK, it is a skin texture. Get the handle.
TextureHandle skinHandle = TextureHandle(pathName, MeshTexture, false);
// Do a sanity check; if it fails, use the original skin instead.
if (skinHandle.getGLName() != 0) {
pMatList->mMaterials[j] = skinHandle;
}
else {
makeSkinPath(pathName, NAME_BUFFER_LENGTH, resourcePath, pName, NULL, NULL);
pMatList->mMaterials[j] = TextureHandle(pathName, MeshTexture, false);
}
}
}
//-------------------------------------------------------------------------------------
// Render & detail selection
//-------------------------------------------------------------------------------------
void TSShapeInstance::render(const Point3F * objectScale)
{
if (mCurrentDetailLevel<0)
return;
PROFILE_START(TSShapeInstanceRender);
dglSetRenderPrimType(3);
// alphaIn: we start to alpha-in next detail level when intraDL > 1-alphaIn-alphaOut
// (finishing when intraDL = 1-alphaOut)
// alphaOut: start to alpha-out this detail level when intraDL > 1-alphaOut
// NOTE:
// intraDL is at 1 when if shape were any closer to us we'd be at dl-1,
// intraDL is at 0 when if shape were any farther away we'd be at dl+1
F32 alphaOut = mShape->alphaOut[mCurrentDetailLevel];
F32 alphaIn = mShape->alphaIn[mCurrentDetailLevel];
F32 saveAA = mAlphaAlways ? mAlphaAlwaysValue : 1.0f;
if (mCurrentIntraDetailLevel>alphaIn+alphaOut)
render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
else if (mCurrentIntraDetailLevel>alphaOut)
{
// draw this detail level w/ alpha=1 and next detail level w/
// alpha=1-(intraDl-alphaOut)/alphaIn
// first draw next detail level
if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
{
setAlphaAlways(saveAA * (alphaIn+alphaOut-mCurrentIntraDetailLevel)/alphaIn);
render(mCurrentDetailLevel+1,0.0f,objectScale);
}
setAlphaAlways(saveAA);
render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
}
else
{
// draw next detail level w/ alpha=1 and this detail level w/
// alpha = 1-intraDL/alphaOut
// first draw next detail level
if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
render(mCurrentDetailLevel+1,0.0f,objectScale);
setAlphaAlways(saveAA * mCurrentIntraDetailLevel / alphaOut);
render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
setAlphaAlways(saveAA);
}
dglSetRenderPrimType(0);
PROFILE_END();
}
bool TSShapeInstance::hasTranslucency()
{
if(!mShape->details.size())
return false;
const TSDetail * detail = &mShape->details[0];
S32 ss = detail->subShapeNum;
return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
}
bool TSShapeInstance::hasSolid()
{
if(!mShape->details.size())
return false;
const TSDetail * detail = &mShape->details[0];
S32 ss = detail->subShapeNum;
return mShape->subShapeFirstTranslucentObject[ss] != mShape->subShapeFirstObject[ss];
}
void TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale)
{
// if dl==-1, nothing to do
if (dl==-1)
return;
AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::render");
S32 i;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
// set up static data
setStatics(dl,intraDL,objectScale);
// if we're a billboard detail, draw it and exit
PROFILE_START(TSShapeInstanceRenderBillboards);
if (ss<0)
{
if (!smNoRenderTranslucent)
mShape->billboardDetails[dl]->render(mAlphaAlways ? mAlphaAlwaysValue : 1.0f, smRenderData.fogOn);
PROFILE_END();
return;
}
PROFILE_END();
PROFILE_START(TSShapeInstanceMaterials);
// set up animating ifl materials
for (i=0; i<mIflMaterialInstances.size(); i++)
{
IflMaterialInstance * iflMaterialInstance = &mIflMaterialInstances[i];
const TSShape::IflMaterial * iflMaterial = iflMaterialInstance->iflMaterial;
mMaterialList->remap(iflMaterial->materialSlot, iflMaterial->firstFrame + iflMaterialInstance->frame);
}
// decide how to use gl resources
setupTexturing(dl,intraDL);
// set up gl environment for drawing mesh materials
TSMesh::initMaterials();
PROFILE_END();
S32 start;
S32 end;
bool supportBuffers = dglDoesSupportVertexBuffer();
if (!supportBuffers || !renderMeshesX(ss,od))
{
// run through the meshes
smRenderData.currentTransform = NULL;
S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
for (i=start; i<end; i++)
mMeshObjects[i].render(od,mMaterialList);
}
// if we have a matrix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// restore gl state
TSMesh::resetMaterials();
// render detail maps using a second pass?
if (twoPassDetailMap())
renderDetailMap();
// render environment map using second pass?
if (twoPassEnvironmentMap())
renderEnvironmentMap();
if (!supportBuffers || !renderDecalsX(ss,od))
{
start = mShape->subShapeFirstDecal[ss];
end = mShape->subShapeNumDecals[ss] + start;
if( smRenderData.renderDecals && !smNoRenderTranslucent && start<end)
{
// set up gl environment for decals...
TSDecalMesh::initDecalMaterials();
// render decals...
smRenderData.currentTransform = NULL;
for (i=start; i<end; i++)
mDecalObjects[i].render(od,mMaterialList);
// if we have a matrix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// restore gl state
TSDecalMesh::resetDecalMaterials();
}
}
// render fog if 2-passing it
if (twoPassFog())
renderFog();
// restore gl state
TSMesh::resetMaterials();
clearStatics();
}
bool TSShapeInstance::fillVB()
{
S32 i,start,end,vb;
if ((vb = mShape->mVertexBuffer) == -1)
{
// find out before we calc the needed buffer size if there are any free
if (!glAvailableVertexBufferEXT())
{
PROFILE_END();
return false;
}
GLsizei size = 0;
start = mShape->subShapeFirstObject[0];
end = mShape->subShapeFirstObject[0] + mShape->subShapeNumObjects[0];
for (i = start; i < end; ++i)
size += mMeshObjects[i].getSizeVB(size);
mShape->mMorphable = false;
for (i = start; i < end; ++i)
if (mMeshObjects[i].hasMergeIndices())
{
mShape->mMorphable = true;
break;
}
vb = mShape->mVertexBuffer = glAllocateVertexBufferEXT(size,GL_V12MTNVFMT_EXT,true);
if (vb == -1)
{
PROFILE_END();
return false;
}
if (mShape->mCallbackKey == -1)
mShape->mCallbackKey = TextureManager::registerEventCallback(tsShapeTextureEventCB, mShape);
// run through the meshes -- fill vertex buffer
glLockVertexBufferEXT(vb,0);
for (i = start; i < end; ++i)
mMeshObjects[i].fillVB(vb,mMaterialList);
glUnlockVertexBufferEXT(vb);
}
return true;
}
bool TSShapeInstance::renderMeshesX(S32 ss, S32 od)
{
// TODO: find out why this case doesn't work
if (smRenderData.vertexAlpha.current < 1.0)
return false;
PROFILE_START(TSShapeInstanceMeshes);
S32 i,start,end,vb;
if ((vb = mShape->mVertexBuffer) == -1)
{
// find out before we calc the needed buffer size if there are any free
if (!glAvailableVertexBufferEXT())
{
PROFILE_END();
return false;
}
GLsizei size = 0;
start = mShape->subShapeFirstObject[0];
end = mShape->subShapeFirstObject[0] + mShape->subShapeNumObjects[0];
for (i = start; i < end; ++i)
size += mMeshObjects[i].getSizeVB(size);
mShape->mMorphable = false;
for (i = start; i < end; ++i)
if (mMeshObjects[i].hasMergeIndices())
{
mShape->mMorphable = true;
break;
}
vb = mShape->mVertexBuffer = glAllocateVertexBufferEXT(size,GL_V12MTNVFMT_EXT,true);
if (vb == -1)
{
PROFILE_END();
return false;
}
if (mShape->mCallbackKey == -1)
mShape->mCallbackKey = TextureManager::registerEventCallback(tsShapeTextureEventCB, mShape);
// run through the meshes -- fill vertex buffer
glLockVertexBufferEXT(vb,0);
for (i = start; i < end; ++i)
mMeshObjects[i].fillVB(vb,mMaterialList);
glUnlockVertexBufferEXT(vb);
}
// run through the meshes
start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
if (mShape->mMorphable)
{
PROFILE_START(TSShapeInstanceMorphVB);
glLockVertexBufferEXT(vb,0);
for (i = start; i < end; ++i)
mMeshObjects[i].morphVB(vb,mShape->mPreviousMerge[i],od,mMaterialList);
glUnlockVertexBufferEXT(vb);
PROFILE_END();
}
smRenderData.currentTransform = NULL;
PROFILE_START(TSShapeInstanceRenderVB);
for (i = start; i < end; ++i)
mMeshObjects[i].renderVB(vb,od,mMaterialList);
PROFILE_END();
PROFILE_END();
return true;
}
bool TSShapeInstance::renderDecalsX(S32 ss, S32 od)
{
return false;
ss,od;
// I don't know why, but this doesn't quite work -- no time to fix
#if 0
if (supportBuffers)
{
S32 i,start,end,vb;
vb = mShape->mVertexBuffer;
start = mShape->subShapeFirstDecal[ss];
end = mShape->subShapeNumDecals[ss] + start;
if (smRenderData.renderDecals && start<end)
{
// set up gl environment for decals...
TSDecalMesh::initDecalMaterials();
// render decals...
smRenderData.currentTransform = NULL;
for (i=start; i<end; i++)
{
TSDecalMesh *decal0 = mDecalObjects[i].getDecalMesh(0);
if (!decal0)
continue;
TSMesh *target0 = decal0->targetMesh;
TSDecalMesh *decal;
if (!target0 ||
mDecalObjects[i].targetObject->visible <= 0.01f ||
!(decal = mDecalObjects[i].getDecalMesh(od)) ||
mDecalObjects[i].frame < 0 ||
!decal->targetMesh ||
decal->texgenS.empty() ||
decal->texgenT.empty())
continue;
GLuint foffset = mDecalObjects[i].frame*target0->numMatFrames*target0->vertsPerFrame;
glSetVertexBufferEXT(vb);
glOffsetVertexBufferEXT(vb,target0->vbOffset+foffset);
mDecalObjects[i].render(od,mMaterialList);
}
// if we have a matrix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// restore gl state
TSDecalMesh::resetDecalMaterials();
}
}
else
#endif
}
void TSShapeInstance::setStatics(S32 dl, F32 intraDL, const Point3F * objectScale)
{
ObjectInstance::smTransforms = mNodeTransforms.address();
smRenderData.objectScale = objectScale;
smRenderData.detailLevel = dl;
smRenderData.intraDetailLevel = intraDL;
smRenderData.alwaysAlpha = mAlphaAlways;
smRenderData.alwaysAlphaValue = getAlphaAlwaysValue();
smRenderData.balloonShape = mBalloonShape;
smRenderData.balloonValue = getBalloonValue();
smRenderData.useOverride = mUseOverrideTexture;
smRenderData.override = mOverrideTexture;
S32 ss = mShape->details[dl].subShapeNum;
S32 od = mShape->details[dl].objectDetailNum;
TSMesh::smSaveVerts.setSize(mShape->mMergeBufferSize);
TSMesh::smSaveTVerts.setSize(mShape->mMergeBufferSize);
// If we have a billboard, skip the rest
if (ss < 0)
return;
S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
for (S32 i=start; i<end; i++)
{
TSMesh * mesh = mMeshObjects[i].getMesh(od);
if (mesh)
mesh->saveMergeVerts();
}
}
void TSShapeInstance::clearStatics()
{
ObjectInstance::smTransforms = NULL;
smRenderData.override = NULL;
S32 ss = mShape->details[smRenderData.detailLevel].subShapeNum;
S32 od = mShape->details[smRenderData.detailLevel].objectDetailNum;
S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
for (S32 i=start; i<end; i++)
{
TSMesh * mesh = mMeshObjects[i].getMesh(od);
if (mesh)
mesh->restoreMergeVerts();
}
}
void TSShapeInstance::setupTexturing(S32 dl, F32 intraDL)
{
// first we'll decide what maps we want
// then we'll decide how we can implement them (1-pass or 2-pass or not at all)
// we need to set up these variables
S32 & emapMethod = smRenderData.environmentMapMethod;
S32 & dmapMethod = smRenderData.detailMapMethod;
S32 & fogMethod = smRenderData.fogMethod;
S32 & dmapTE = smRenderData.detailMapTE;
S32 & emapTE = smRenderData.environmentMapTE;
S32 & baseTE = smRenderData.baseTE;
S32 & fogTE = smRenderData.fogTE;
baseTE = 0; // initially assume base texture will go in first TE
// -------------------------------------------------
// what do we want to do?
bool wantEMap = ((mShape->mExportMerge && dl<=mShape->mSmallestVisibleDL/2) ||
(!mShape->mExportMerge && dl<=mMaxEnvironmentMapDL)) &&
mEnvironmentMapOn && (TextureObject*)mEnvironmentMap && mEnvironmentMapAlpha>0.01f;
bool wantDMap = dl<=mMaxDetailMapDL;
bool wantFog = smRenderData.fogOn && !smSkipFog;
smRenderData.detailMapAlpha = (dl<mMaxDetailMapDL || intraDL>0.5f) ? 1.0f : 2.0f * intraDL;
smRenderData.environmentMapAlpha = mEnvironmentMapAlpha *
(
(((mShape->mExportMerge && dl<mShape->mSmallestVisibleDL/2) ||
(!mShape->mExportMerge && dl<mMaxEnvironmentMapDL)) ||
intraDL>0.5f) ? 1.0f : 2.0f * intraDL );
smRenderData.environmentMapGLName = wantEMap ? mEnvironmentMap.getGLName() : 0;
// -------------------------------------------------
// what can we do?
if (!dglDoesSupportARBMultitexture())
{
// we don't support multitexturing -- early out
emapMethod = NO_ENVIRONMENT_MAP;
dmapMethod = (wantDMap && mAllowTwoPassDetailMap) ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
fogMethod = wantFog ? FOG_TWO_PASS : NO_FOG;
return;
}
// how many texture environments (TE's) do we have?
GLint numTE = 1, numUsedTE = 1;
if (dglDoesSupportARBMultitexture())
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&numTE);
// what we do with the TE's will depend on whether the following extension is supported
if (dglDoesSupportTextureEnvCombine())
{
// environment map...
if (wantEMap)
{
if (mAlphaIsReflectanceMap)
{
emapMethod = ENVIRONMENT_MAP_MULTI_1;
emapTE = numUsedTE;
numUsedTE++;
}
else if (numUsedTE+3<=numTE)
{
emapMethod = ENVIRONMENT_MAP_MULTI_3;
emapTE = numUsedTE;
numUsedTE += 3;
}
else if (mAllowTwoPassEnvironmentMap)
emapMethod = ENVIRONMENT_MAP_TWO_PASS;
else
emapMethod = NO_ENVIRONMENT_MAP;
}
else
emapMethod = NO_ENVIRONMENT_MAP;
// detail map...
if (wantDMap)
{
if (smRenderData.detailMapAlpha>0.99f && numTE>=numUsedTE+1)
{
dmapMethod = DETAIL_MAP_MULTI_1;
dmapTE = numUsedTE;
numUsedTE++;
}
else if (smRenderData.detailMapAlpha<=0.9f && numTE>=numUsedTE+2)
{
dmapMethod = DETAIL_MAP_MULTI_2;
dmapTE = 0; // detail texture goes in first unit...
baseTE++; // so we bump this back one...
emapTE++; // this one gets bumped back 2...
numUsedTE += 2; // end up using two additional units..
}
else
dmapMethod = mAllowTwoPassDetailMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
}
else
dmapMethod = NO_DETAIL_MAP;
// fog...
if (wantFog)
{
// DMMUNDO!
if (numTE>=numUsedTE+1 && emapMethod!=ENVIRONMENT_MAP_TWO_PASS)
{
fogMethod = smRenderData.fogMapHandle ? FOG_MULTI_1_TEXGEN : FOG_MULTI_1;
fogTE = numUsedTE;
numUsedTE++;
}
else
fogMethod = smRenderData.fogMapHandle ? FOG_TWO_PASS_TEXGEN : FOG_TWO_PASS;
}
else
fogMethod = NO_FOG;
}
else
{
// we can't single pass environment map without texture combine extension...
wantEMap = wantEMap && mAllowTwoPassEnvironmentMap;
emapMethod = wantEMap ? ENVIRONMENT_MAP_TWO_PASS : NO_ENVIRONMENT_MAP;
// ditto for detail map...
wantDMap = wantDMap && mAllowTwoPassDetailMap;
dmapMethod = wantDMap ? DETAIL_MAP_TWO_PASS : NO_DETAIL_MAP;
fogMethod = wantFog ? FOG_TWO_PASS : NO_FOG;
}
if (emapMethod == NO_ENVIRONMENT_MAP)
smRenderData.environmentMapAlpha = 1.0f;
}
void TSShapeInstance::setupFog(F32 fogAmount, const ColorF & fogColor)
{
smRenderData.fogOn = (fogAmount > 1.0 / 64.0f);
smRenderData.fogMapHandle = NULL;
bool refresh = false;
if (!smRenderData.fogBitmap)
{
smRenderData.fogBitmap = new GBitmap(8,8,false,GBitmap::RGBA);
// clear the bitmap (defaults to 0xff) so if fogColor is 0,0,0
// we will have a valid bitmap
dMemset(smRenderData.fogBitmap->getWritableBits(), 0, 256);
}
if (smRenderData.fogColor.x != fogColor.red ||
smRenderData.fogColor.y != fogColor.green ||
smRenderData.fogColor.z != fogColor.blue)
{
U8 *bits = smRenderData.fogBitmap->getWritableBits();
U8 red = U8(255*fogColor.red);
U8 green = U8(255*fogColor.green);
U8 blue = U8(255*fogColor.blue);
for (U8 i = 0; i < 64; ++i)
{
*bits++ = red;
*bits++ = green;
*bits++ = blue;
bits++;
}
refresh = true;
}
// the ATI Rage 128 needs a forthcoming driver to do do constant alpha blend
if (smRenderData.fogTexture)
{
if (smRenderData.fogColor.w != fogAmount)
{
U8 *bits = smRenderData.fogBitmap->getWritableBits();
U8 fog = U8(255 * fogAmount);
for (U8 i = 0; i < 64; ++i)
{
bits[3] = fog;
bits += 4;
}
refresh = true;
}
}
if (!smRenderData.fogHandle)
smRenderData.fogHandle = new TextureHandle("fog_texture", smRenderData.fogBitmap);
else
if (refresh)
smRenderData.fogHandle->refresh();
smRenderData.fogColor.set(fogColor.red,fogColor.green,fogColor.blue,fogAmount);
}
void TSShapeInstance::setupFog(F32 fogAmount, TextureHandle * fogMap, Point4F & s, Point4F & t)
{
smRenderData.fogColor.w = fogAmount;
smRenderData.fogOn = true;
smRenderData.fogMapHandle = fogMap;
smRenderData.fogTexGenS = s;
smRenderData.fogTexGenT = t;
}
bool TSShapeInstance::twoPassEnvironmentMap()
{
return (smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS);
}
bool TSShapeInstance::twoPassDetailMap()
{
return (smRenderData.detailMapMethod==DETAIL_MAP_TWO_PASS);
}
bool TSShapeInstance::twoPassFog()
{
return (smRenderData.fogMethod==FOG_TWO_PASS || smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN);
}
void TSShapeInstance::renderEnvironmentMap()
{
AssertFatal((void *)mEnvironmentMap!=NULL,"TSShapeInstance::renderEnvironmentMap (1)");
AssertFatal(mEnvironmentMapOn,"TSShapeInstance::renderEnvironmentMap (2)");
AssertFatal(dglDoesSupportARBMultitexture(),"TSShapeInstance::renderEnvironmentMap (3)");
AssertFatal(smRenderData.environmentMapMethod==ENVIRONMENT_MAP_TWO_PASS,"TSShapeInstance::renderEnvironmentMap (4)");
S32 dl = smRenderData.detailLevel;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
S32 start = mShape->subShapeFirstObject[ss];
S32 end = mShape->subShapeNumObjects[ss] + start;
if (start>=end)
return;
// set up gl environment for emap...
TSMesh::initEnvironmentMapMaterials();
// run through objects and render
smRenderData.currentTransform = NULL;
for (S32 i=start; i<end; i++)
mMeshObjects[i].renderEnvironmentMap(od,mMaterialList);
// if we have a matrix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// restore gl state
TSMesh::resetEnvironmentMapMaterials();
}
void TSShapeInstance::renderFog()
{
AssertFatal(smRenderData.fogMethod==FOG_TWO_PASS || smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN,"TSShapeInstance::renderFog");
S32 dl = smRenderData.detailLevel;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
GLboolean wasLit = glIsEnabled(GL_LIGHTING);
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
if (smRenderData.fogMethod==FOG_TWO_PASS_TEXGEN)
{
// set up fog map
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, TSShapeInstance::smRenderData.fogMapHandle->getGLName());
// set up texgen equations
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGenfv(GL_S,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenS.x);
glTexGenfv(GL_T,GL_OBJECT_PLANE,&TSShapeInstance::smRenderData.fogTexGenT.x);
}
else
{
// just one fog color per shape...
glColor4fv(smRenderData.fogColor);
// texture should be disabled already...
}
smRenderData.currentTransform = NULL;
S32 start = smNoRenderNonTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
S32 end = smNoRenderTranslucent ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
for (S32 i=start; i<end; i++)
mMeshObjects[i].renderFog(od, mMaterialList);
// if we have a marix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// reset gl state
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_2D);
glBlendFunc(GL_ONE, GL_ZERO);
glDisableClientState(GL_VERTEX_ARRAY);
if (wasLit)
glEnable(GL_LIGHTING);
}
void TSShapeInstance::renderDetailMap()
{
AssertFatal(smRenderData.detailMapMethod==DETAIL_MAP_TWO_PASS,"TSShapeInstance::renderDetailMap (1)");
S32 dl = smRenderData.detailLevel;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
S32 start = mShape->subShapeFirstObject[ss];
S32 end = mShape->subShapeNumObjects[ss] + start;
// set up gl environment for the detail map
TSMesh::initDetailMapMaterials();
// run through objects and render detail maps
smRenderData.currentTransform = NULL;
for (S32 i=start; i<end; i++)
mMeshObjects[i].renderDetailMap(od,mMaterialList);
// if we have a matrix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
// restore gl state
TSMesh::resetDetailMapMaterials();
}
S32 TSShapeInstance::getCurrentDetail()
{
return mCurrentDetailLevel;
}
F32 TSShapeInstance::getCurrentIntraDetail()
{
return mCurrentIntraDetailLevel;
}
void TSShapeInstance::setCurrentDetail(S32 dl, F32 intraDL)
{
mCurrentDetailLevel = dl;
mCurrentIntraDetailLevel = intraDL>1.0f ? 1.0f : (intraDL<0.0f ? 0.0f : intraDL);
// restrict chosen detail level by cutoff value
S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL);
if (mCurrentDetailLevel>=0 && mCurrentDetailLevel<cutoff)
{
mCurrentDetailLevel = cutoff;
mCurrentIntraDetailLevel = 1.0f;
}
}
S32 TSShapeInstance::selectCurrentDetail(bool ignoreScale)
{
if (mShape->mSmallestVisibleDL>=0 && mShape->details[0].maxError>=0)
// use new scheme
return selectCurrentDetailEx(ignoreScale);
MatrixF toCam;
Point3F p;
dglGetModelview(&toCam);
toCam.mulP(mShape->center,&p);
F32 dist = mDot(p,p);
F32 scale = 1.0f;
if (!ignoreScale)
{
// any scale?
Point3F x,y,z;
toCam.getRow(0,&x);
toCam.getRow(1,&y);
toCam.getRow(2,&z);
F32 scalex = mDot(x,x);
F32 scaley = mDot(y,y);
F32 scalez = mDot(z,z);
scale = scalex;
if (scaley > scale)
scale = scaley;
if (scalez > scale)
scale = scalez;
}
dist /= scale;
dist = mSqrt(dist);
F32 pixelRadius = dglProjectRadius(dist,mShape->radius) * dglGetPixelScale() * smDetailAdjust;
return selectCurrentDetail(pixelRadius);
}
S32 TSShapeInstance::selectCurrentDetailEx(bool ignoreScale)
{
MatrixF toCam;
Point3F p;
dglGetModelview(&toCam);
toCam.mulP(mShape->center,&p);
F32 dist = mDot(p,p);
F32 scale = 1.0f;
if (!ignoreScale)
{
// any scale?
Point3F x,y,z;
toCam.getRow(0,&x);
toCam.getRow(1,&y);
toCam.getRow(2,&z);
F32 scalex = mDot(x,x);
F32 scaley = mDot(y,y);
F32 scalez = mDot(z,z);
scale = scalex;
if (scaley > scale)
scale = scaley;
if (scalez > scale)
scale = scalez;
}
dist /= scale;
dist = mSqrt(dist);
// find tolerance
F32 proj = dglProjectRadius(dist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance
if ( smFogExemptionOn )
return selectCurrentDetailEx(F32(0.001)/proj);
else
return selectCurrentDetailEx(smScreenError/proj);
}
S32 TSShapeInstance::selectCurrentDetail(Point3F offset, F32 invScale)
{
F32 dist = mSqrt(mDot(offset,offset));
dist *= invScale;
return selectCurrentDetail2(dist);
}
S32 TSShapeInstance::selectCurrentDetail2(F32 adjustedDist)
{
if (mShape->mSmallestVisibleDL>=0 && mShape->details[0].maxError>=0)
// use new scheme
return selectCurrentDetail2Ex(adjustedDist);
F32 pixelRadius = dglProjectRadius(adjustedDist,mShape->radius) * dglGetPixelScale();
F32 adjustedPR = pixelRadius * smDetailAdjust;
if(adjustedPR <= mShape->mSmallestVisibleSize)
adjustedPR = mShape->mSmallestVisibleSize + 0.01f;
return selectCurrentDetail(adjustedPR);
}
S32 TSShapeInstance::selectCurrentDetail2Ex(F32 adjustedDist)
{
// find tolerance
F32 proj = dglProjectRadius(adjustedDist,1.0f) * dglGetPixelScale(); // pixel size of 1 meter at given distance
if ( smFogExemptionOn )
return selectCurrentDetailEx(F32(0.001)/proj);
else
return selectCurrentDetailEx(smScreenError/proj);
}
S32 TSShapeInstance::selectCurrentDetail(F32 size)
{
// check to see if not visible first...
if (size<=mShape->mSmallestVisibleSize)
{
// don't render...
mCurrentDetailLevel=-1;
mCurrentIntraDetailLevel = 0.0f;
return -1;
}
// same detail level as last time?
// only search for detail level if the current one isn't the right one already
if ( mCurrentDetailLevel<0 ||
(mCurrentDetailLevel==0 && size<=mShape->details[0].size) ||
(mCurrentDetailLevel>0 && (size<=mShape->details[mCurrentDetailLevel].size || size>mShape->details[mCurrentDetailLevel-1].size)))
{
// scan shape for highest detail size smaller than us...
// shapes details are sorted from largest to smallest...
// a detail of size <= 0 means it isn't a renderable detail level (utility detail)
for (S32 i=0; i<mShape->details.size(); i++)
{
if (size>mShape->details[i].size)
{
mCurrentDetailLevel = i;
break;
}
if (i+1>=mShape->details.size() || mShape->details[i+1].size<0)
{
// We've run out of details and haven't found anything?
// Let's just grab this one.
mCurrentDetailLevel = i;
break;
}
}
}
F32 curSize = mShape->details[mCurrentDetailLevel].size;
F32 nextSize = mCurrentDetailLevel==0 ? 2.0f * curSize : mShape->details[mCurrentDetailLevel-1].size;
mCurrentIntraDetailLevel = nextSize-curSize>0.01f ? (size-curSize) / (nextSize-curSize) : 1.0f;
mCurrentIntraDetailLevel = mCurrentIntraDetailLevel>1.0f ? 1.0f : (mCurrentIntraDetailLevel<0.0f ? 0.0f : mCurrentIntraDetailLevel);
// now restrict chosen detail level by cutoff value
S32 cutoff = getMin(smNumSkipRenderDetails,mShape->mSmallestVisibleDL);
if (mCurrentDetailLevel>=0 && mCurrentDetailLevel<cutoff)
{
mCurrentDetailLevel = cutoff;
mCurrentIntraDetailLevel = 1.0f;
}
return mCurrentDetailLevel;
}
S32 TSShapeInstance::selectCurrentDetailEx(F32 errorTOL)
{
// note: we use 10 time the average error as the metric...this is
// more robust than the maxError...the factor of 10 is to put average error
// on about the same scale as maxError. The errorTOL is how much
// error we are able to tolerate before going to a more detailed version of the
// shape. We look for a pair of details with errors bounding our errorTOL,
// and then we select an interpolation parameter to tween betwen them. Ok, so
// this isn't exactly an error tolerance. A tween value of 0 is the lower poly
// model (higher detail number) and a value of 1 is the higher poly model (lower
// detail number).
// deal with degenerate case first...
// if smallest detail corresponds to less than half tolerable error, then don't even draw
F32 prevErr;
if (mShape->mSmallestVisibleDL<0)
prevErr=0.0f;
else
prevErr = 10.0f * mShape->details[mShape->mSmallestVisibleDL].averageError * 20.0f;
if (mShape->mSmallestVisibleDL<0 || prevErr<errorTOL)
{
// draw last detail
mCurrentDetailLevel=mShape->mSmallestVisibleDL;
mCurrentIntraDetailLevel = 0.0f;
return mCurrentDetailLevel;
}
// this function is a little odd
// the reason is that the detail numbers correspond to
// when we stop using a given detail level...
// we search the details from most error to least error
// until we fit under the tolerance (errorTOL) and then
// we use the next highest detail (higher error)
for (S32 i=mShape->mSmallestVisibleDL; i>=0; i--)
{
F32 err0 = 10.0f * mShape->details[i].averageError;
if (err0 < errorTOL)
{
// ok, stop here
// intraDL = 1 corresponds to fully this detail
// intraDL = 0 corresponds to the next lower (higher number) detail
mCurrentDetailLevel = i;
mCurrentIntraDetailLevel = 1.0f - (errorTOL-err0)/(prevErr-err0);
return mCurrentDetailLevel;
}
prevErr=err0;
}
// get here if we are drawing at DL==0
mCurrentDetailLevel = 1;
mCurrentIntraDetailLevel = 1.0f;
return mCurrentDetailLevel;
}
GBitmap * TSShapeInstance::snapshot(TSShape * shape, U32 width, U32 height, bool mip, MatrixF & cameraPos, S32 dl, F32 intraDL, bool hiQuality)
{
TSShapeInstance * shapeInstance = new TSShapeInstance(shape, true);
shapeInstance->setCurrentDetail(dl,intraDL);
shapeInstance->animate();
GBitmap * bmp = shapeInstance->snapshot(width,height,mip,cameraPos,hiQuality);
delete shapeInstance;
return bmp;
}
GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraPos, S32 dl, F32 intraDL, bool hiQuality)
{
setCurrentDetail(dl,intraDL);
animate();
return snapshot(width,height,mip,cameraPos,hiQuality);
}
GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraMatrix,bool hiQuality)
{
U32 screenWidth = Platform::getWindowSize().x;
U32 screenHeight = Platform::getWindowSize().y;
U32 xcenter = screenWidth >> 1;
U32 ycenter = screenHeight >> 1;
if (screenWidth==0 || screenHeight==0)
return NULL; // probably in exporter...
AssertFatal(width<screenWidth && height<screenHeight,"TSShapeInstance::snapshot: bitmap cannot be larger than screen resolution");
S32 scale = 1;
if (hiQuality)
while ((scale<<1)*width <= screenWidth && (scale<<1)*height <= screenHeight)
scale <<= 1;
if (scale>smMaxSnapshotScale)
scale = smMaxSnapshotScale;
// height and width of intermediate bitmaps
U32 bmpWidth = width*scale;
U32 bmpHeight = height*scale;
Point4F saveClearColor;
glGetFloatv(GL_COLOR_CLEAR_VALUE,(F32*)&saveClearColor);
// setup viewport and frustrum (do orthographic projection)
dglSetViewport(RectI(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight));
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
dglSetFrustum(-mShape->radius, mShape->radius, -mShape->radius, mShape->radius, 1, 20.0f * mShape->radius,true);
// position camera...
glMatrixMode(GL_MODELVIEW);
Point3F y;
cameraMatrix.getColumn(1,&y);
y *= -10.0f * mShape->radius;
y += mShape->center;
cameraMatrix.setColumn(3,y);
cameraMatrix.inverse();
dglLoadMatrix(&cameraMatrix);
// set some initial gl states
glDisable(GL_CULL_FACE);
glDisable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
// take a snapshot of the shape with a black background...
glClearColor(0,0,0,0);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
GBitmap * blackBmp = new GBitmap;
blackBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)blackBmp->getBits(0));
// take a snapshot of the shape with a white background...
glClearColor(1,1,1,1);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
GBitmap * whiteBmp = new GBitmap;
whiteBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)whiteBmp->getBits(0));
glDisable(GL_DEPTH_TEST);
glClearColor(saveClearColor.x,saveClearColor.y,saveClearColor.z,saveClearColor.w);
// now separate the color and alpha channels
GBitmap * bmp = new GBitmap;
bmp->allocateBitmap(width,height,mip,GBitmap::RGBA);
U8 * wbmp = (U8*)whiteBmp->getBits(0);
U8 * bbmp = (U8*)blackBmp->getBits(0);
U8 * dst = (U8*)bmp->getBits(0);
U32 i,j;
if (hiQuality)
{
for (i=0; i<height; i++)
{
for (j=0; j<width; j++)
{
F32 alphaTally = 0.0f;
S32 alphaIntTally = 0;
S32 alphaCount = 0;
F32 rTally = 0.0f;
F32 gTally = 0.0f;
F32 bTally = 0.0f;
for (S32 k=0; k<scale; k++)
{
for (S32 l=0; l<scale; l++)
{
// shape on black background is alpha * color, shape on white background is alpha * color + (1-alpha) * 255
// we want 255 * alpha, or 255 - (white - black)
U32 pos = 3*((i*scale+k)*bmpWidth+j*scale+l);
U32 alpha = 255 - (wbmp[pos+0] - bbmp[pos+0]);
alpha += 255 - (wbmp[pos+1] - bbmp[pos+1]);
alpha += 255 - (wbmp[pos+2] - bbmp[pos+2]);
F32 floatAlpha = ((F32)alpha)/(1.0f*255.0f);
if (alpha != 0)
{
rTally += bbmp[pos+0];
gTally += bbmp[pos+1];
bTally += bbmp[pos+2];
alphaCount++;
}
alphaTally += floatAlpha;
alphaIntTally += alpha;
}
}
F32 invAlpha = alphaTally > 0.01f ? 1.0f / alphaTally : 0.0f;
U32 pos = 4*(i*width+j);
dst[pos+0] = (U8)(rTally * invAlpha);
dst[pos+1] = (U8)(gTally * invAlpha);
dst[pos+2] = (U8)(bTally * invAlpha);
dst[pos+3] = (U8)(((F32)alphaIntTally) / (F32) (3*alphaCount));
}
}
}
else
{
// simpler, probably faster...
for (i=0; i<height*width; i++)
{
// shape on black background is alpha * color, shape on white background is alpha * color + (1-alpha) * 255
// we want 255 * alpha, or 255 - (white - black)
U32 alpha = 255 - (wbmp[i*3+0] - bbmp[i*3+0]);
alpha += 255 - (wbmp[i*3+1] - bbmp[i*3+1]);
alpha += 255 - (wbmp[i*3+2] - bbmp[i*3+2]);
if (alpha != 0)
{
F32 floatAlpha = ((F32)alpha)/(3.0f*255.0f);
dst[i*4+0] = (U8)(bbmp[i*3+0] / floatAlpha);
dst[i*4+1] = (U8)(bbmp[i*3+1] / floatAlpha);
dst[i*4+2] = (U8)(bbmp[i*3+2] / floatAlpha);
dst[i*4+3] = (U8)(alpha/3);
}
else
{
dst[i*4+0] = dst[i*4+1] = dst[i*4+2] = dst[i*4+3] = 0;
}
}
}
delete blackBmp;
delete whiteBmp;
if (mip)
bmp->extrudeMipLevels();
return bmp;
}
void TSShapeInstance::renderShadow(S32 dl, const MatrixF & mat, S32 dim, U32 * bits)
{
// if dl==-1, nothing to do
if (dl==-1)
return;
AssertFatal(dl>=0 && dl<mShape->details.size(),"TSShapeInstance::renderShadow");
S32 i;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
// assert if we're a billboard detail
AssertFatal(ss>=0,"TSShapeInstance::renderShadow: not with a billboard detail level");
// set up render data
setStatics(dl);
// run through the meshes
smRenderData.currentTransform = NULL;
S32 start = mShape->subShapeFirstObject[ss];
S32 end = start + mShape->subShapeNumObjects[ss];
for (i=start; i<end; i++)
mMeshObjects[i].renderShadow(od,mat,dim,bits,mMaterialList);
// if we have a marix pushed, pop it now
if (smRenderData.currentTransform)
glPopMatrix();
clearStatics();
}
//-------------------------------------------------------------------------------------
// Object (MeshObjectInstance & PluginObjectInstance) render methods
//-------------------------------------------------------------------------------------
void TSShapeInstance::ObjectInstance::render(S32, TSMaterialList *)
{
AssertFatal(0,"TSShapeInstance::ObjectInstance::render: no default render method.");
}
void TSShapeInstance::ObjectInstance::renderEnvironmentMap(S32, TSMaterialList *)
{
AssertFatal(0,"TSShapeInstance::ObjectInstance::renderEnvironmentMap: no default render method.");
}
void TSShapeInstance::ObjectInstance::renderDetailMap(S32, TSMaterialList *)
{
AssertFatal(0,"TSShapeInstance::ObjectInstance::renderDetailMap: no default render method.");
}
void TSShapeInstance::ObjectInstance::renderFog(S32, TSMaterialList*)
{
AssertFatal(0,"TSShapeInstance::ObjectInstance::renderFog: no default render method.");
}
S32 TSShapeInstance::MeshObjectInstance::getSizeVB(S32 size)
{
TSMesh *mesh = getMesh(0);
if (mesh && mesh->getMeshType() == TSMesh::StandardMeshType && mesh->vertsPerFrame > 0)
{
mesh->vbOffset = size;
return mesh->numFrames*mesh->numMatFrames*mesh->vertsPerFrame;
}
else
return 0;
}
bool TSShapeInstance::MeshObjectInstance::hasMergeIndices()
{
TSMesh *mesh = getMesh(0);
return (mesh && mesh->getMeshType() == TSMesh::StandardMeshType && mesh->mergeIndices.size());
}
void TSShapeInstance::MeshObjectInstance::fillVB(S32 vb, TSMaterialList *materials)
{
TSMesh *mesh = getMesh(0);
if (!mesh || mesh->getMeshType() != TSMesh::StandardMeshType || mesh->vertsPerFrame <= 0)
return;
for (S32 f = 0; f < mesh->numFrames; ++f)
for (S32 m = 0; m < mesh->numMatFrames; ++m)
mesh->fillVB(vb,f,m,materials);
}
void TSShapeInstance::MeshObjectInstance::morphVB(S32 vb, S32 &previousMerge, S32 objectDetail, TSMaterialList *materials)
{
if (visible > 0.01f)
{
TSMesh *m0 = getMesh(0);
TSMesh *mesh = getMesh(objectDetail);
if (m0 && mesh)
{
// render TSSortedMesh's standard
if (m0->getMeshType() != TSMesh::StandardMeshType)
return;
GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame;
U32 morphSize = mesh->mergeIndices.size();
S32 merge = mesh->vertsPerFrame-morphSize;
if (!morphSize)
return;
if (previousMerge != -1 && previousMerge < merge)
{
S32 tmp = merge;
merge = previousMerge;
previousMerge = tmp;
morphSize = mesh->vertsPerFrame-merge;
}
else
previousMerge = merge;
glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset + merge);
mesh->morphVB(vb,morphSize,frame,matFrame,materials);
}
}
}
void TSShapeInstance::MeshObjectInstance::renderVB(S32 vb, S32 objectDetail, TSMaterialList *materials)
{
if (visible > 0.01f)
{
TSMesh *m0 = getMesh(0);
TSMesh *mesh = getMesh(objectDetail);
if (m0 && mesh)
{
// render TSSortedMesh's standard
if (m0->getMeshType() != TSMesh::StandardMeshType)
{
render(objectDetail, materials);
return;
}
if (mesh->vertsPerFrame <= 0)
return;
MatrixF *transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
GLuint foffset = (frame*m0->numMatFrames + matFrame)*m0->vertsPerFrame;
glSetVertexBufferEXT(vb);
glOffsetVertexBufferEXT(vb,m0->vbOffset + foffset);
if (visible>0.99f)
{
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 &bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
mesh->renderVB(frame,matFrame,materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
else
{
mesh->setFade(visible);
mesh->renderVB(frame,matFrame,materials);
mesh->clearFade();
}
}
}
}
void TSShapeInstance::MeshObjectInstance::render(S32 objectDetail, TSMaterialList * materials)
{
if (visible>0.01f)
{
TSMesh * mesh = getMesh(objectDetail);
if (mesh)
{
MatrixF * transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
if (visible>0.99f)
{
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 & bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
mesh->render(frame,matFrame,materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
else
{
mesh->setFade(visible);
mesh->render(frame,matFrame,materials);
mesh->clearFade();
}
}
}
}
void TSShapeInstance::DecalObjectInstance::render(S32 objectDetail, TSMaterialList * materials)
{
if (targetObject->visible>0.01f)
{
TSDecalMesh * decalMesh = getDecalMesh(objectDetail);
if (decalMesh && frame>=0)
{
MatrixF * transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 & bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
decalMesh->render(targetObject->frame,frame,materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
}
}
void TSShapeInstance::MeshObjectInstance::renderEnvironmentMap(S32 objectDetail, TSMaterialList * materials)
{
if (visible>0.01f)
{
TSMesh * mesh = getMesh(objectDetail);
if (mesh)
{
MatrixF * transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 & bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
mesh->renderEnvironmentMap(frame,matFrame,materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
}
}
void TSShapeInstance::MeshObjectInstance::renderDetailMap(S32 objectDetail, TSMaterialList * materials)
{
if (visible>0.01f)
{
TSMesh * mesh = getMesh(objectDetail);
if (mesh && mesh->getFlags(TSMesh::HasDetailTexture))
{
MatrixF * transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 & bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
mesh->renderDetailMap(frame,matFrame,materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
}
}
void TSShapeInstance::MeshObjectInstance::renderShadow(S32 objectDetail, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList * materialList)
{
if (visible>0.01f)
{
TSMesh * mesh = getMesh(objectDetail);
if (mesh)
{
MatrixF mat2;
MatrixF * transform = getTransform();
if (transform)
mat2.mul(mat,*transform);
else
mat2=mat;
mesh->renderShadow(frame,mat2,dim,bits,materialList);
}
}
}
void TSShapeInstance::MeshObjectInstance::renderFog(S32 objectDetail, TSMaterialList* materials)
{
if (visible>0.01f)
{
TSMesh * mesh = getMesh(objectDetail);
if (mesh)
{
MatrixF * transform = getTransform();
if (transform != TSShapeInstance::smRenderData.currentTransform)
{
if (TSShapeInstance::smRenderData.currentTransform)
glPopMatrix();
if (transform)
{
glPushMatrix();
dglMultMatrix(transform);
}
TSShapeInstance::smRenderData.currentTransform = transform;
}
if (TSShapeInstance::smRenderData.balloonShape)
{
glPushMatrix();
F32 & bv = TSShapeInstance::smRenderData.balloonValue;
glScalef(bv,bv,bv);
}
mesh->renderFog(frame, materials);
if (TSShapeInstance::smRenderData.balloonShape)
glPopMatrix();
}
}
}
void TSShapeInstance::incDebrisRefCount()
{
++debrisRefCount;
}
void TSShapeInstance::decDebrisRefCount()
{
if( debrisRefCount == 0 ) return;
--debrisRefCount;
}
U32 TSShapeInstance::getDebrisRefCount()
{
return debrisRefCount;
}
U32 TSShapeInstance::getNumDetails()
{
if( mShape )
{
return mShape->details.size();
}
return 0;
}