// 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.h" #include "nel/3d/mesh_instance.h" #include "nel/3d/scene.h" #include "nel/3d/skeleton_model.h" #include "nel/3d/mesh_morpher.h" #include "nel/misc/bsphere.h" #include "nel/3d/stripifier.h" #include "nel/misc/fast_floor.h" #include "nel/misc/hierarchical_timer.h" #include "nel/3d/mesh_blender.h" #include "nel/3d/matrix_3x4.h" #include "nel/3d/render_trav.h" #include "nel/3d/visual_collision_mesh.h" #include "nel/3d/meshvp_wind_tree.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** // *************************************************************************** // MeshGeom Tools. // *************************************************************************** // *************************************************************************** // *************************************************************************** static NLMISC::CAABBoxExt makeBBox(const std::vector &Vertices) { NLMISC::CAABBox ret; nlassert(Vertices.size()); ret.setCenter(Vertices[0]); for(sint i=0;i<(sint)Vertices.size();i++) { ret.extend(Vertices[i]); } return ret; } // *************************************************************************** sint CMeshGeom::CCornerTmp::Flags=0; // *************************************************************************** bool CMeshGeom::CCornerTmp::operator<(const CCornerTmp &c) const { sint i; // Vert first. if(Vertex!=c.Vertex) return Vertex0); // Copy the UV routing table for (i=0; i tmpFaces; tmpFaces.resize(m.Faces.size()); for(i=0;i<(sint)tmpFaces.size();i++) tmpFaces[i]= m.Faces[i]; _Skinned= ((m.VertexFlags & CVertexBuffer::PaletteSkinFlag)==CVertexBuffer::PaletteSkinFlag); // Skinning is OK only if SkinWeights are of same size as vertices. _Skinned= _Skinned && (m.Vertices.size()==m.SkinWeights.size()); // If skinning is KO, remove the Skin option. uint vbFlags= m.VertexFlags; if(!_Skinned) vbFlags&= ~CVertexBuffer::PaletteSkinFlag; // Force presence of vertex. vbFlags|= CVertexBuffer::PositionFlag; // If the mesh is not skinned, we have just 1 _MatrixBlocks. if(!_Skinned) { _MatrixBlocks.resize(1); // For each faces, assign it to the matrix block 0. for(i=0;i<(sint)tmpFaces.size();i++) tmpFaces[i].MatrixBlockId= 0; } // Else We must group/compute the matrixs blocks. else { // reset matrix blocks. _MatrixBlocks.clear(); // build matrix blocks, and link faces to good matrix blocks. buildSkin(m, tmpFaces); } /// 2. Then, for all faces, resolve continuities, building VBuffer. //================================================ // Setup VB. _VBuffer.setNumVertices(0); _VBuffer.reserve(0); bool useFormatExt = false; /** If all texture coordinates are of dimension 2, we can setup the flags as before. * If this isn't the case, we must setup a custom format */ for (uint k = 0; k < CVertexBuffer::MaxStage; ++k) { if ( (vbFlags & (CVertexBuffer::TexCoord0Flag << k)) && m.NumCoords[k] != 2) { useFormatExt = true; break; } } if (!useFormatExt) { // setup standard format _VBuffer.setVertexFormat(vbFlags); } else // setup extended format { _VBuffer.clearValueEx(); if (vbFlags & CVertexBuffer::PositionFlag) _VBuffer.addValueEx(CVertexBuffer::Position, CVertexBuffer::Float3); if (vbFlags & CVertexBuffer::NormalFlag) _VBuffer.addValueEx(CVertexBuffer::Normal, CVertexBuffer::Float3); if (vbFlags & CVertexBuffer::PrimaryColorFlag) _VBuffer.addValueEx(CVertexBuffer::PrimaryColor, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::SecondaryColorFlag) _VBuffer.addValueEx(CVertexBuffer::SecondaryColor, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::WeightFlag) _VBuffer.addValueEx(CVertexBuffer::Weight, CVertexBuffer::Float4); if (vbFlags & CVertexBuffer::PaletteSkinFlag) _VBuffer.addValueEx(CVertexBuffer::PaletteSkin, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::FogFlag) _VBuffer.addValueEx(CVertexBuffer::Fog, CVertexBuffer::Float1); for (uint k = 0; k < CVertexBuffer::MaxStage; ++k) { if (vbFlags & (CVertexBuffer::TexCoord0Flag << k)) { switch(m.NumCoords[k]) { case 2: _VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float2); break; case 3: _VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float3); break; default: nlassert(0); break; } } } _VBuffer.initEx(); } // Set local flags for corner comparison. CCornerTmp::Flags= vbFlags; // Setup locals. TCornerSet corners; const CFaceTmp *pFace= &(*tmpFaces.begin()); uint32 nFaceMB = 0; sint N= (sint)tmpFaces.size(); sint currentVBIndex=0; m.VertLink.clear (); // process each face, building up the VB. for(;N>0;N--, pFace++) { sint v0= pFace->Corner[0].Vertex; sint v1= pFace->Corner[1].Vertex; sint v2= pFace->Corner[2].Vertex; findVBId(corners, &pFace->Corner[0], currentVBIndex, m.Vertices[v0], m); findVBId(corners, &pFace->Corner[1], currentVBIndex, m.Vertices[v1], m); findVBId(corners, &pFace->Corner[2], currentVBIndex, m.Vertices[v2], m); CMesh::CVertLink vl1(nFaceMB, 0, pFace->Corner[0].VBId); CMesh::CVertLink vl2(nFaceMB, 1, pFace->Corner[1].VBId); CMesh::CVertLink vl3(nFaceMB, 2, pFace->Corner[2].VBId); m.VertLink.push_back(vl1); m.VertLink.push_back(vl2); m.VertLink.push_back(vl3); ++nFaceMB; } /// 3. build the RdrPass material. //================================ uint mb; // For each _MatrixBlocks, point on those materials. for(mb= 0;mb<_MatrixBlocks.size();mb++) { // Build RdrPass ids. _MatrixBlocks[mb].RdrPass.resize (numMaxMaterial); for(i=0;i<(sint)_MatrixBlocks[mb].RdrPass.size(); i++) { _MatrixBlocks[mb].RdrPass[i].MaterialId= i; // for build, force 32 bit indices _MatrixBlocks[mb].RdrPass[i].PBlock.setFormat(CIndexBuffer::Indices32); } } /// 4. Then, for all faces, build the RdrPass PBlock. //=================================================== pFace= &(*tmpFaces.begin()); N= (sint)tmpFaces.size(); for(;N>0;N--, pFace++) { sint mbId= pFace->MatrixBlockId; nlassert(mbId>=0 && mbId<(sint)_MatrixBlocks.size()); // Insert the face in good MatrixBlock/RdrPass. CIndexBuffer &ib = _MatrixBlocks[mbId].RdrPass[pFace->MaterialId].PBlock; uint index = ib.getNumIndexes(); ib.setNumIndexes (index+3); CIndexBufferReadWrite iba; ib.lock (iba); iba.setTri(index, pFace->Corner[0].VBId, pFace->Corner[1].VBId, pFace->Corner[2].VBId); } /// 5. Remove empty RdrPasses. //============================ for(mb= 0;mb<_MatrixBlocks.size();mb++) { // NB: slow process (erase from a vector). Doens't matter since made at build. vector::iterator itPass; for( itPass=_MatrixBlocks[mb].RdrPass.begin(); itPass!=_MatrixBlocks[mb].RdrPass.end(); ) { // If this pass is empty, remove it. if( itPass->PBlock.getNumIndexes()==0 ) itPass= _MatrixBlocks[mb].RdrPass.erase(itPass); else itPass++; } } /// 6. Misc. //============================ // BShapes this->_MeshMorpher->BlendShapes = m.BlendShapes; // sort triangles for better cache use. optimizeTriangleOrder(); // SmartPtr Copy VertexProgram effect. this->_MeshVertexProgram= m.MeshVertexProgram; /// 7. Compact bones id and build bones name array. //================================================= // If skinned if(_Skinned) { // Reserve some space _BonesName.reserve (m.BonesNames.size ()); // Current local bone uint currentBone = 0; // For each matrix block uint matrixBlock; for (matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++) { // Ref on the matrix block CMatrixBlock &mb = _MatrixBlocks[matrixBlock]; // Remap the skeleton index in model index std::map remap; // For each matrix uint matrix; for (matrix=0; matrix::iterator ite = remap.find (mb.MatrixId[matrix]); // Not found if (ite == remap.end()) { // Insert it remap.insert (std::map::value_type (mb.MatrixId[matrix], currentBone)); // Check the matrix id nlassert (mb.MatrixId[matrix] < m.BonesNames.size()); // Set the bone name _BonesName.push_back (m.BonesNames[mb.MatrixId[matrix]]); // Set the id in local mb.MatrixId[matrix] = currentBone++; } else { // Set the id in local mb.MatrixId[matrix] = ite->second; } } } // Bone id in local _BoneIdComputed = false; _BoneIdExtended = false; } // Set the vertex buffer preferred memory bool avoidVBHard= _Skinned || ( _MeshMorpher && _MeshMorpher->BlendShapes.size()>0 ); _VBuffer.setPreferredMemory (avoidVBHard?CVertexBuffer::RAMPreferred:CVertexBuffer::StaticPreferred, false); // End!! // Some runtime not serialized compilation compileRunTime(); } // *************************************************************************** void CMeshGeom::setBlendShapes(std::vector&bs) { _MeshMorpher->BlendShapes = bs; // must update some RunTime parameters compileRunTime(); } // *************************************************************************** void CMeshGeom::applyMaterialRemap(const std::vector &remap) { for(uint mb=0;mb=0); matId= remap[matId]; } } } // *************************************************************************** void CMeshGeom::initInstance(CMeshBaseInstance *mbi) { // init the instance with _MeshVertexProgram infos if(_MeshVertexProgram) _MeshVertexProgram->initInstance(mbi); } // *************************************************************************** bool CMeshGeom::clip(const std::vector &pyramid, const CMatrix &worldMatrix) { // Speed Clip: clip just the sphere. CBSphere localSphere(_BBox.getCenter(), _BBox.getRadius()); CBSphere worldSphere; // transform the sphere in WorldMatrix (with nearly good scale info). localSphere.applyTransform(worldMatrix, worldSphere); // if out of only plane, entirely out. for(sint i=0;i<(sint)pyramid.size();i++) { // We are sure that pyramid has normalized plane normals. // if SpherMax OUT return false. float d= pyramid[i]*worldSphere.Center; if(d>worldSphere.Radius) return false; } // test if must do a precise clip, according to mesh size. if( _PreciseClipping ) { CPlane localPlane; // if out of only plane, entirely out. for(sint i=0;i<(sint)pyramid.size();i++) { // Transform the pyramid in Object space. localPlane= pyramid[i]*worldMatrix; // localPlane must be normalized, because worldMatrix mya have a scale. localPlane.normalize(); // if the box is not partially inside the plane, quit if( !_BBox.clipBack(localPlane) ) return false; } } return true; } // *************************************************************************** void CMeshGeom::render(IDriver *drv, CTransformShape *trans, float polygonCount, uint32 rdrFlags, float globalAlpha) { nlassert(drv); // get the mesh instance. CMeshBaseInstance *mi= safe_cast(trans); // get a ptr on scene CScene *ownerScene= mi->getOwnerScene(); // get a ptr on renderTrav CRenderTrav *renderTrav= &ownerScene->getRenderTrav(); // Soft vb if not supported by the driver if (drv->slowUnlockVertexBufferHard()) _VBuffer.setPreferredMemory (CVertexBuffer::RAMPreferred, false); // get the skeleton model to which I am binded (else NULL). CSkeletonModel *skeleton; skeleton= mi->getSkeletonModel(); // The mesh must not be skinned for render() nlassert(!(_Skinned && mi->isSkinned() && skeleton)); bool bMorphApplied = _MeshMorpher->BlendShapes.size() > 0; bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace(); // Profiling //=========== H_AUTO( NL3D_MeshGeom_RenderNormal ); // Morphing // ======== if (bMorphApplied) { // If _Skinned (NB: the skin is not applied) and if lod.OriginalSkinRestored, then restoreOriginalSkinPart is // not called but mush morpher write changed vertices into VBHard so its ok. The unchanged vertices // are written in the preceding call to restoreOriginalSkinPart. if (_Skinned) { _MeshMorpher->initSkinned(&_VBufferOri, &_VBuffer, useTangentSpace, &_OriginalSkinVertices, &_OriginalSkinNormals, useTangentSpace ? &_OriginalTGSpace : NULL, false ); _MeshMorpher->updateSkinned (mi->getBlendShapeFactors()); } else // Not even skinned so we have to do all the stuff { _MeshMorpher->init(&_VBufferOri, &_VBuffer, useTangentSpace); _MeshMorpher->update (mi->getBlendShapeFactors()); } } // Skinning // ======== // else setup instance matrix drv->setupModelMatrix(trans->getWorldMatrix()); // since instance skin is invalid but mesh is skinned , we must copy vertices/normals from original vertices. if (_Skinned) { // do it for this Lod only, and if cache say it is necessary. if (!_OriginalSkinRestored) restoreOriginalSkinVertices(); } // Setup meshVertexProgram //=========== // use MeshVertexProgram effect? bool useMeshVP= _MeshVertexProgram != NULL; if( useMeshVP ) { CMatrix invertedObjectMatrix; invertedObjectMatrix = trans->getWorldMatrix().inverted(); // really ok if success to begin VP useMeshVP= _MeshVertexProgram->begin(drv, ownerScene, mi, invertedObjectMatrix, renderTrav->CamPos); if (!useMeshVP && !mi->_VPWindTreeFixed) { if (dynamic_cast(&(*_MeshVertexProgram))) { // fix for mesh tree v.p : all material should be lighted for(uint mb=0;mb<_MatrixBlocks.size();mb++) { CMatrixBlock &mBlock= _MatrixBlocks[mb]; for(uint i=0;iMaterials[mBlock.RdrPass[i].MaterialId]; mat.setLighting(true, mat.getEmissive(), mat.getAmbient(), mat.getDiffuse(), mat.getSpecular()); } } } mi->_VPWindTreeFixed = true; } } // Render the mesh. //=========== // active VB. drv->activeVertexBuffer(_VBuffer); // Global alpha used ? uint32 globalAlphaUsed= rdrFlags & IMeshGeom::RenderGlobalAlpha; uint8 globalAlphaInt=(uint8)NLMISC::OptFastFloor(globalAlpha*255); // For all _MatrixBlocks for(uint mb=0;mb<_MatrixBlocks.size();mb++) { CMatrixBlock &mBlock= _MatrixBlocks[mb]; if(mBlock.RdrPass.size()==0) continue; // Global alpha ? if (globalAlphaUsed) { bool gaDisableZWrite= (rdrFlags & IMeshGeom::RenderGADisableZWrite)?true:false; // Render all pass. for (uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) || ( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) ) { // CMaterial Ref CMaterial &material=mi->Materials[rdrPass.MaterialId]; // Use a MeshBlender to modify material and driver. CMeshBlender blender; blender.prepareRenderForGlobalAlpha(material, drv, globalAlpha, globalAlphaInt, gaDisableZWrite); // Setup VP material if (useMeshVP) { _MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer); } // Render drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); // Resetup material/driver blender.restoreRender(material, drv, gaDisableZWrite); } } } else { // Render all pass. for(uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) || ( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) ) { // CMaterial Ref CMaterial &material=mi->Materials[rdrPass.MaterialId]; // Setup VP material if (useMeshVP) { _MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer); } // render primitives drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } } } } // End VertexProgram effect if(useMeshVP) { // Apply it. _MeshVertexProgram->end(drv); } } // *************************************************************************** void CMeshGeom::renderSkin(CTransformShape *trans, float alphaMRM) { // get the mesh instance. CMeshBaseInstance *mi= safe_cast(trans); // get a ptr on scene CScene *ownerScene= mi->getOwnerScene(); // get a ptr on renderTrav CRenderTrav *renderTrav= &ownerScene->getRenderTrav(); // get a ptr on the driver IDriver *drv= renderTrav->getDriver(); nlassert(drv); // get the skeleton model to which I am binded (else NULL). CSkeletonModel *skeleton; skeleton= mi->getSkeletonModel(); // must be skinned for renderSkin() nlassert(_Skinned && mi->isSkinned() && skeleton); bool bMorphApplied = _MeshMorpher->BlendShapes.size() > 0; bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace(); // Profiling //=========== H_AUTO( NL3D_MeshGeom_RenderSkinned ); // Morphing // ======== if (bMorphApplied) { // Since Skinned we must update original skin vertices and normals because skinning use it _MeshMorpher->initSkinned(&_VBufferOri, &_VBuffer, useTangentSpace, &_OriginalSkinVertices, &_OriginalSkinNormals, useTangentSpace ? &_OriginalTGSpace : NULL, true ); _MeshMorpher->updateSkinned (mi->getBlendShapeFactors()); } // Skinning // ======== // NB: the skeleton matrix has already been setuped by CSkeletonModel // NB: the normalize flag has already been setuped by CSkeletonModel // apply the skinning: _VBuffer is modified. applySkin(skeleton); // Setup meshVertexProgram //=========== // use MeshVertexProgram effect? bool useMeshVP= _MeshVertexProgram != NULL; if( useMeshVP ) { CMatrix invertedObjectMatrix; invertedObjectMatrix = skeleton->getWorldMatrix().inverted(); // really ok if success to begin VP useMeshVP= _MeshVertexProgram->begin(drv, ownerScene, mi, invertedObjectMatrix, renderTrav->CamPos); } // Render the mesh. //=========== // active VB. drv->activeVertexBuffer(_VBuffer); // For all _MatrixBlocks for(uint mb=0;mb<_MatrixBlocks.size();mb++) { CMatrixBlock &mBlock= _MatrixBlocks[mb]; if(mBlock.RdrPass.size()==0) continue; // Render all pass. for(uint i=0;iMaterials[rdrPass.MaterialId]; // Setup VP material if (useMeshVP) { _MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBuffer); } // render primitives drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } } // End VertexProgram effect if(useMeshVP) { // Apply it. _MeshVertexProgram->end(drv); } } // *************************************************************************** void CMeshGeom::renderSimpleWithMaterial(IDriver *drv, const CMatrix &worldMatrix, CMaterial &mat) { H_AUTO( NL3D_MeshGeom_RenderSimpleWithMaterial ); nlassert(drv); // setup matrix drv->setupModelMatrix(worldMatrix); // Active simple VB. drv->activeVertexBuffer(_VBuffer); // For all _MatrixBlocks for(uint mb=0;mb<_MatrixBlocks.size();mb++) { CMatrixBlock &mBlock= _MatrixBlocks[mb]; if(mBlock.RdrPass.size()==0) continue; // Render all pass. for(uint i=0;iactiveIndexBuffer(rdrPass.PBlock); drv->renderTriangles(mat, 0, rdrPass.PBlock.getNumIndexes()/3); } } } // *************************************************************************** void CMeshGeom::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 * ***********************************************/ /* Version 5: - Preferred memory. Version 4: - BonesName. Version 3: - MeshVertexProgram. Version 2: - precompute of triangle order. (nothing more to load). Version 1: - added blend shapes Version 0: - separate serialisation CMesh / CMeshGeom. */ sint ver = f.serialVersion (4); // must have good original Skinned Vertex before writing. if( !f.isReading() && _Skinned && !_OriginalSkinRestored ) { restoreOriginalSkinVertices(); } // Version 4+: Array of bone name if (ver >= 4) { f.serialCont (_BonesName); } if (f.isReading()) { // Version3-: Bones index are in skeleton model id list _BoneIdComputed = (ver < 4); // In all case, must recompute usage of parents. _BoneIdExtended= false; } else { // Warning, if you have skinned this shape, you can't write it anymore because skinning id have been changed! nlassert (_BoneIdComputed==false); } // Version3+: MeshVertexProgram. if (ver >= 3) { IMeshVertexProgram *mvp= NULL; if(f.isReading()) { f.serialPolyPtr(mvp); _MeshVertexProgram= mvp; } else { mvp= _MeshVertexProgram; f.serialPolyPtr(mvp); } } else if(f.isReading()) { // release vp _MeshVertexProgram= NULL; } // TestYoyo //_MeshVertexProgram= NULL; // Version1+: _MeshMorpher. if (ver >= 1) f.serial (*_MeshMorpher); // serial geometry. f.serial (_VBuffer); f.serialCont (_MatrixBlocks); f.serial (_BBox); f.serial (_Skinned); // If _VertexBuffer changed, flag the VertexBufferHard. if(f.isReading()) { // if >= version 2, reorder of triangles is precomputed, else compute it now. if(ver < 2 ) { optimizeTriangleOrder(); } } // Skinning: If Version < 4, _BonesName are not present, must compute _BonesId from localId // Else it is computed at first computeBonesId(). if(ver < 4) buildBoneUsageVer3(); // TestYoyo //_MeshVertexProgram= NULL; /*{ uint numTris= 0; for(uint i=0;i<_MatrixBlocks.size();i++) { for(uint j=0;j<_MatrixBlocks[i].RdrPass.size();j++) numTris+= _MatrixBlocks[i].RdrPass[j].PBlock.getNumTri(); } nlinfo("YOYO: %d Vertices. %d Triangles.", _VBuffer.getNumVertices(), numTris); }*/ // Some runtime not serialized compilation if(f.isReading()) compileRunTime(); } // *************************************************************************** void CMeshGeom::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 * ***********************************************/ // if skinned, prepare skinning if(_Skinned) { // bkup vertices bkupOriginalSkinVertices(); // build the shadow skin buildShadowSkin(); } // Do precise clipping for big object?? _PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD; // Support MeshBlockRendering only if not skinned/meshMorphed. bool supportMeshBlockRendering= !_Skinned && _MeshMorpher->BlendShapes.size()==0; // true only if one matrix block, and at least one rdrPass. supportMeshBlockRendering= supportMeshBlockRendering && _MatrixBlocks.size()==1 && _MatrixBlocks[0].RdrPass.size()>0; if (supportMeshBlockRendering && _MeshVertexProgram) { supportMeshBlockRendering = supportMeshBlockRendering && _MeshVertexProgram->supportMeshBlockRendering(); } // TestYoyo //supportMeshBlockRendering= false; // support MeshVertexProgram, but no material sorting... bool supportMBRPerMaterial= supportMeshBlockRendering && _MeshVertexProgram==NULL; // setup flags _SupportMBRFlags= 0; if(supportMeshBlockRendering) _SupportMBRFlags|= MBROk; if(supportMBRPerMaterial) _SupportMBRFlags|= MBRSortPerMaterial; bool avoidVBHard= _Skinned || ( _MeshMorpher && _MeshMorpher->BlendShapes.size()>0 ); _VBuffer.setPreferredMemory (avoidVBHard?CVertexBuffer::RAMPreferred:CVertexBuffer::StaticPreferred, false); } // *************************************************************************** bool CMeshGeom::retrieveVertices(std::vector &vertices) const { /* *********************************************** * 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 * ***********************************************/ uint i; // if resident, fails!!! cannot read! const CVertexBuffer &vb= getVertexBuffer(); if(vb.isResident()) return false; vertices.clear(); vertices.resize(vb.getNumVertices()); { CVertexBufferRead vba; vb.lock (vba); const uint8 *pVert= (const uint8*)vba.getVertexCoordPointer(0); uint vSize= vb.getVertexSize(); for(i=0;i &indices) const { /* *********************************************** * 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 * ***********************************************/ uint i; indices.clear(); // count numTris uint numTris= 0; for(i=0;i &tmpFaces) { sint i,j,k; TBoneMap remainingBones; list remainingFaces; // 0. normalize SkinWeights: for all weights at 0, copy the matrixId from 0th matrix => no random/bad use of matrix. //================================ for(i=0;i<(sint)m.SkinWeights.size();i++) { CMesh::CSkinWeight &sw= m.SkinWeights[i]; // 0th weight must not be 0. nlassert(sw.Weights[0]!=0); // Begin at 1, tests all other weights. for(j=1;j boneUse; boneUse.reserve(NL3D_MESH_SKINNING_MAX_MATRIX*3); // While still exist faces. while(!remainingFaces.empty()) { // create a new matrix block. _MatrixBlocks.push_back(CMatrixBlock()); CMatrixBlock &matrixBlock= _MatrixBlocks[_MatrixBlocks.size()-1]; matrixBlock.NumMatrix=0; // a. reset remainingBones as not inserted in the current matrixBlock. //============================ ItBoneMap itBone; for(itBone= remainingBones.begin();itBone!=remainingBones.end();itBone++) { itBone->second.Inserted= false; } // b. while still exist bones, try to insert faces which use them in matrixBlock. //============================ while(!remainingBones.empty()) { // get the first bone from the map. (remind: depth-first order). uint currentBoneId= remainingBones.begin()->first; // If no more faces in the remainingFace list use this bone, remove it, and continue. if(remainingBones.begin()->second.RefCount==0) { remainingBones.erase(remainingBones.begin()); continue; } // this is a marker, to know if a face insertion will occurs. bool faceAdded= false; // traverse all faces, trying to insert them in current MatrixBlock processed. list::iterator itFace; for(itFace= remainingFaces.begin(); itFace!=remainingFaces.end();) { bool useCurrentBoneId; uint newBoneAdded; // i/ Get info on current face. //----------------------------- // build which bones this face use. tmpFaces[*itFace].buildBoneUse(boneUse, m.SkinWeights); // test if this face use the currentBoneId. useCurrentBoneId= false; for(i=0;i<(sint)boneUse.size();i++) { // if this face use the currentBoneId if(boneUse[i]==currentBoneId) { useCurrentBoneId= true; break; } } // compute how many bones that are not in the current matrixblock this face use. newBoneAdded=0; for(i=0;i<(sint)boneUse.size();i++) { // if this bone is not inserted in the current matrix block, inform it. if(!remainingBones[boneUse[i]].Inserted) newBoneAdded++; } // ii/ insert/reject face. //------------------------ // If this face do not add any more bone, we can insert it into the current matrixblock. // If it use the currentBoneId, and do not explode max count, we allow insert it too in the current matrixblock. if( newBoneAdded==0 || (useCurrentBoneId && newBoneAdded+matrixBlock.NumMatrix < IDriver::MaxModelMatrix) ) { // Insert this face in the current matrix block CFaceTmp &face= tmpFaces[*itFace]; // for all vertices of this face. for(j=0;j<3;j++) { CMesh::CSkinWeight &sw= m.SkinWeights[face.Corner[j].Vertex]; // for each corner weight (4) for(k=0;k blockRemaps; blockRemaps.resize(_MatrixBlocks.size()); // For all MatrixBlocks > first, try to "mirror" bones from previous. for(i=1;i<(sint)_MatrixBlocks.size();i++) { CMatrixBlock &mBlock= _MatrixBlocks[i]; CMatrixBlock &mPrevBlock= _MatrixBlocks[i-1]; CMatrixBlockRemap &remap= blockRemaps[i]; // First bkup the bone ids in remap table. for(j=0;j<(sint)mBlock.NumMatrix;j++) { remap.Remap[j]= mBlock.MatrixId[j]; } // For all ids of this blocks, try to mirror them. for(j=0;j<(sint)mBlock.NumMatrix;j++) { // get the location of this bone in the prev bone. sint idLoc= mPrevBlock.getMatrixIdLocation(mBlock.MatrixId[j]); // If not found, or if bigger than current array, fails (cant be mirrored). // Or if already mirrored. if(idLoc==-1 || idLoc>=(sint)mBlock.NumMatrix || idLoc==j) { // next id. j++; } else { // puts me on my mirrored location. and swap with the current one at this mirrored location. swap(mBlock.MatrixId[j], mBlock.MatrixId[idLoc]); // mBlock.MatrixId[j] is now a candidate for mirror. } } // Then build the Remap table, to re-order faces matrixId which use this matrix block. for(j=0;j<(sint)mBlock.NumMatrix;j++) { // get the boneid which was at this position j before. uint boneId= remap.Remap[j]; // search his new position, and store the result in the remap table. remap.Remap[j]= mBlock.getMatrixIdLocation(boneId); } // NB: this matrixBlock is re-ordered. next matrixBlock use this state. } // For all faces/corners/weights, remap MatrixIds. for(i=0;i<(sint)tmpFaces.size();i++) { CFaceTmp &face= tmpFaces[i]; // do it but for matrixblock0. if(face.MatrixBlockId!=0) { CMatrixBlockRemap &remap= blockRemaps[face.MatrixBlockId]; // For all corners. for(j=0;j<3;j++) { for(k=0;k &boneUse, vector &skinWeights) { boneUse.clear(); // For the 3 corners of the face. for(sint i=0;i<3;i++) { // get the CSkinWeight of this vertex. CMesh::CSkinWeight &sw= skinWeights[Corner[i].Vertex]; // For all skin weights of this vertex, for(sint j=0;j remap; skeleton->remapSkinBones(_BonesName, _BonesId, remap); // **** Remap matrix blocks for (uint matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++) { // Ref on the matrix block CMatrixBlock &mb = _MatrixBlocks[matrixBlock]; // For each matrix uint matrix; for (matrix=0; matrix boneBBoxes; static std::vector boneBBEmpty; boneBBoxes.clear(); boneBBEmpty.clear(); boneBBoxes.resize(_BonesId.size()); boneBBEmpty.resize(_BonesId.size(), true); // For simplicity, use the shadow skin info only, to compute bone sphere for(uint vert=0;vert<_ShadowSkin.Vertices.size();vert++) { CShadowVertex &v= _ShadowSkin.Vertices[vert]; // Check id uint srcId= v.MatrixId; nlassert ( srcId < remap.size()); // remap v.MatrixId= remap[srcId]; // if the boneId is valid (ie found) if(_BonesId[srcId]>=0) { // transform the vertex pos in BoneSpace CVector p= skeleton->Bones[_BonesId[srcId]].getBoneBase().InvBindPos * v.Vertex; // extend the bone bbox. if(boneBBEmpty[srcId]) { boneBBoxes[srcId].setCenter(p); boneBBEmpty[srcId]= false; } else { boneBBoxes[srcId].extend(p); } } } // Compile spheres _BonesSphere.resize(_BonesId.size()); for(uint bone=0;bone<_BonesSphere.size();bone++) { // If the bone is empty, mark with -1 in the radius. if(boneBBEmpty[bone]) { _BonesSphere[bone].Radius= -1; } else { _BonesSphere[bone].Center= boneBBoxes[bone].getCenter(); _BonesSphere[bone].Radius= boneBBoxes[bone].getRadius(); } } // Computed _BoneIdComputed = true; } } // Already extended ? if (!_BoneIdExtended) { nlassert (skeleton); if (skeleton) { // the total bone Usage of the mesh. vector boneUsage; boneUsage.resize(skeleton->Bones.size(), false); // for all Bones marked as valid. uint i; for(i=0; i<_BonesId.size(); i++) { // if not a valid boneId, skip it. if(_BonesId[i]<0) continue; // mark him and his father in boneUsage. skeleton->flagBoneAndParents(_BonesId[i], boneUsage); } // fill _BonesIdExt with bones of _BonesId and their parents. _BonesIdExt.clear(); for(i=0; i boneUsage; boneUsage.resize(maxBoneId+1, 0); // reparse all matrixBlocks, counting usage for each bone. for (matrixBlock=0; matrixBlock<_MatrixBlocks.size(); matrixBlock++) { CMatrixBlock &mb = _MatrixBlocks[matrixBlock]; // For each matrix for (uint matrix=0; matrix suppose radius 0 sphere CBSphere sphere(CVector::Null, 0.f); _BonesSphere.clear(); _BonesSphere.resize(_BonesId.size(), sphere); } } // *************************************************************************** void CMeshGeom::updateSkeletonUsage(CSkeletonModel *sm, bool increment) { // For all Bones used by this mesh. for(uint i=0; i<_BonesIdExt.size();i++) { uint boneId= _BonesIdExt[i]; // Some explicit Error. if(boneId>=sm->Bones.size()) nlerror(" Skin is incompatible with Skeleton: tries to use bone %d", boneId); // increment or decrement not Forced, because CMeshGeom use getActiveBoneSkinMatrix(). if(increment) sm->incBoneUsage(boneId, CSkeletonModel::UsageNormal); else sm->decBoneUsage(boneId, CSkeletonModel::UsageNormal); } } // *************************************************************************** void CMeshGeom::bkupOriginalSkinVertices() { nlassert(_Skinned); // reset contReset(_OriginalSkinVertices); contReset(_OriginalSkinNormals); contReset(_OriginalTGSpace); // get num of vertices uint numVertices= _VBuffer.getNumVertices(); CVertexBufferRead vba; _VBuffer.lock (vba); // Copy VBuffer content into Original vertices normals. if(_VBuffer.getVertexFormat() & CVertexBuffer::PositionFlag) { // copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter). _OriginalSkinVertices.resize(numVertices); for(uint i=0; ineedTangentSpace()) { // yes, backup it nlassert(_VBuffer.getNumTexCoordUsed() > 0); uint tgSpaceStage = _VBuffer.getNumTexCoordUsed() - 1; _OriginalTGSpace.resize(numVertices); for(uint i=0; ineedTangentSpace()) { uint numTexCoords = _VBuffer.getNumTexCoordUsed(); nlassert(numTexCoords >= 2); nlassert(_OriginalTGSpace.size() == numVertices); // copy tangent space vectors for(uint i = 0; i < numVertices; ++i) { *(CVector*)vba.getTexCoordPointer(i, numTexCoords - 1)= _OriginalTGSpace[i]; } } // cleared _OriginalSkinRestored= true; } // *************************************************************************** // Flags for software vertex skinning. #define NL3D_SOFTSKIN_VNEEDCOMPUTE 3 #define NL3D_SOFTSKIN_VMUSTCOMPUTE 1 #define NL3D_SOFTSKIN_VCOMPUTED 0 // 3 means "vertex may need compute". // 1 means "Primitive say vertex must be computed". // 0 means "vertex is computed". // *************************************************************************** void CMeshGeom::applySkin(CSkeletonModel *skeleton) { // init. //=================== if(_OriginalSkinVertices.empty()) return; // Use correct skinning TSkinType skinType; if( _OriginalSkinNormals.empty() ) skinType= SkinPosOnly; else if( _OriginalTGSpace.empty() ) skinType= SkinWithNormal; else skinType= SkinWithTgSpace; // Get VB src/dst info/ptrs. uint numVertices= (uint)_OriginalSkinVertices.size(); uint dstStride= _VBuffer.getVertexSize(); // Get dst TgSpace. uint tgSpaceStage = 0; if( skinType>= SkinWithTgSpace) { nlassert(_VBuffer.getNumTexCoordUsed() > 0); tgSpaceStage= _VBuffer.getNumTexCoordUsed() - 1; } // Mark all vertices flag to not computed. static vector skinFlags; skinFlags.resize(numVertices); // reset all flags memset(&skinFlags[0], NL3D_SOFTSKIN_VNEEDCOMPUTE, numVertices ); CVertexBufferRead vba; _VBuffer.lock (vba); // For all MatrixBlocks //=================== for(uint mb= 0; mb<_MatrixBlocks.size();mb++) { // compute matrixes for this block. static CMatrix3x4 matrixes[IDriver::MaxModelMatrix]; computeSkinMatrixes(skeleton, matrixes, mb==0?NULL:&_MatrixBlocks[mb-1], _MatrixBlocks[mb]); // check what vertex to skin for this PB. flagSkinVerticesForMatrixBlock(&skinFlags[0], _MatrixBlocks[mb]); // Get VB src/dst ptrs. uint8 *pFlag= &skinFlags[0]; CVector *srcVector= &_OriginalSkinVertices[0]; uint8 *srcPal= (uint8*)vba.getPaletteSkinPointer(0); uint8 *srcWgt= (uint8*)vba.getWeightPointer(0); uint8 *dstVector= (uint8*)vba.getVertexCoordPointer(0); // Normal. CVector *srcNormal= NULL; uint8 *dstNormal= NULL; if(skinType>=SkinWithNormal) { srcNormal= &_OriginalSkinNormals[0]; dstNormal= (uint8*)vba.getNormalCoordPointer(0); } // TgSpace. CVector *srcTgSpace= NULL; uint8 *dstTgSpace= NULL; if(skinType>=SkinWithTgSpace) { srcTgSpace= &_OriginalTGSpace[0]; dstTgSpace= (uint8*)vba.getTexCoordPointer(0, tgSpaceStage); } // For all vertices that need to be computed. uint size= numVertices; for(;size>0;size--) { // If we must compute this vertex. if(*pFlag==NL3D_SOFTSKIN_VMUSTCOMPUTE) { // Flag this vertex as computed. *pFlag=NL3D_SOFTSKIN_VCOMPUTED; CPaletteSkin *psPal= (CPaletteSkin*)srcPal; // checks indices. nlassert(psPal->MatrixId[0]MatrixId[1]MatrixId[2]MatrixId[3]=SkinWithNormal) computeSoftwareVectorSkinning(matrixes, srcNormal, psPal, (float*)srcWgt, (CVector*)dstNormal); // compute tg part. if(skinType>=SkinWithTgSpace) computeSoftwareVectorSkinning(matrixes, srcTgSpace, psPal, (float*)srcWgt, (CVector*)dstTgSpace); } // inc flags. pFlag++; // inc src (all whatever skin type used...) srcVector++; srcNormal++; srcTgSpace++; // inc paletteSkin and dst (all whatever skin type used...) srcPal+= dstStride; srcWgt+= dstStride; dstVector+= dstStride; dstNormal+= dstStride; dstTgSpace+= dstStride; } } // dirt _OriginalSkinRestored= false; } // *************************************************************************** void CMeshGeom::flagSkinVerticesForMatrixBlock(uint8 *skinFlags, CMatrixBlock &mb) { for(uint i=0; i0;nIndex--, pIndex++) skinFlags[*pIndex]&= NL3D_SOFTSKIN_VMUSTCOMPUTE; } else { uint16 *pIndex= (uint16*)iba.getPtr(); for(;nIndex>0;nIndex--, pIndex++) skinFlags[*pIndex]&= NL3D_SOFTSKIN_VMUSTCOMPUTE; } } } // *************************************************************************** void CMeshGeom::computeSoftwarePointSkinning(CMatrix3x4 *matrixes, CVector *srcVec, CPaletteSkin *srcPal, float *srcWgt, CVector *pDst) { CMatrix3x4 *pMat; // 0th matrix influence. pMat= matrixes + srcPal->MatrixId[0]; pMat->mulSetPoint(*srcVec, srcWgt[0], *pDst); // 1th matrix influence. pMat= matrixes + srcPal->MatrixId[1]; pMat->mulAddPoint(*srcVec, srcWgt[1], *pDst); // 2th matrix influence. pMat= matrixes + srcPal->MatrixId[2]; pMat->mulAddPoint(*srcVec, srcWgt[2], *pDst); // 3th matrix influence. pMat= matrixes + srcPal->MatrixId[3]; pMat->mulAddPoint(*srcVec, srcWgt[3], *pDst); } // *************************************************************************** void CMeshGeom::computeSoftwareVectorSkinning(CMatrix3x4 *matrixes, CVector *srcVec, CPaletteSkin *srcPal, float *srcWgt, CVector *pDst) { CMatrix3x4 *pMat; // 0th matrix influence. pMat= matrixes + srcPal->MatrixId[0]; pMat->mulSetVector(*srcVec, srcWgt[0], *pDst); // 1th matrix influence. pMat= matrixes + srcPal->MatrixId[1]; pMat->mulAddVector(*srcVec, srcWgt[1], *pDst); // 2th matrix influence. pMat= matrixes + srcPal->MatrixId[2]; pMat->mulAddVector(*srcVec, srcWgt[2], *pDst); // 3th matrix influence. pMat= matrixes + srcPal->MatrixId[3]; pMat->mulAddVector(*srcVec, srcWgt[3], *pDst); } // *************************************************************************** void CMeshGeom::computeSkinMatrixes(CSkeletonModel *skeleton, CMatrix3x4 *matrixes, CMatrixBlock *prevBlock, CMatrixBlock &mBlock) { // For all matrix of this mBlock. for(uint idMat=0;idMatNumMatrix && prevBlock->MatrixId[idMat]== curBoneId) continue; // Else, we must setup the matrix matrixes[idMat].set(skeleton->getActiveBoneSkinMatrix(curBoneId)); } } // *************************************************************************** void CMeshGeom::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, float polygonCount, uint32 rdrFlags) { // get the mesh instance. CMeshBaseInstance *mi= safe_cast(trans); // For all _MatrixBlocks uint triCount= 0; for(uint mb=0;mb<_MatrixBlocks.size();mb++) { CMatrixBlock &mBlock= _MatrixBlocks[mb]; // Profile all pass. for (uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) || ( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) ) { triCount+= rdrPass.PBlock.getNumIndexes()/3; } } } // Profile if(triCount) { // tri per VBFormat rdrTrav->Scene->incrementProfileTriVBFormat(rdrTrav->Scene->BenchRes.MeshProfileTriVBFormat, _VBuffer.getVertexFormat(), triCount); // VBHard if(_VBuffer.getPreferredMemory()!=CVertexBuffer::RAMPreferred) rdrTrav->Scene->BenchRes.NumMeshVBufferHard++; else rdrTrav->Scene->BenchRes.NumMeshVBufferStd++; // rendered in BlockRendering, only if not transparent pass (known it if RenderTransparentMaterial is set) if(supportMeshBlockRendering() && (rdrFlags & IMeshGeom::RenderTransparentMaterial)==0 ) { if(isMeshInVBHeap()) { rdrTrav->Scene->BenchRes.NumMeshRdrBlockWithVBHeap++; rdrTrav->Scene->BenchRes.NumMeshTriRdrBlockWithVBHeap+= triCount; } else { rdrTrav->Scene->BenchRes.NumMeshRdrBlock++; rdrTrav->Scene->BenchRes.NumMeshTriRdrBlock+= triCount; } } else { rdrTrav->Scene->BenchRes.NumMeshRdrNormal++; rdrTrav->Scene->BenchRes.NumMeshTriRdrNormal+= triCount; } } } // *************************************************************************** bool CMeshGeom::intersectSkin(CTransformShape *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D) { // for Mesh, Use the Shadow Skinning (simple version). // get skeleton if(!mi || _OriginalSkinVertices.empty()) return false; CSkeletonModel *skeleton= mi->getSkeletonModel(); if(!skeleton) return false; // Compute skinning with all matrix this Mesh use. (the shadow geometry cannot use other Matrix than the mesh use). static std::vector matInfs; matInfs.resize(_BonesId.size()); for(uint i=0;iMatrixId[0]; // Next srcVert+= srcVertSize; srcPal+= srcVertSize; } } // But _ShadowSkin.Vertices[i].MatrixId is incorrect, since < IDriver::MaxModelMatrix // *** Count number of triangles, and get start index of each matrix block in the final Tri list uint numIndices= 0; uint mb; // can't be static cause of ThreadSafe vector > mbIndexRange; mbIndexRange.resize(_MatrixBlocks.size()); for(mb=0;mb<_MatrixBlocks.size();mb++) { // this matrix block start here mbIndexRange[mb].first= numIndices; // count tris rendered for this matrix block const CMatrixBlock &mBlock= _MatrixBlocks[mb]; for(uint rp=0;rp vertReIndexed; vertReIndexed.resize(_ShadowSkin.Vertices.size(), false); // for all matrix blocks for(mb=0;mb0;--nbIndex, pIndex++) { uint index= *pIndex; // if not already reindexed if(!vertReIndexed[index]) { vertReIndexed[index]= true; // reindex uint matId= _ShadowSkin.Vertices[index].MatrixId; nlassert(matIdisMBRVpOk(rdrCtx.Driver) ) { // Ok will use it. _SupportMBRFlags|= MBRCurrentUseVP; // Before VB activation _MeshVertexProgram->beginMBRMesh(rdrCtx.Driver, rdrCtx.Scene ); } // active VB. SoftwareSkinning: reset flags for skinning. rdrCtx.Driver->activeVertexBuffer(_VBuffer); } } // *************************************************************************** void CMeshGeom::activeInstance(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *inst, float polygonCount, void *vbDst) { // setup instance matrix rdrCtx.Driver->setupModelMatrix(inst->getWorldMatrix()); // setupLighting. inst->changeLightSetup(rdrCtx.RenderTrav); // MeshVertexProgram ? if( _SupportMBRFlags & MBRCurrentUseVP ) { CMatrix invertedObjectMatrix; invertedObjectMatrix = inst->getWorldMatrix().inverted(); _MeshVertexProgram->beginMBRInstance(rdrCtx.Driver, rdrCtx.Scene, inst, invertedObjectMatrix); } } // *************************************************************************** void CMeshGeom::renderPass(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *mi, float polygonCount, uint rdrPassId) { CMatrixBlock &mBlock= _MatrixBlocks[0]; CRdrPass &rdrPass= mBlock.RdrPass[rdrPassId]; // Render with the Materials of the MeshInstance, only if not blended. if( ( (mi->Materials[rdrPass.MaterialId].getBlend() == false) ) ) { CMaterial &material= mi->Materials[rdrPass.MaterialId]; // MeshVertexProgram ? if( _SupportMBRFlags & MBRCurrentUseVP ) { rdrCtx.RenderTrav->changeVPLightSetupMaterial(material, false); } if(rdrCtx.RenderThroughVBHeap) { // render shifted primitives rdrCtx.Driver->activeIndexBuffer(rdrPass.VBHeapPBlock); rdrCtx.Driver->renderTriangles(material, 0, rdrPass.VBHeapPBlock.getNumIndexes()/3); } else { // render primitives rdrCtx.Driver->activeIndexBuffer(rdrPass.PBlock); rdrCtx.Driver->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } } } // *************************************************************************** void CMeshGeom::endMesh(CMeshGeomRenderContext &rdrCtx) { // MeshVertexProgram ? if( _SupportMBRFlags & MBRCurrentUseVP ) { // End Mesh _MeshVertexProgram->endMBRMesh( rdrCtx.Driver ); // and remove Current Flag. _SupportMBRFlags&= ~MBRCurrentUseVP; } } // *************************************************************************** bool CMeshGeom::getVBHeapInfo(uint &vertexFormat, uint &numVertices) { // CMeshGeom support VBHeap rendering, assuming supportMeshBlockRendering is true. if( _SupportMBRFlags ) /* Yoyo: If VertexProgram, DON'T SUPPORT!! because VB need to be activated AFTER meshVP activation NB: still possible with complex code to do it (sort per VP type (with or not)...), but tests in Ryzom shows that VBHeap is not really important (not so much different shapes...) */ if( _MeshVertexProgram==NULL ) { vertexFormat= _VBuffer.getVertexFormat(); numVertices= _VBuffer.getNumVertices(); return true; } return false; } // *************************************************************************** void CMeshGeom::computeMeshVBHeap(void *dst, uint indexStart) { // Fill dst with Buffer content. CVertexBufferRead vba; _VBuffer.lock (vba); memcpy(dst, vba.getVertexCoordPointer(), _VBuffer.getNumVertices()*_VBuffer.getVertexSize() ); // NB: only 1 MB is possible ... nlassert(_MatrixBlocks.size()==1); CMatrixBlock &mBlock= _MatrixBlocks[0]; // For all rdrPass. for(uint i=0;ibuild (m, (uint)mbase.Materials.size()); // compile some stuff compileRunTime(); } // *************************************************************************** void CMesh::optimizeMaterialUsage(std::vector &remap) { // For each material, count usage. vector materialUsed; materialUsed.resize(CMeshBase::_Materials.size(), false); for(uint mb=0;mbapplyMaterialRemap(remap); } // *************************************************************************** void CMesh::setBlendShapes(std::vector&bs) { _MeshGeom->setBlendShapes (bs); } // *************************************************************************** void CMesh::build(CMeshBase::CMeshBaseBuild &mbuild, CMeshGeom &meshGeom) { /// copy MeshBase info: materials .... CMeshBase::buildMeshBase(mbuild); // build the geometry. *_MeshGeom= meshGeom; // compile some stuff compileRunTime(); } // *************************************************************************** CTransformShape *CMesh::createInstance(CScene &scene) { // Create a CMeshInstance, an instance of a mesh. //=============================================== CMeshInstance *mi= (CMeshInstance*)scene.createModel(NL3D::MeshInstanceId); mi->Shape= this; // instanciate the material part of the Mesh, ie the CMeshBase. CMeshBase::instanciateMeshBase(mi, &scene); // do some instance init for MeshGeom _MeshGeom->initInstance(mi); // init render Filter mi->initRenderFilterType(); return mi; } // *************************************************************************** bool CMesh::clip(const std::vector &pyramid, const CMatrix &worldMatrix) { return _MeshGeom->clip(pyramid, worldMatrix); } // *************************************************************************** void CMesh::render(IDriver *drv, CTransformShape *trans, bool passOpaque) { // 0 or 0xFFFFFFFF uint32 mask= (0-(uint32)passOpaque); uint32 rdrFlags; // select rdrFlags, without ifs. rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque); rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial); // render the mesh _MeshGeom->render(drv, trans, 0, rdrFlags, 1); } // *************************************************************************** void CMesh::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 * ***********************************************/ /* Version 6: - cut in serialisation, because of: - bad ITexture serialisation (with no version....) => must cut. (see CMeshBase serial). - because of this and to simplify, make a cut too in CMesh serialisation. NB : all old version code is dropped. */ sint ver= f.serialVersion(6); if(ver<6) throw NLMISC::EStream(f, "Mesh in Stream is too old (Mesh version < 6)"); // serial Materials infos contained in CMeshBase. CMeshBase::serialMeshBase(f); // serial geometry. _MeshGeom->serial(f); // if reading, compile some stuff if(f.isReading()) compileRunTime(); } // *************************************************************************** const NLMISC::CAABBoxExt& CMesh::getBoundingBox() const { return _MeshGeom->getBoundingBox(); } // *************************************************************************** const CVertexBuffer &CMesh::getVertexBuffer() const { return _MeshGeom->getVertexBuffer() ; } // *************************************************************************** uint CMesh::getNbMatrixBlock() const { return _MeshGeom->getNbMatrixBlock(); } // *************************************************************************** uint CMesh::getNbRdrPass(uint matrixBlockIndex) const { return _MeshGeom->getNbRdrPass(matrixBlockIndex) ; } // *************************************************************************** const CIndexBuffer &CMesh::getRdrPassPrimitiveBlock(uint matrixBlockIndex, uint renderingPassIndex) const { return _MeshGeom->getRdrPassPrimitiveBlock(matrixBlockIndex, renderingPassIndex) ; } // *************************************************************************** uint32 CMesh::getRdrPassMaterial(uint matrixBlockIndex, uint renderingPassIndex) const { return _MeshGeom->getRdrPassMaterial(matrixBlockIndex, renderingPassIndex) ; } // *************************************************************************** float CMesh::getNumTriangles (float distance) { // A CMesh do not degrad himself, so return 0, to not be taken into account. return 0; } // *************************************************************************** const CMeshGeom& CMesh::getMeshGeom () const { return *_MeshGeom; } // *************************************************************************** void CMesh::computeBonesId (CSkeletonModel *skeleton) { nlassert (_MeshGeom); _MeshGeom->computeBonesId (skeleton); } // *************************************************************************** void CMesh::updateSkeletonUsage(CSkeletonModel *sm, bool increment) { nlassert (_MeshGeom); _MeshGeom->updateSkeletonUsage(sm, increment); } // *************************************************************************** IMeshGeom *CMesh::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const { // Ok if meshGeom is ok. if(_MeshGeom->supportMeshBlockRendering()) { polygonCount= 0; return _MeshGeom; } else return NULL; } // *************************************************************************** void CMesh::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, bool passOpaque) { // 0 or 0xFFFFFFFF uint32 mask= (0-(uint32)passOpaque); uint32 rdrFlags; // select rdrFlags, without ifs. rdrFlags= mask & (IMeshGeom::RenderOpaqueMaterial | IMeshGeom::RenderPassOpaque); rdrFlags|= ~mask & (IMeshGeom::RenderTransparentMaterial); // render the mesh _MeshGeom->profileSceneRender(rdrTrav, trans, 0, rdrFlags); } // *************************************************************************** void CMesh::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 * ***********************************************/ // **** 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 ) { 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 CMesh::buildSystemGeometry() { // clear any _SystemGeometry.clear(); // don't build a system copy if skinned. In this case, ray intersection is done through CSkeletonModel // and intersectSkin() scheme if(_MeshGeom->isSkinned()) return; // retrieve geometry (if VB/IB not resident) if( !_MeshGeom->retrieveVertices(_SystemGeometry.Vertices) || !_MeshGeom->retrieveTriangles(_SystemGeometry.Triangles)) { _SystemGeometry.clear(); } // TestYoyo /*static uint32 totalMem= 0; totalMem+= _SystemGeometry.Vertices.size()*sizeof(CVector); totalMem+= _SystemGeometry.Triangles.size()*sizeof(uint32); nlinfo("CMesh: TotalMem: %d", totalMem);*/ } } // NL3D