mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
906 lines
31 KiB
C++
906 lines
31 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
|
|
#include "ts/tsLastDetail.h"
|
|
#include "ts/tsMaterialList.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "ts/tsDecal.h"
|
|
#include "platform/profiler.h"
|
|
#include "core/frameAllocator.h"
|
|
#include "gfx/gfxDevice.h"
|
|
#include "materials/materialManager.h"
|
|
#include "materials/materialFeatureTypes.h"
|
|
#include "materials/sceneData.h"
|
|
#include "materials/matInstance.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "gfx/primBuilder.h"
|
|
#include "gfx/gfxDrawUtil.h"
|
|
#include "core/module.h"
|
|
|
|
MODULE_BEGIN( TSShapeInstance )
|
|
|
|
MODULE_INIT
|
|
{
|
|
Con::addVariable("$pref::TS::detailAdjust", TypeF32, &TSShapeInstance::smDetailAdjust,
|
|
"@brief User perference for scaling the TSShape level of detail.\n"
|
|
"The smaller the value the closer the camera must get to see the "
|
|
"highest LOD. This setting can have a huge impact on performance in "
|
|
"mesh heavy scenes. The default value is 1.\n"
|
|
"@ingroup Rendering\n" );
|
|
|
|
Con::addVariable("$pref::TS::skipLoadDLs", TypeS32, &TSShape::smNumSkipLoadDetails,
|
|
"@brief User perference which causes TSShapes to skip loading higher lods.\n"
|
|
"This potentialy reduces the GPU resources and materials generated as well as "
|
|
"limits the LODs rendered. The default value is 0.\n"
|
|
"@see $pref::TS::skipRenderDLs\n"
|
|
"@ingroup Rendering\n" );
|
|
|
|
Con::addVariable("$pref::TS::skipRenderDLs", TypeS32, &TSShapeInstance::smNumSkipRenderDetails,
|
|
"@brief User perference which causes TSShapes to skip rendering higher lods.\n"
|
|
"This will reduce the number of draw calls and triangles rendered and improve "
|
|
"rendering performance when proper LODs have been created for your models. "
|
|
"The default value is 0.\n"
|
|
"@see $pref::TS::skipLoadDLs\n"
|
|
"@ingroup Rendering\n" );
|
|
|
|
Con::addVariable("$pref::TS::smallestVisiblePixelSize", TypeF32, &TSShapeInstance::smSmallestVisiblePixelSize,
|
|
"@brief User perference which sets the smallest pixel size at which TSShapes will skip rendering.\n"
|
|
"This will force all shapes to stop rendering when they get smaller than this size. "
|
|
"The default value is -1 which disables it.\n"
|
|
"@ingroup Rendering\n" );
|
|
|
|
Con::addVariable("$pref::TS::maxInstancingVerts", TypeS32, &TSMesh::smMaxInstancingVerts,
|
|
"@brief Enables mesh instancing on non-skin meshes that have less that this count of verts.\n"
|
|
"The default value is 2000. Higher values can degrade performance.\n"
|
|
"@ingroup Rendering\n" );
|
|
}
|
|
|
|
MODULE_END;
|
|
|
|
|
|
F32 TSShapeInstance::smDetailAdjust = 1.0f;
|
|
F32 TSShapeInstance::smSmallestVisiblePixelSize = -1.0f;
|
|
S32 TSShapeInstance::smNumSkipRenderDetails = 0;
|
|
|
|
F32 TSShapeInstance::smLastScreenErrorTolerance = 0.0f;
|
|
F32 TSShapeInstance::smLastScaledDistance = 0.0f;
|
|
F32 TSShapeInstance::smLastPixelSize = 0.0f;
|
|
|
|
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<MatrixF> TSShapeInstance::smNodeLocalTransforms(__FILE__, __LINE__);
|
|
TSIntegerSet TSShapeInstance::smNodeLocalTransformDirty;
|
|
|
|
Vector<TSThread*> TSShapeInstance::smRotationThreads(__FILE__, __LINE__);
|
|
Vector<TSThread*> TSShapeInstance::smTranslationThreads(__FILE__, __LINE__);
|
|
Vector<TSThread*> TSShapeInstance::smScaleThreads(__FILE__, __LINE__);
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// constructors, destructors, initialization
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
TSShapeInstance::TSShapeInstance( const Resource<TSShape> &shape, bool loadMaterials )
|
|
{
|
|
VECTOR_SET_ASSOCIATION(mMeshObjects);
|
|
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);
|
|
|
|
mShapeResource = shape;
|
|
mShape = mShapeResource;
|
|
mUseOverrideTexture = false;
|
|
buildInstanceData( mShape, loadMaterials );
|
|
}
|
|
|
|
TSShapeInstance::TSShapeInstance( TSShape *shape, bool loadMaterials )
|
|
{
|
|
VECTOR_SET_ASSOCIATION(mMeshObjects);
|
|
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);
|
|
|
|
mShapeResource = NULL;
|
|
mShape = shape;
|
|
mUseOverrideTexture = false;
|
|
buildInstanceData( mShape, loadMaterials );
|
|
}
|
|
|
|
TSShapeInstance::~TSShapeInstance()
|
|
{
|
|
mMeshObjects.clear();
|
|
|
|
while (mThreadList.size())
|
|
destroyThread(mThreadList.last());
|
|
|
|
setMaterialList(NULL);
|
|
|
|
delete [] mDirtyFlags;
|
|
}
|
|
|
|
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
|
|
{
|
|
mShape = _shape;
|
|
|
|
debrisRefCount = 0;
|
|
|
|
mCurrentDetailLevel = 0;
|
|
mCurrentIntraDetailLevel = 1.0f;
|
|
|
|
// all triggers off at start
|
|
mTriggerStates = 0;
|
|
|
|
//
|
|
mAlphaAlways = false;
|
|
mAlphaAlwaysValue = 1.0f;
|
|
|
|
// material list...
|
|
mMaterialList = NULL;
|
|
mOwnMaterialList = false;
|
|
mUseOwnBuffer = false;
|
|
|
|
//
|
|
mData = 0;
|
|
mScaleCurrentlyAnimated = false;
|
|
|
|
if(loadMaterials)
|
|
setMaterialList(mShape->materialList);
|
|
|
|
// set up node data
|
|
initNodeTransforms();
|
|
|
|
// add objects to trees
|
|
initMeshObjects();
|
|
|
|
// 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 && mShapeResource && GFXDevice::devicePresent() )
|
|
mShape->setupBillboardDetails( mShapeResource.getPath().getFullPath() );
|
|
}
|
|
|
|
void TSShapeInstance::initNodeTransforms()
|
|
{
|
|
// set up node data
|
|
S32 numNodes = mShape->nodes.size();
|
|
mNodeTransforms.setSize(numNodes);
|
|
}
|
|
|
|
void TSShapeInstance::initMeshObjects()
|
|
{
|
|
// add objects to trees
|
|
S32 numObjects = mShape->objects.size();
|
|
mMeshObjects.setSize(numObjects);
|
|
for (S32 i=0; i<numObjects; i++)
|
|
{
|
|
const TSObject * obj = &mShape->objects[i];
|
|
MeshObjectInstance * objInst = &mMeshObjects[i];
|
|
|
|
// hook up the object to it's node and transforms.
|
|
objInst->mTransforms = &mNodeTransforms;
|
|
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;
|
|
objInst->forceHidden = false;
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::setMaterialList( TSMaterialList *matList )
|
|
{
|
|
// get rid of old list
|
|
if ( mOwnMaterialList )
|
|
delete mMaterialList;
|
|
|
|
mMaterialList = matList;
|
|
mOwnMaterialList = false;
|
|
|
|
// If the material list is already be mapped then
|
|
// don't bother doing the initializing a second time.
|
|
// Note: only check the last material instance as this will catch both
|
|
// uninitialised lists, as well as initialised lists that have had new
|
|
// materials appended
|
|
if ( mMaterialList && !mMaterialList->getMaterialInst( mMaterialList->size()-1 ) )
|
|
{
|
|
mMaterialList->setTextureLookupPath( mShapeResource.getPath().getPath() );
|
|
mMaterialList->mapMaterials();
|
|
Material::sAllowTextureTargetAssignment = true;
|
|
initMaterialList();
|
|
Material::sAllowTextureTargetAssignment = false;
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::cloneMaterialList( const FeatureSet *features )
|
|
{
|
|
if ( mOwnMaterialList )
|
|
return;
|
|
|
|
Material::sAllowTextureTargetAssignment = true;
|
|
mMaterialList = new TSMaterialList(mMaterialList);
|
|
initMaterialList( features );
|
|
Material::sAllowTextureTargetAssignment = false;
|
|
|
|
mOwnMaterialList = true;
|
|
}
|
|
|
|
void TSShapeInstance::initMaterialList( const FeatureSet *features )
|
|
{
|
|
// If we don't have features then use the default.
|
|
if ( !features )
|
|
features = &MATMGR->getDefaultFeatures();
|
|
|
|
// Initialize the materials.
|
|
mMaterialList->initMatInstances( *features, mShape->getVertexFormat() );
|
|
|
|
// TODO: It would be good to go thru all the meshes and
|
|
// pre-create all the active material hooks for shadows,
|
|
// reflections, and instancing. This would keep these
|
|
// hiccups from happening at runtime.
|
|
}
|
|
|
|
void TSShapeInstance::reSkin( String newBaseName, String oldBaseName )
|
|
{
|
|
if( newBaseName.isEmpty() )
|
|
newBaseName = "base";
|
|
if( oldBaseName.isEmpty() )
|
|
oldBaseName = "base";
|
|
|
|
if ( newBaseName.equal( oldBaseName, String::NoCase ) )
|
|
return;
|
|
|
|
const U32 oldBaseNameLength = oldBaseName.length();
|
|
|
|
// Make our own copy of the materials list from the resource if necessary
|
|
if (ownMaterialList() == false)
|
|
cloneMaterialList();
|
|
|
|
TSMaterialList* pMatList = getMaterialList();
|
|
pMatList->setTextureLookupPath( mShapeResource.getPath().getPath() );
|
|
|
|
// Cycle through the materials
|
|
const Vector<String> &materialNames = pMatList->getMaterialNameList();
|
|
for ( S32 i = 0; i < materialNames.size(); i++ )
|
|
{
|
|
// Try changing base
|
|
const String &pName = materialNames[i];
|
|
String newName( String::ToLower(pName) );
|
|
newName.replace( String::ToLower(oldBaseName), String::ToLower(newBaseName) );
|
|
pMatList->renameMaterial( i, newName );
|
|
}
|
|
|
|
// Initialize the material instances
|
|
initMaterialList();
|
|
}
|
|
|
|
void TSShapeInstance::resetMaterialList()
|
|
{
|
|
TSMaterialList* oMatlist = mShape->materialList;
|
|
setMaterialList(oMatlist);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Render & detail selection
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void TSShapeInstance::renderDebugNormals( F32 normalScalar, S32 dl )
|
|
{
|
|
if ( dl < 0 )
|
|
return;
|
|
|
|
AssertFatal( dl >= 0 && dl < mShape->details.size(),
|
|
"TSShapeInstance::renderDebugNormals() - Bad detail level!" );
|
|
|
|
static GFXStateBlockRef sb;
|
|
if ( sb.isNull() )
|
|
{
|
|
GFXStateBlockDesc desc;
|
|
desc.setCullMode( GFXCullNone );
|
|
desc.setZReadWrite( true );
|
|
desc.zWriteEnable = false;
|
|
desc.vertexColorEnable = true;
|
|
|
|
sb = GFX->createStateBlock( desc );
|
|
}
|
|
GFX->setStateBlock( sb );
|
|
|
|
const TSDetail *detail = &mShape->details[dl];
|
|
const S32 ss = detail->subShapeNum;
|
|
if ( ss < 0 )
|
|
return;
|
|
|
|
const S32 start = mShape->subShapeFirstObject[ss];
|
|
const S32 end = start + mShape->subShapeNumObjects[ss];
|
|
|
|
for ( S32 i = start; i < end; i++ )
|
|
{
|
|
MeshObjectInstance *meshObj = &mMeshObjects[i];
|
|
if ( !meshObj )
|
|
continue;
|
|
|
|
const MatrixF &meshMat = meshObj->getTransform();
|
|
|
|
// Then go through each TSMesh...
|
|
U32 m = 0;
|
|
for( TSMesh *mesh = meshObj->getMesh(m); mesh != NULL; mesh = meshObj->getMesh(m++) )
|
|
{
|
|
// and pull out the list of normals.
|
|
const U32 numNrms = mesh->mNumVerts;
|
|
PrimBuild::begin( GFXLineList, 2 * numNrms );
|
|
for ( U32 n = 0; n < numNrms; n++ )
|
|
{
|
|
const TSMesh::__TSMeshVertexBase &v = mesh->mVertexData.getBase(n);
|
|
Point3F norm = v.normal();
|
|
Point3F vert = v.vert();
|
|
|
|
meshMat.mulP( vert );
|
|
meshMat.mulV( norm );
|
|
|
|
// Then render them.
|
|
PrimBuild::color4f( mFabs( norm.x ), mFabs( norm.y ), mFabs( norm.z ), 1.0f );
|
|
PrimBuild::vertex3fv( vert );
|
|
PrimBuild::vertex3fv( vert + (norm * normalScalar) );
|
|
}
|
|
|
|
PrimBuild::end();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::renderDebugNodes()
|
|
{
|
|
GFXDrawUtil *drawUtil = GFX->getDrawUtil();
|
|
ColorI color( 255, 0, 0, 255 );
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.setBlend( false );
|
|
desc.setZReadWrite( false, false );
|
|
|
|
for ( U32 i = 0; i < mNodeTransforms.size(); i++ )
|
|
drawUtil->drawTransform( desc, mNodeTransforms[i], NULL, NULL );
|
|
}
|
|
|
|
void TSShapeInstance::listMeshes( const String &state ) const
|
|
{
|
|
if ( state.equal( "All", String::NoCase ) )
|
|
{
|
|
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
|
|
{
|
|
const MeshObjectInstance &mesh = mMeshObjects[i];
|
|
Con::warnf( "meshidx %3d, %8s, %s", i, ( mesh.forceHidden ) ? "Hidden" : "Visible", mShape->getMeshName(i).c_str() );
|
|
}
|
|
}
|
|
else if ( state.equal( "Hidden", String::NoCase ) )
|
|
{
|
|
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
|
|
{
|
|
const MeshObjectInstance &mesh = mMeshObjects[i];
|
|
if ( mesh.forceHidden )
|
|
Con::warnf( "meshidx %3d, %8s, %s", i, "Visible", mShape->getMeshName(i).c_str() );
|
|
}
|
|
}
|
|
else if ( state.equal( "Visible", String::NoCase ) )
|
|
{
|
|
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
|
|
{
|
|
const MeshObjectInstance &mesh = mMeshObjects[i];
|
|
if ( !mesh.forceHidden )
|
|
Con::warnf( "meshidx %3d, %8s, %s", i, "Hidden", mShape->getMeshName(i).c_str() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con::warnf( "TSShapeInstance::listMeshes( %s ) - only All/Hidden/Visible are valid parameters." );
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::render( const TSRenderState &rdata )
|
|
{
|
|
if (mCurrentDetailLevel<0)
|
|
return;
|
|
|
|
PROFILE_SCOPE( TSShapeInstance_Render );
|
|
|
|
// 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;
|
|
|
|
/// This first case is the single detail level render.
|
|
if ( mCurrentIntraDetailLevel > alphaIn + alphaOut )
|
|
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
|
|
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( rdata, mCurrentDetailLevel + 1, 0.0f );
|
|
}
|
|
|
|
setAlphaAlways( saveAA );
|
|
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
|
|
}
|
|
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( rdata, mCurrentDetailLevel+1, 0.0f );
|
|
|
|
setAlphaAlways( saveAA * mCurrentIntraDetailLevel / alphaOut );
|
|
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
|
|
setAlphaAlways( saveAA );
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::setMeshForceHidden( const char *meshName, bool hidden )
|
|
{
|
|
Vector<MeshObjectInstance>::iterator iter = mMeshObjects.begin();
|
|
for ( ; iter != mMeshObjects.end(); iter++ )
|
|
{
|
|
S32 nameIndex = iter->object->nameIndex;
|
|
const char *name = mShape->names[ nameIndex ];
|
|
|
|
if ( String::compare( meshName, name ) == 0 )
|
|
{
|
|
iter->forceHidden = hidden;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeInstance::setMeshForceHidden( S32 meshIndex, bool hidden )
|
|
{
|
|
AssertFatal( meshIndex > -1 && meshIndex < mMeshObjects.size(),
|
|
"TSShapeInstance::setMeshForceHidden - Invalid index!" );
|
|
|
|
mMeshObjects[meshIndex].forceHidden = hidden;
|
|
}
|
|
|
|
void TSShapeInstance::render( const TSRenderState &rdata, S32 dl, F32 intraDL )
|
|
{
|
|
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;
|
|
|
|
// if we're a billboard detail, draw it and exit
|
|
if ( ss < 0 )
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_RenderBillboards );
|
|
|
|
if ( !rdata.isNoRenderTranslucent() && ( TSLastDetail::smCanShadow || !rdata.getSceneState()->isShadowPass() ) )
|
|
mShape->billboardDetails[ dl ]->render( rdata, mAlphaAlways ? mAlphaAlwaysValue : 1.0f );
|
|
|
|
return;
|
|
}
|
|
|
|
S32 start = rdata.isNoRenderNonTranslucent() ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
|
|
S32 end = rdata.isNoRenderTranslucent() ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
|
|
TSVertexBufferHandle *realBuffer;
|
|
|
|
if (TSShape::smUseHardwareSkinning && !mUseOwnBuffer)
|
|
{
|
|
// For hardware skinning, just using the buffer associated with the shape will work fine
|
|
realBuffer = &mShape->mShapeVertexBuffer;
|
|
}
|
|
else
|
|
{
|
|
// For software skinning, we need to update our own buffer each frame
|
|
realBuffer = &mSoftwareVertexBuffer;
|
|
if (realBuffer->getPointer() == NULL)
|
|
{
|
|
mShape->getVertexBuffer(*realBuffer, GFXBufferTypeDynamic);
|
|
}
|
|
|
|
if (bufferNeedsUpdate(od, start, end))
|
|
{
|
|
U8 *buffer = realBuffer->lock();
|
|
if (!buffer)
|
|
return;
|
|
|
|
// Base vertex data
|
|
dMemcpy(buffer, mShape->mShapeVertexData.base, mShape->mShapeVertexData.size);
|
|
|
|
// Apply skinned verts (where applicable)
|
|
for (i = start; i < end; i++)
|
|
{
|
|
mMeshObjects[i].updateVertexBuffer(od, buffer);
|
|
}
|
|
|
|
realBuffer->unlock();
|
|
}
|
|
}
|
|
|
|
// run through the meshes
|
|
for (i=start; i<end; i++)
|
|
{
|
|
TSRenderState objState = rdata;
|
|
// following line is handy for debugging, to see what part of the shape that it is rendering
|
|
const char *name = mShape->names[ mMeshObjects[i].object->nameIndex ];
|
|
mMeshObjects[i].render( od, *realBuffer, mMaterialList, objState, mAlphaAlways ? mAlphaAlwaysValue : 1.0f, name );
|
|
}
|
|
}
|
|
|
|
bool TSShapeInstance::bufferNeedsUpdate(S32 objectDetail, S32 start, S32 end)
|
|
{
|
|
// run through the meshes
|
|
for (U32 i = start; i<end; i++)
|
|
{
|
|
if (mMeshObjects[i].bufferNeedsUpdate(objectDetail))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TSShapeInstance::setCurrentDetail( S32 dl, F32 intraDL )
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_setCurrentDetail );
|
|
|
|
mCurrentDetailLevel = mClamp( dl, -1, mShape->mSmallestVisibleDL );
|
|
mCurrentIntraDetailLevel = intraDL > 1.0f ? 1.0f : (intraDL < 0.0f ? 0.0f : intraDL);
|
|
|
|
// Restrict the chosen detail level by cutoff value.
|
|
if ( smNumSkipRenderDetails > 0 && mCurrentDetailLevel >= 0 )
|
|
{
|
|
S32 cutoff = getMin( smNumSkipRenderDetails, mShape->mSmallestVisibleDL );
|
|
if ( mCurrentDetailLevel < cutoff )
|
|
{
|
|
mCurrentDetailLevel = cutoff;
|
|
mCurrentIntraDetailLevel = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
S32 TSShapeInstance::setDetailFromPosAndScale( const SceneRenderState *state,
|
|
const Point3F &pos,
|
|
const Point3F &scale )
|
|
{
|
|
VectorF camVector = pos - state->getDiffuseCameraPosition();
|
|
F32 dist = getMax( camVector.len(), 0.01f );
|
|
F32 invScale = ( 1.0f / getMax( getMax( scale.x, scale.y ), scale.z ) );
|
|
|
|
return setDetailFromDistance( state, dist * invScale );
|
|
}
|
|
|
|
S32 TSShapeInstance::setDetailFromDistance( const SceneRenderState *state, F32 scaledDistance )
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_setDetailFromDistance );
|
|
|
|
// For debugging/metrics.
|
|
smLastScaledDistance = scaledDistance;
|
|
|
|
// Shortcut if the distance is really close or negative.
|
|
if ( scaledDistance <= 0.0f )
|
|
{
|
|
mShape->mDetailLevelLookup[0].get( mCurrentDetailLevel, mCurrentIntraDetailLevel );
|
|
return mCurrentDetailLevel;
|
|
}
|
|
|
|
// The pixel scale is used the linearly scale the lod
|
|
// selection based on the viewport size.
|
|
//
|
|
// The original calculation from TGEA was...
|
|
//
|
|
// pixelScale = viewport.extent.x * 1.6f / 640.0f;
|
|
//
|
|
// Since we now work on the viewport height, assuming
|
|
// 4:3 aspect ratio, we've changed the reference value
|
|
// to 300 to be more compatible with legacy shapes.
|
|
//
|
|
const F32 pixelScale = (state->getViewport().extent.x / state->getViewport().extent.y)*2;
|
|
|
|
// This is legacy DTS support for older "multires" based
|
|
// meshes. The original crossbow weapon uses this.
|
|
//
|
|
// If we have more than one detail level and the maxError
|
|
// is non-negative then we do some sort of screen error
|
|
// metric for detail selection.
|
|
//
|
|
if ( mShape->mUseDetailFromScreenError )
|
|
{
|
|
// The pixel size of 1 meter at the input distance.
|
|
F32 pixelRadius = state->projectRadius( scaledDistance, 1.0f ) * pixelScale;
|
|
static const F32 smScreenError = 5.0f;
|
|
return setDetailFromScreenError( smScreenError / pixelRadius );
|
|
}
|
|
|
|
// We're inlining SceneRenderState::projectRadius here to
|
|
// skip the unnessasary divide by zero protection.
|
|
F32 pixelRadius = ( mShape->mRadius / scaledDistance ) * state->getWorldToScreenScale().y * pixelScale;
|
|
F32 pixelSize = pixelRadius * smDetailAdjust;
|
|
|
|
if ( pixelSize < smSmallestVisiblePixelSize ) {
|
|
mCurrentDetailLevel = -1;
|
|
return mCurrentDetailLevel;
|
|
}
|
|
|
|
if ( pixelSize > smSmallestVisiblePixelSize &&
|
|
pixelSize <= mShape->mSmallestVisibleSize )
|
|
pixelSize = mShape->mSmallestVisibleSize + 0.01f;
|
|
|
|
// For debugging/metrics.
|
|
smLastPixelSize = pixelSize;
|
|
|
|
// Clamp it to an acceptable range for the lookup table.
|
|
U32 index = (U32)mClampF( pixelSize, 0, mShape->mDetailLevelLookup.size() - 1 );
|
|
|
|
// Check the lookup table for the detail and intra detail levels.
|
|
mShape->mDetailLevelLookup[ index ].get( mCurrentDetailLevel, mCurrentIntraDetailLevel );
|
|
|
|
// Restrict the chosen detail level by cutoff value.
|
|
if ( smNumSkipRenderDetails > 0 && mCurrentDetailLevel >= 0 )
|
|
{
|
|
S32 cutoff = getMin( smNumSkipRenderDetails, mShape->mSmallestVisibleDL );
|
|
if ( mCurrentDetailLevel < cutoff )
|
|
{
|
|
mCurrentDetailLevel = cutoff;
|
|
mCurrentIntraDetailLevel = 1.0f;
|
|
}
|
|
}
|
|
|
|
return mCurrentDetailLevel;
|
|
}
|
|
|
|
S32 TSShapeInstance::setDetailFromScreenError( F32 errorTolerance )
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_setDetailFromScreenError );
|
|
|
|
// For debugging/metrics.
|
|
smLastScreenErrorTolerance = errorTolerance;
|
|
|
|
// 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 < errorTolerance )
|
|
{
|
|
// 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 < errorTolerance )
|
|
{
|
|
// 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 - (errorTolerance - err0) / (prevErr - err0);
|
|
return mCurrentDetailLevel;
|
|
}
|
|
prevErr = err0;
|
|
}
|
|
|
|
// get here if we are drawing at DL==0
|
|
mCurrentDetailLevel = 0;
|
|
mCurrentIntraDetailLevel = 1.0f;
|
|
return mCurrentDetailLevel;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Object (MeshObjectInstance & PluginObjectInstance) render methods
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
void TSShapeInstance::ObjectInstance::render( S32, TSVertexBufferHandle &vb, TSMaterialList *, TSRenderState &rdata, F32 alpha, const char *meshName )
|
|
{
|
|
AssertFatal(0,"TSShapeInstance::ObjectInstance::render: no default render method.");
|
|
}
|
|
|
|
void TSShapeInstance::ObjectInstance::updateVertexBuffer( S32 objectDetail, U8 *buffer )
|
|
{
|
|
AssertFatal(0, "TSShapeInstance::ObjectInstance::updateVertexBuffer: no default vertex buffer update method.");
|
|
}
|
|
|
|
bool TSShapeInstance::ObjectInstance::bufferNeedsUpdate( S32 objectDetai )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void TSShapeInstance::MeshObjectInstance::render( S32 objectDetail,
|
|
TSVertexBufferHandle &vb,
|
|
TSMaterialList *materials,
|
|
TSRenderState &rdata,
|
|
F32 alpha,
|
|
const char *meshName )
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_MeshObjectInstance_render );
|
|
|
|
if ( forceHidden || ( ( visible * alpha ) <= 0.01f ) )
|
|
return;
|
|
|
|
TSMesh *mesh = getMesh(objectDetail);
|
|
if ( !mesh )
|
|
return;
|
|
|
|
const MatrixF &transform = getTransform();
|
|
|
|
if ( rdata.getCuller() )
|
|
{
|
|
Box3F box( mesh->getBounds() );
|
|
transform.mul( box );
|
|
if ( rdata.getCuller()->isCulled( box ) )
|
|
return;
|
|
}
|
|
|
|
GFX->pushWorldMatrix();
|
|
GFX->multWorld( transform );
|
|
|
|
mesh->setFade( visible * alpha );
|
|
|
|
// Pass a hint to the mesh that time has advanced and that the
|
|
// skin is dirty and needs to be updated. This should result
|
|
// in the skin only updating once per frame in most cases.
|
|
const U32 currTime = Sim::getCurrentTime();
|
|
bool isSkinDirty = (currTime != mLastTime) || (objectDetail != mLastObjectDetail);
|
|
|
|
// Update active transform list for bones for GPU skinning
|
|
if ( mesh->getMeshType() == TSMesh::SkinMeshType )
|
|
{
|
|
if (isSkinDirty)
|
|
{
|
|
static_cast<TSSkinMesh*>(mesh)->updateSkinBones(*mTransforms, mActiveTransforms);
|
|
}
|
|
rdata.setNodeTransforms(mActiveTransforms.address(), mActiveTransforms.size());
|
|
}
|
|
|
|
mesh->render( materials,
|
|
rdata,
|
|
isSkinDirty,
|
|
*mTransforms,
|
|
vb,
|
|
meshName );
|
|
|
|
// Update the last render time.
|
|
mLastTime = currTime;
|
|
mLastObjectDetail = objectDetail;
|
|
GFX->popWorldMatrix();
|
|
}
|
|
|
|
void TSShapeInstance::MeshObjectInstance::updateVertexBuffer(S32 objectDetail, U8 *buffer)
|
|
{
|
|
PROFILE_SCOPE(TSShapeInstance_MeshObjectInstance_updateVertexBuffer);
|
|
|
|
if (forceHidden || ((visible) <= 0.01f))
|
|
return;
|
|
|
|
TSMesh *mesh = getMesh(objectDetail);
|
|
if (!mesh)
|
|
return;
|
|
|
|
// Update the buffer here
|
|
if (mesh->getMeshType() == TSMesh::SkinMeshType)
|
|
{
|
|
static_cast<TSSkinMesh*>(mesh)->updateSkinBuffer(*mTransforms, buffer);
|
|
}
|
|
|
|
mLastTime = Sim::getCurrentTime();
|
|
}
|
|
|
|
bool TSShapeInstance::MeshObjectInstance::bufferNeedsUpdate( S32 objectDetail )
|
|
{
|
|
TSMesh *mesh = getMesh(objectDetail);
|
|
const U32 currTime = Sim::getCurrentTime();
|
|
return mesh && mesh->getMeshType() == TSMesh::SkinMeshType && currTime != mLastTime;
|
|
}
|
|
|
|
TSShapeInstance::MeshObjectInstance::MeshObjectInstance()
|
|
: meshList(0), object(0), frame(0), matFrame(0),
|
|
visible(1.0f), forceHidden(false), mLastTime(0), mLastObjectDetail(0)
|
|
{
|
|
}
|
|
|
|
void TSShapeInstance::prepCollision()
|
|
{
|
|
PROFILE_SCOPE( TSShapeInstance_PrepCollision );
|
|
|
|
// Iterate over all our meshes and call prepCollision on them...
|
|
for(S32 i=0; i<mShape->meshes.size(); i++)
|
|
{
|
|
if(mShape->meshes[i])
|
|
mShape->meshes[i]->prepOpcodeCollision();
|
|
}
|
|
}
|
|
|
|
// Returns true is the shape contains any materials with accumulation enabled.
|
|
bool TSShapeInstance::hasAccumulation()
|
|
{
|
|
bool result = false;
|
|
for ( U32 i = 0; i < mMaterialList->size(); ++i )
|
|
{
|
|
BaseMatInstance* mat = mMaterialList->getMaterialInst(i);
|
|
if ( mat->hasAccumulation() )
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void TSShapeInstance::setUseOwnBuffer()
|
|
{
|
|
mUseOwnBuffer = true;
|
|
}
|