// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/mesh_multi_lod.h" #include "nel/3d/mesh_multi_lod_instance.h" #include "nel/3d/mesh_instance.h" #include "nel/3d/mesh_mrm.h" #include "nel/3d/scene.h" #include "nel/3d/coarse_mesh_manager.h" #include "nel/3d/skeleton_model.h" #include "nel/misc/fast_floor.h" #include "nel/3d/mesh_blender.h" #include "nel/3d/visual_collision_mesh.h" #include "nel/misc/debug.h" #include "nel/misc/hierarchical_timer.h" using namespace NLMISC; using namespace std; namespace NL3D { // *************************************************************************** void CMeshMultiLod::build(CMeshMultiLodBuild &mbuild) { // Clear the mesh clear (); // Build the base mesh CMeshBase::buildMeshBase (mbuild.BaseMesh); // Static flag _StaticLod=mbuild.StaticLod; // Resize the array _MeshVector.resize (mbuild.LodMeshes.size()); // For each slots for (uint slot=0; slot(mbuild.LodMeshes[slot].MeshGeom)==NULL) { // If it is a coarse mesh, it must be a CMeshGeom. _MeshVector[slot].MeshGeom = NULL; delete mbuild.LodMeshes[slot].MeshGeom; } else // Ok, no prb _MeshVector[slot].MeshGeom = mbuild.LodMeshes[slot].MeshGeom; } else // Ok, no prb _MeshVector[slot].MeshGeom = mbuild.LodMeshes[slot].MeshGeom; } // Sort the slot by the distance... for (int i=(uint)mbuild.LodMeshes.size()-1; i>0; i--) for (int j=0; j_MeshVector[j+1].DistMax) { // Exchange slots CMeshSlot tmp=_MeshVector[j]; _MeshVector[j]=_MeshVector[j+1]; _MeshVector[j+1]=tmp; tmp.MeshGeom=NULL; } } // Calc start and end polygon count for (uint k=0; kgetNumTriangles (startDist); // Get end distance float endDist=_MeshVector[k].DistMax; // Get end poly count if (k==mbuild.LodMeshes.size()-1) { _MeshVector[k].EndPolygonCount=_MeshVector[k].MeshGeom->getNumTriangles (endDist); if (startPolyCount==_MeshVector[k].EndPolygonCount) _MeshVector[k].EndPolygonCount=startPolyCount/2; } else _MeshVector[k].EndPolygonCount=_MeshVector[k+1].MeshGeom->getNumTriangles (endDist); // Calc A if (endDist==startDist) _MeshVector[k].A=0; else _MeshVector[k].A=(_MeshVector[k].EndPolygonCount-startPolyCount)/(endDist-startDist); // Calc A _MeshVector[k].B=_MeshVector[k].EndPolygonCount-_MeshVector[k].A*endDist; } // End: compile some stuff compileRunTime(); } // *************************************************************************** CTransformShape *CMeshMultiLod::createInstance(CScene &scene) { // Create a CMeshInstance, an instance of a multi lod mesh. CMeshMultiLodInstance *mi=(CMeshMultiLodInstance*)scene.createModel(NL3D::MeshMultiLodInstanceId); mi->Shape= this; mi->_LastLodMatrixDate=0; // instanciate the material part of the Mesh, ie the CMeshBase. CMeshBase::instanciateMeshBase(mi, &scene); // Create the necessary space for Coarse Instanciation instanciateCoarseMeshSpace(mi); // For all lods, do some instance init for MeshGeom for(uint i=0; i<_MeshVector.size(); i++) { if(_MeshVector[i].MeshGeom) _MeshVector[i].MeshGeom->initInstance(mi); } // init the Filter type mi->initRenderFilterType(); return mi; } // *************************************************************************** bool CMeshMultiLod::clip(const std::vector &pyramid, const CMatrix &worldMatrix) { // Look for the biggest mesh uint meshCount=(uint)_MeshVector.size(); for (uint i=0; iclip (pyramid, worldMatrix); } } return true; } // *************************************************************************** void CMeshMultiLod::render(IDriver *drv, CTransformShape *trans, bool passOpaque) { // Render good meshes CMeshMultiLodInstance *instance=safe_cast(trans); // Static or dynamic coarse mesh ? CCoarseMeshManager *manager; // Get the coarse mesh manager manager=instance->getOwnerScene()->getCoarseMeshManager(); // *** Render Lods // Second lod ? if ( (instance->Lod1!=0xffffffff) && (passOpaque==false) ) { // build rdrFlags to rdr both transparent and opaque materials, // use globalAlphaBlend, and disable ZWrite for Lod1 uint32 rdrFlags= IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderTransparentMaterial | IMeshGeom::RenderGlobalAlpha | IMeshGeom::RenderGADisableZWrite; // NB: very important to render Lod1 first, because Lod0 is still rendered with ZWrite enabled. renderMeshGeom (instance->Lod1, drv, instance, instance->PolygonCountLod1, rdrFlags, 1.f-instance->BlendFactor, manager); } // Have an opaque pass ? if ( (instance->Flags&CMeshMultiLodInstance::Lod0Blend) == 0) { // Is this slot a CoarseMesh? if ( _MeshVector[instance->Lod0].Flags&CMeshSlot::CoarseMesh ) { // render as a CoarseMesh the lod 0, only in opaque pass if(passOpaque) renderCoarseMesh (instance->Lod0, drv, instance, manager); } else { // build rdrFlags the normal way (as CMesh::render() for example) uint32 mask= (0-(uint32)passOpaque); uint32 rdrFlags; // select rdrFlags, without ifs. rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque); rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial); // Only render the normal way the first lod renderMeshGeom (instance->Lod0, drv, instance, instance->PolygonCountLod0, rdrFlags, 1, manager); } } else { // Should not be in opaque nlassert (passOpaque==false); // build rdrFlags to rdr both transparent and opaque materials, // use globalAlphaBlend, BUT Don't disable ZWrite for Lod0 uint32 rdrFlags= IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderTransparentMaterial | IMeshGeom::RenderGlobalAlpha; // Render first lod in blend mode. Don't disable ZWrite for Lod0 renderMeshGeom (instance->Lod0, drv, instance, instance->PolygonCountLod0, rdrFlags, instance->BlendFactor, manager); } } // *************************************************************************** void CMeshMultiLod::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ // Serial a version number (void)f.serialVersion (0); // serial Materials infos contained in CMeshBase. CMeshBase::serialMeshBase(f); // Static lod flag f.serial (_StaticLod); // Serial the values f.serialCont (_MeshVector); // if reading, compile some stuff if (f.isReading()) compileRunTime(); } // *************************************************************************** float CMeshMultiLod::getNumTrianglesWithCoarsestDist(float distance, float coarsestMeshDist) const { // Look in the table for good distances.. uint meshCount=(uint)_MeshVector.size(); // At least on mesh if (meshCount>0) { if (coarsestMeshDist != -1) { if (coarsestMeshDist != 0) { // rescale distance to new coarse mesh distance.. distance *= _MeshVector[meshCount - 1].DistMax / coarsestMeshDist; } } uint i=0; // Look for good i while ( _MeshVector[i].DistMax < distance) { if (i==meshCount-1) // Abort if last one break; i++; } // Ref on slot const CMeshSlot &slot=_MeshVector[i]; // Is mesh present ? if (slot.MeshGeom) { // Get the polygon count with the distance float polyCount=slot.A * distance + slot.B; /*// Get the perfect polygon count in this slot for the asked distance float goodPolyCount=slot.MeshGeom->getNumTriangles (distance); // Get the next slot perfect polygon count float realEndPolyCount; // Last slot ? if ( (igetNumTriangles (slot.DistMax); else // Take end number polygon count in the this slot realEndPolyCount=slot.EndPolygonCount; // Return blended polygon count to have a continous decreasing function return (goodPolyCount-slot.BeginPolygonCount) * (realEndPolyCount-slot.BeginPolygonCount) / (slot.EndPolygonCount-slot.BeginPolygonCount) + slot.BeginPolygonCount;*/ return polyCount; } } return 0; } // *************************************************************************** void CMeshMultiLod::getAABBox(NLMISC::CAABBox &bbox) const { // Get count uint count=(uint)_MeshVector.size(); for (uint slot=0; slotgetBoundingBox().getAABBox(); // ok break; } } } // *************************************************************************** void CMeshMultiLod::clear () { _MeshVector.clear (); } // *************************************************************************** void CMeshMultiLod::CMeshSlot::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { // Check version (void)f.serialVersion (0); f.serialPolyPtr (MeshGeom); f.serial (A); f.serial (B); f.serial (DistMax); f.serial (EndPolygonCount); f.serial (BlendLength); f.serial (Flags); if (f.isReading()) { } } // *************************************************************************** CMeshMultiLod::CMeshSlot::CMeshSlot () { MeshGeom=NULL; CoarseNumTris= 0; } // *************************************************************************** CMeshMultiLod::CMeshSlot::~CMeshSlot () { if (MeshGeom) delete MeshGeom; } // *************************************************************************** void CMeshMultiLod::renderMeshGeom (uint slot, IDriver *drv, CMeshMultiLodInstance *trans, float numPoylgons, uint32 rdrFlags, float alpha, CCoarseMeshManager *manager) { // Ref CMeshSlot &slotRef=_MeshVector[slot]; // MeshGeom exist? if (slotRef.MeshGeom) { // NB Here, the meshGeom may still be a coarseMesh, but rendered through CMeshGeom if(slotRef.Flags&CMeshSlot::CoarseMesh) { // Render only for opaque material if(manager && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) { bool gaDisableZWrite= (rdrFlags & IMeshGeom::RenderGADisableZWrite)?true:false; // Render the CoarseMesh with the manager material CMaterial &material= manager->getMaterial(); // modulate material for alphaBlend transition // ---------- // get average sun color for this coarseMesh CRGBA newCol= trans->getCoarseMeshLighting(); // Use a CMeshBlender to modify material and driver. CMeshBlender blender; blender.prepareRenderForGlobalAlphaCoarseMesh(material, drv, newCol, alpha, gaDisableZWrite); // render simple the coarseMesh CMeshGeom *meshGeom= safe_cast(slotRef.MeshGeom); // Force corse mesh vertex buffer in system memory const_cast(meshGeom->getVertexBuffer ()).setPreferredMemory (CVertexBuffer::RAMPreferred, false); meshGeom->renderSimpleWithMaterial(drv, trans->getWorldMatrix(), material); // resetup standard CoarseMeshMaterial material values // ---------- // blender restore blender.restoreRenderCoarseMesh(material, drv, gaDisableZWrite); } } else { // Render the geom mesh // Disable ZWrite only if in transition and for rendering Lod1 slotRef.MeshGeom->render (drv, trans, numPoylgons, rdrFlags, alpha); } } } // *************************************************************************** void CMeshMultiLod::renderCoarseMesh (uint slot, IDriver *drv, CMeshMultiLodInstance *trans, CCoarseMeshManager *manager) { // if the manager is NULL, quit. if(manager==NULL) return; // get the scene CScene *scene= trans->getOwnerScene(); if(!scene) return; // If filtered... if( (scene->getFilterRenderFlags() & UScene::FilterCoarseMesh)==0 ) return; // Ref CMeshSlot &slotRef=_MeshVector[slot]; // the slot must be a Coarse mesh nlassert(slotRef.Flags&CMeshSlot::CoarseMesh); // Get a pointer on the geom mesh CMeshGeom *meshGeom= safe_cast(slotRef.MeshGeom); // ** If not the same as Before (or if NULL before...) if ( trans->_LastCoarseMesh!=meshGeom ) { uint numVerts= meshGeom->getVertexBuffer().getNumVertices(); uint numTris= slotRef.CoarseNumTris; // If empty meshGeom, erase cache (each frame, ugly but error mgt here...) if( numTris==0 || numVerts==0 ) trans->_LastCoarseMesh= NULL; else { // Cache trans->_LastCoarseMesh= meshGeom; trans->_LastCoarseMeshNumVertices= numVerts; // Check setuped size. nlassert( trans->_CoarseMeshVB.size() >= numVerts*manager->getVertexSize() ); // Fill only UVs here. (Pos updated in Matrix pass. Color in Lighting Pass) trans->setUVCoarseMesh( *meshGeom, manager->getVertexSize(), manager->getUVOff() ); // Dirt the matrix trans->_LastLodMatrixDate=0; // Dirt the lighting. NB: period maximum is 255. Hence the -256, to ensure lighting compute now trans->_LastLodLightingDate= -0x100; } } // ** If setuped, update and render if( trans->_LastCoarseMesh ) { // Matrix has changed ? if ( trans->ITransformable::compareMatrixDate (trans->_LastLodMatrixDate) ) { // Get date trans->_LastLodMatrixDate = trans->ITransformable::getMatrixDate(); // Set matrix trans->setPosCoarseMesh ( *meshGeom, trans->getMatrix(), manager->getVertexSize() ); } // Lighting: test if must update lighting, according to date of HrcTrav (num of CScene::render() call). sint64 currentDate= scene->getHrcTrav().CurrentDate; if( trans->_LastLodLightingDate < currentDate - scene->getCoarseMeshLightingUpdate() ) { // reset the date. trans->_LastLodLightingDate= currentDate; // get average sun color CRGBA sunContrib= trans->getCoarseMeshLighting(); // Invert BR if driver is BGRA if(drv->getVertexColorFormat()==CVertexBuffer::TBGRA) sunContrib.swapBR(); // Set color trans->setColorCoarseMesh ( sunContrib, manager->getVertexSize(), manager->getColorOff()); } // Add dynamic to the manager if( !manager->addMesh(trans->_LastCoarseMeshNumVertices, &trans->_CoarseMeshVB[0], slotRef.CoarseNumTris, &slotRef.CoarseTriangles[0] ) ) { // If failure, flush the manager manager->flushRender(drv); // then try to re-add. No-op if fails this time.. manager->addMesh(trans->_LastCoarseMeshNumVertices, &trans->_CoarseMeshVB[0], slotRef.CoarseNumTris, &slotRef.CoarseTriangles[0] ); } } } // *************************************************************************** void CMeshMultiLod::compileDistMax() { // Last element if(_MeshVector.empty()) IShape::_DistMax= -1; else IShape::_DistMax= _MeshVector.back().DistMax; } // *************************************************************************** const IMeshGeom& CMeshMultiLod::getMeshGeom (uint slot) const { // Checks nlassert (slot(_MeshVector[0].MeshGeom); if(mgeom==NULL) return; // ok, setup. mgeom->changeMRMDistanceSetup(distanceFinest, distanceMiddle, distanceCoarsest); } // *************************************************************************** IMeshGeom *CMeshMultiLod::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const { IMeshGeom *ret= NULL; // get the instance CMeshMultiLodInstance *instance=safe_cast(trans); // Must not be in blend transition. if ( (instance->Flags&CMeshMultiLodInstance::Lod0Blend) == 0) { uint slot= instance->Lod0; // The slot must not be a CoarseMesh if ( (_MeshVector[slot].Flags&CMeshSlot::CoarseMesh)==0 ) { // MeshGeom exist? ret= _MeshVector[slot].MeshGeom; } } // Ok if meshGeom is ok. if( ret && ret->supportMeshBlockRendering() ) { polygonCount= instance->PolygonCountLod0; return ret; } else return NULL; } // *************************************************************************** void CMeshMultiLod::profileMeshGeom (uint slot, CRenderTrav *rdrTrav, CMeshMultiLodInstance *trans, float numPoylgons, uint32 rdrFlags) { // Ref CMeshSlot &slotRef=_MeshVector[slot]; // MeshGeom exist? if (slotRef.MeshGeom) { // NB Here, the meshGeom may still be a coarseMesh, but rendered through CMeshGeom if(slotRef.Flags&CMeshSlot::CoarseMesh) { // Render only for opaque material if(rdrFlags & IMeshGeom::RenderOpaqueMaterial) { slotRef.MeshGeom->profileSceneRender(rdrTrav, trans, numPoylgons, rdrFlags); } } else { slotRef.MeshGeom->profileSceneRender(rdrTrav, trans, numPoylgons, rdrFlags); } } } // *************************************************************************** void CMeshMultiLod::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, bool passOpaque) { // Render good meshes CMeshMultiLodInstance *instance=safe_cast(trans); // Second lod ? if ( (instance->Lod1!=0xffffffff) && (passOpaque==false) ) { // build rdrFlags to rdr both transparent and opaque materials, // use globalAlphaBlend, and disable ZWrite for Lod1 uint32 rdrFlags= IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderTransparentMaterial | IMeshGeom::RenderGlobalAlpha | IMeshGeom::RenderGADisableZWrite; // NB: very important to render Lod1 first, because Lod0 is still rendered with ZWrite enabled. profileMeshGeom (instance->Lod1, rdrTrav, instance, instance->PolygonCountLod1, rdrFlags); } // Have an opaque pass ? if ( (instance->Flags&CMeshMultiLodInstance::Lod0Blend) == 0) { // Is this slot a CoarseMesh? if ( _MeshVector[instance->Lod0].Flags&CMeshSlot::CoarseMesh ) { } else { // build rdrFlags the normal way (as CMesh::render() for example) uint32 mask= (0-(uint32)passOpaque); uint32 rdrFlags; // select rdrFlags, without ifs. rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque); rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial); // Only render the normal way the first lod profileMeshGeom (instance->Lod0, rdrTrav, instance, instance->PolygonCountLod0, rdrFlags); } } else { // Should not be in opaque nlassert (passOpaque==false); // build rdrFlags to rdr both transparent and opaque materials, // use globalAlphaBlend, BUT Don't disable ZWrite for Lod0 uint32 rdrFlags= IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderTransparentMaterial | IMeshGeom::RenderGlobalAlpha; // Render first lod in blend mode. Don't disable ZWrite for Lod0 profileMeshGeom (instance->Lod0, rdrTrav, instance, instance->PolygonCountLod0, rdrFlags); } } // *************************************************************************** void CMeshMultiLod::instanciateCoarseMeshSpace(CMeshMultiLodInstance *mi) { CCoarseMeshManager *manager= mi->getOwnerScene()->getCoarseMeshManager(); if(manager) { // For all MeshSlots that have a CoarseMesh, count max Coarse NumVertices; uint numVertices= 0; for(uint i=0;i<_MeshVector.size();i++) { CMeshSlot &slotRef= _MeshVector[i]; if( slotRef.Flags & CMeshSlot::CoarseMesh ) { // Get a pointer on the geom mesh CMeshGeom *meshGeom= safe_cast(slotRef.MeshGeom); numVertices= max(numVertices, (uint)meshGeom->getVertexBuffer().getNumVertices() ); } } // Then allocate vertex space for dest manager vertex size. mi->_CoarseMeshVB.resize( numVertices*manager->getVertexSize() ); } } // *************************************************************************** void CMeshMultiLod::compileCoarseMeshes() { // For All Slots that are CoarseMeshes. for(uint i=0;i<_MeshVector.size();i++) { CMeshSlot &slotRef= _MeshVector[i]; if( slotRef.Flags & CMeshSlot::CoarseMesh ) { // reset slotRef.CoarseNumTris= 0; // Get a pointer on the geom mesh CMeshGeom *meshGeom= safe_cast(slotRef.MeshGeom); // For All RdrPass of the 1st matrix block if( meshGeom->getNbMatrixBlock()>0 ) { // 1st count for(uint i=0;igetNbRdrPass(0);i++) { slotRef.CoarseNumTris+= meshGeom->getRdrPassPrimitiveBlock(0, i).getNumIndexes()/3; } // 2nd allocate and fill if( slotRef.CoarseNumTris ) { slotRef.CoarseTriangles.resize(slotRef.CoarseNumTris * 3); TCoarseMeshIndexType *dstPtr= &slotRef.CoarseTriangles[0]; for(uint i=0;igetNbRdrPass(0);i++) { const CIndexBuffer &pb= meshGeom->getRdrPassPrimitiveBlock(0, i); CIndexBufferRead ibaRead; pb.lock (ibaRead); uint numTris= pb.getNumIndexes()/3; if (pb.getFormat() == CIndexBuffer::Indices16) { if (sizeof(TCoarseMeshIndexType) == sizeof(uint16)) { memcpy(dstPtr, (uint16 *) ibaRead.getPtr(), numTris*3*sizeof(uint16)); } else { // 16 -> 32 uint16 *src = (uint16 *) ibaRead.getPtr(); for(uint k = 0; k < numTris; ++k) { *dstPtr++ = (TCoarseMeshIndexType) *src++; *dstPtr++ = (TCoarseMeshIndexType) *src++; *dstPtr++ = (TCoarseMeshIndexType) *src++; } } } else { if (sizeof(TCoarseMeshIndexType) == sizeof(uint32)) { memcpy(dstPtr, (uint32 *) ibaRead.getPtr(), numTris*3*sizeof(uint32)); } else { const uint32 *src = (const uint32 *) ibaRead.getPtr(); for(uint k = 0; k < numTris; ++k) { // 32 -> 16 nlassert(src[0] <= 0xffff); nlassert(src[1] <= 0xffff); nlassert(src[2] <= 0xffff); *dstPtr++ = (TCoarseMeshIndexType) *src++; *dstPtr++ = (TCoarseMeshIndexType) *src++; *dstPtr++ = (TCoarseMeshIndexType) *src++; } } } dstPtr+= numTris*3; } } } } } } // *************************************************************************** void CMeshMultiLod::compileRunTime() { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ // **** MultiLod basics compileDistMax(); compileCoarseMeshes(); // **** try to build a Visual Collision Mesh // clear first if(_VisualCollisionMesh) { delete _VisualCollisionMesh; _VisualCollisionMesh= NULL; } // build only if wanted if( (_CollisionMeshGeneration==AutoCameraCol && !_LightInfos.empty()) || _CollisionMeshGeneration==ForceCameraCol ) { // try to retrieve the info from a CMeshGeom only if(getNumSlotMesh()) { const CMeshGeom *meshGeom= dynamic_cast(&getMeshGeom(0)); if(meshGeom) { vector vertices; vector indices; if(meshGeom->retrieveVertices(vertices) && meshGeom->retrieveTriangles(indices)) { // ok, can build! _VisualCollisionMesh= new CVisualCollisionMesh; // if fails to build cause of too many vertices/indices for instance if(!_VisualCollisionMesh->build(vertices, indices,const_cast(meshGeom->getVertexBuffer()))) { // delete delete _VisualCollisionMesh; _VisualCollisionMesh= NULL; } } } } } } // *************************************************************************** void CMeshMultiLod::buildSystemGeometry() { // clear any _SystemGeometry.clear(); // Use the first lod, for system geometry copy if(getNumSlotMesh()) { // the first is a meshGeom? const CMeshGeom *meshGeom= dynamic_cast(&getMeshGeom(0)); if(meshGeom) { // retrieve geometry (if VB/IB not resident) if( !meshGeom->retrieveVertices(_SystemGeometry.Vertices) || !meshGeom->retrieveTriangles(_SystemGeometry.Triangles)) { _SystemGeometry.clear(); } } // else it is a mrm geom? else { const CMeshMRMGeom *meshMRMGeom= dynamic_cast(&getMeshGeom(0)); if(meshMRMGeom) { // Choose the best Lod available for system geometry if(meshMRMGeom->getNbLodLoaded()==0) return; uint lodId= meshMRMGeom->getNbLodLoaded()-1; // retrieve geometry (if VB/IB not resident) if( !meshMRMGeom->buildGeometryForLod(lodId, _SystemGeometry.Vertices, _SystemGeometry.Triangles) ) { _SystemGeometry.clear(); } } } } // TestYoyo /*static uint32 totalMem= 0; totalMem+= _SystemGeometry.Vertices.size()*sizeof(CVector); totalMem+= _SystemGeometry.Triangles.size()*sizeof(uint32); nlinfo("CMeshMultiLod: TotalMem: %d", totalMem);*/ } } // NL3D