//----------------------------------------------------------------------------- // 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 TSShapeInstance::smNodeCurrentRotations(__FILE__, __LINE__); Vector TSShapeInstance::smNodeCurrentTranslations(__FILE__, __LINE__); Vector TSShapeInstance::smNodeCurrentUniformScales(__FILE__, __LINE__); Vector TSShapeInstance::smNodeCurrentAlignedScales(__FILE__, __LINE__); Vector TSShapeInstance::smNodeCurrentArbitraryScales(__FILE__, __LINE__); Vector TSShapeInstance::smNodeLocalTransforms(__FILE__, __LINE__); TSIntegerSet TSShapeInstance::smNodeLocalTransformDirty; Vector TSShapeInstance::smRotationThreads(__FILE__, __LINE__); Vector TSShapeInstance::smTranslationThreads(__FILE__, __LINE__); Vector TSShapeInstance::smScaleThreads(__FILE__, __LINE__); //------------------------------------------------------------------------------------- // constructors, destructors, initialization //------------------------------------------------------------------------------------- TSShapeInstance::TSShapeInstance( const Resource &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; iobjects[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 &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::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; inames[ 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; imSmallestVisibleDL ); 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(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(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; imeshes.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; }