// 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/misc/bsphere.h" #include "nel/misc/system_info.h" #include "nel/misc/hierarchical_timer.h" #include "nel/misc/fast_mem.h" #include "nel/3d/mesh_mrm_skinned.h" #include "nel/3d/mrm_builder.h" #include "nel/3d/mrm_parameters.h" #include "nel/3d/mesh_mrm_skinned_instance.h" #include "nel/3d/scene.h" #include "nel/3d/skeleton_model.h" #include "nel/3d/stripifier.h" #include "nel/3d/mesh_blender.h" #include "nel/3d/render_trav.h" #include "nel/misc/fast_floor.h" #include "nel/3d/raw_skinned.h" #include "nel/3d/shifted_triangle_cache.h" #include "nel/3d/texture_file.h" #include "nel/3d/matrix_3x4.h" using namespace NLMISC; using namespace std; namespace NL3D { H_AUTO_DECL( NL3D_MeshMRMGeom_RenderShadow ) // *************************************************************************** // *************************************************************************** // CMeshMRMSkinnedGeom::CLod // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshMRMSkinnedGeom::CLod::serial(NLMISC::IStream &f) { /* Version 0: - base vdrsion. */ f.serialVersion(0); uint i; f.serial(NWedges); f.serialCont(RdrPass); f.serialCont(Geomorphs); f.serialCont(MatrixInfluences); // Serial array of InfluencedVertices. NB: code written so far for NL3D_MESH_SKINNING_MAX_MATRIX==4 only. nlassert(NL3D_MESH_SKINNING_MAX_MATRIX==4); for(i= 0; i &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; } // *************************************************************************** CMeshMRMSkinnedGeom::CMeshMRMSkinnedGeom() { _BoneIdComputed = false; _BoneIdExtended = false; _PreciseClipping= false; _MeshDataId= 0; _SupportShadowSkinGrouping= false; } // *************************************************************************** CMeshMRMSkinnedGeom::~CMeshMRMSkinnedGeom() { } // *************************************************************************** void CMeshMRMSkinnedGeom::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest) { // check input. if(distanceFinest<0) return; if(distanceMiddle<=distanceFinest) return; if(distanceCoarsest<=distanceMiddle) return; // Change. _LevelDetail.DistanceFinest= distanceFinest; _LevelDetail.DistanceMiddle= distanceMiddle; _LevelDetail.DistanceCoarsest= distanceCoarsest; // compile _LevelDetail.compileDistanceSetup(); } // *************************************************************************** void CMeshMRMSkinnedGeom::build(CMesh::CMeshBuild &m, uint numMaxMaterial, const CMRMParameters ¶ms) { // Empty geometry? if(m.Vertices.size()==0 || m.Faces.size()==0) { _VBufferFinal.clear(); _Lods.clear(); _BBox.setCenter(CVector::Null); _BBox.setSize(CVector::Null); return; } nlassert(numMaxMaterial>0); /// 0. First, make bbox. //====================== // NB: this is equivalent as building BBox from MRM VBuffer, because CMRMBuilder create new vertices // which are just interpolation of original vertices. _BBox= makeBBox(m.Vertices); /// 1. Launch the MRM build process. //================================================ CMRMBuilder mrmBuilder; CMeshBuildMRM meshBuildMRM; std::vector bsList; mrmBuilder.compileMRM(m, bsList, params, meshBuildMRM, numMaxMaterial); nlassert (meshBuildMRM.Skinned); // Then build the packed vertex buffer //================================================ _VBufferFinal.build (meshBuildMRM.VBuffer, meshBuildMRM.SkinWeights); _Lods= meshBuildMRM.Lods; // Compute degradation control. //================================================ _LevelDetail.DistanceFinest= meshBuildMRM.DistanceFinest; _LevelDetail.DistanceMiddle= meshBuildMRM.DistanceMiddle; _LevelDetail.DistanceCoarsest= meshBuildMRM.DistanceCoarsest; nlassert(_LevelDetail.DistanceFinest>=0); nlassert(_LevelDetail.DistanceMiddle > _LevelDetail.DistanceFinest); nlassert(_LevelDetail.DistanceCoarsest > _LevelDetail.DistanceMiddle); // Compute OODistDelta and DistancePow _LevelDetail.compileDistanceSetup(); // For load balancing. //================================================ // compute Max Face Used _LevelDetail.MaxFaceUsed= 0; _LevelDetail.MinFaceUsed= 0; // Count of primitive block if(_Lods.size()>0) { uint pb; // Compute MinFaces. CLod &firstLod= _Lods[0]; for (pb=0; pb remap; // Current bone uint currentBone = 0; // Reserve memory _BonesName.reserve (m.BonesNames.size()); // For each vertices uint vert; CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices(); for (vert=0; vert<_VBufferFinal.getNumVertices(); vert++) { // Found one ? bool found=false; // For each weight uint weight; for (weight=0; weight0)||(weight==0)) { // Look for it std::map::iterator ite = remap.find (vertices[vert].Matrices[weight]); // Find ? if (ite == remap.end()) { // Insert it remap.insert (std::map::value_type (vertices[vert].Matrices[weight], currentBone)); // Check the id nlassert (vertices[vert].Matrices[weight]second; } // Found one found = true; } } // Found one ? nlassert (found); } // Remap the vertex influence by lods uint lod; for (lod=0; lod<_Lods.size(); lod++) { // For each matrix used uint matrix; for (matrix=0; matrix<_Lods[lod].MatrixInfluences.size(); matrix++) { // Remap std::map::iterator ite = remap.find (_Lods[lod].MatrixInfluences[matrix]); // Not find ? if (ite == remap.end()) { // Remove it _Lods[lod].MatrixInfluences.erase (_Lods[lod].MatrixInfluences.begin()+matrix); matrix--; continue; } // Remap _Lods[lod].MatrixInfluences[matrix] = ite->second; } } // Misc. //=================== // Some runtime not serialized compilation compileRunTime(); } // *************************************************************************** void CMeshMRMSkinnedGeom::applyMaterialRemap(const std::vector &remap) { for(uint lod=0;lod=0); matId= remap[matId]; } } } // *************************************************************************** void CMeshMRMSkinnedGeom::applyGeomorph(std::vector &geoms, float alphaLod) { applyGeomorphWithVBHardPtr(geoms, alphaLod); } // *************************************************************************** void CMeshMRMSkinnedGeom::applyGeomorphWithVBHardPtr(std::vector &geoms, float alphaLod) { // no geomorphs? quit. if(geoms.size()==0) return; clamp(alphaLod, 0.f, 1.f); sint a= (uint)floor((256.f*alphaLod)+0.5f); sint a1= 256 - a; // info from VBuffer. uint8 *vertexPtr= (uint8*)_VBufferFinal.getPackedVertices(); sint32 vertexSize= sizeof(CPackedVertexBuffer::CPackedVertex); // use a faster method applyGeomorphPosNormalUV0Int(geoms, vertexPtr, vertexPtr, vertexSize, a, a1); } // *************************************************************************** void CMeshMRMSkinnedGeom::applyGeomorphPosNormalUV0(std::vector &geoms, uint8 *vertexPtr, uint8 *vertexDestPtr, sint32 vertexSize, float a, float a1) { nlassert(vertexSize==32); // For all geomorphs. uint nGeoms= (uint)geoms.size(); CMRMWedgeGeom *ptrGeom= &(geoms[0]); uint8 *destPtr= vertexDestPtr; for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize ) { // Consider the Pos/Normal/UV as an array of 8 float to interpolate. float *start= (float*)(vertexPtr + (ptrGeom->Start<<5)); float *end= (float*)(vertexPtr + (ptrGeom->End<<5)); float *dst= (float*)(destPtr); // unrolled dst[0]= start[0] * a + end[0]* a1; dst[1]= start[1] * a + end[1]* a1; dst[2]= start[2] * a + end[2]* a1; dst[3]= start[3] * a + end[3]* a1; dst[4]= start[4] * a + end[4]* a1; dst[5]= start[5] * a + end[5]* a1; dst[6]= start[6] * a + end[6]* a1; dst[7]= start[7] * a + end[7]* a1; } } // *************************************************************************** void CMeshMRMSkinnedGeom::applyGeomorphPosNormalUV0Int(std::vector &geoms, uint8 *vertexPtr, uint8 *vertexDestPtr, sint32 vertexSize, sint a, sint a1) { // For all geomorphs. uint nGeoms= (uint)geoms.size(); CMRMWedgeGeom *ptrGeom= &(geoms[0]); uint8 *destPtr= vertexDestPtr; for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize ) { // Consider the Pos/Normal/UV as an array of 8 float to interpolate. sint16 *start= (sint16*)(vertexPtr + (ptrGeom->Start*vertexSize)); sint16 *end= (sint16*)(vertexPtr + (ptrGeom->End*vertexSize)); sint16 *dst= (sint16*)(destPtr); /* Hulud * This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning) */ // unrolled dst[0]= (sint16)(((sint)(start[0]) * a + (sint)(end[0]) * a1)>>8); dst[1]= (sint16)(((sint)(start[1]) * a + (sint)(end[1]) * a1)>>8); dst[2]= (sint16)(((sint)(start[2]) * a + (sint)(end[2]) * a1)>>8); dst[3]= (sint16)(((sint)(start[3]) * a + (sint)(end[3]) * a1)>>8); dst[4]= (sint16)(((sint)(start[4]) * a + (sint)(end[4]) * a1)>>8); dst[5]= (sint16)(((sint)(start[5]) * a + (sint)(end[5]) * a1)>>8); dst[6]= (sint16)(((sint)(start[6]) * a + (sint)(end[6]) * a1)>>8); dst[7]= (sint16)(((sint)(start[7]) * a + (sint)(end[7]) * a1)>>8); } } // *************************************************************************** void CMeshMRMSkinnedGeom::initInstance(CMeshBaseInstance *mbi) { } // *************************************************************************** bool CMeshMRMSkinnedGeom::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; } // *************************************************************************** inline sint CMeshMRMSkinnedGeom::chooseLod(float alphaMRM, float &alphaLod) { // Choose what Lod to draw. alphaMRM*= _Lods.size()-1; sint numLod= (sint)ceil(alphaMRM); if(numLod==0) { numLod= 0; alphaLod= 0; } else { // Lerp beetween lod i-1 and lod i. alphaLod= alphaMRM-(numLod-1); } /// Ensure numLod is correct if(numLod>=(sint)_Lods.size()) { numLod= (sint)_Lods.size()-1; alphaLod= 1; } return numLod; } // *************************************************************************** void CMeshMRMSkinnedGeom::render(IDriver *drv, CTransformShape *trans, float polygonCount, uint32 rdrFlags, float globalAlpha) { nlassert(drv); if(_Lods.size()==0) return; // get the meshMRM instance. CMeshBaseInstance *mi= safe_cast(trans); // get the result of the Load Balancing. float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount); // choose the lod. float alphaLod; sint numLod= chooseLod(alphaMRM, alphaLod); // Render the choosen Lod. CLod &lod= _Lods[numLod]; if(lod.RdrPass.size()==0) return; // 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(!(mi->isSkinned() && skeleton)); // Profiling //=========== H_AUTO( NL3D_MeshMRMGeom_RenderNormal ); // Skinning. //=========== // set the instance worldmatrix. drv->setupModelMatrix(trans->getWorldMatrix()); // Geomorph. //=========== // Geomorph the choosen Lod (if not the coarser mesh). if(numLod>0) { applyGeomorph(lod.Geomorphs, alphaLod); } // force normalisation of normals.. bool bkupNorm= drv->isForceNormalize(); drv->forceNormalize(true); // Setup meshVertexProgram //=========== // Render the lod. //=========== // active VB. /* Hulud * This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning) */ CVertexBuffer tmp; getVertexBuffer (tmp); drv->activeVertexBuffer(tmp); // Global alpha used ? uint32 globalAlphaUsed= rdrFlags & IMeshGeom::RenderGlobalAlpha; uint8 globalAlphaInt=(uint8)NLMISC::OptFastFloor(globalAlpha*255); // Render all pass. if (globalAlphaUsed) { bool gaDisableZWrite= (rdrFlags & IMeshGeom::RenderGADisableZWrite)?true:false; // for all passes 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); /* Hulud * This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning) */ CIndexBuffer block; lod.getRdrPassPrimitiveBlock (i, block); // Render drv->activeIndexBuffer(block); drv->renderTriangles(material, 0, block.getNumIndexes()/3); // Resetup material/driver blender.restoreRender(material, drv, gaDisableZWrite); } } } else { 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]; /* Hulud * This is slow but, we don't care because this method is called for debug purpose only (watch skin without skinning) */ CIndexBuffer block; lod.getRdrPassPrimitiveBlock (i, block); // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(block); drv->renderTriangles(material, 0, block.getNumIndexes()/3); } } } // bkup force normalisation. drv->forceNormalize(bkupNorm); } // *************************************************************************** void CMeshMRMSkinnedGeom::renderSkin(CTransformShape *trans, float alphaMRM) { } // *************************************************************************** bool CMeshMRMSkinnedGeom::supportSkinGrouping() const { return true; } // *************************************************************************** sint CMeshMRMSkinnedGeom::renderSkinGroupGeom(CMeshMRMSkinnedInstance *mi, float alphaMRM, uint remainingVertices, uint8 *vbDest) { H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom ) // since not tested in supportSkinGrouping(), must test _Lods.empty(): no lod, no draw if(_Lods.empty()) return 0; // 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); // choose the lod. float alphaLod; sint numLod= chooseLod(alphaMRM, alphaLod); _LastLodComputed= numLod; // Render the choosen Lod. CLod &lod= _Lods[numLod]; if(lod.RdrPass.size()==0) // return no vertices added return 0; // If the Lod is too big to render in the VBufferHard if(lod.NWedges>remainingVertices) // return Failure return -1; // get the skeleton model to which I am skinned CSkeletonModel *skeleton; skeleton = mi->getSkeletonModel(); // must be skinned for renderSkin() nlassert(mi->isSkinned() && skeleton); // Profiling //=========== H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom_go ); // Skinning. //=========== // Use RawSkin if possible: only if no morph, and only Vertex/Normal updateRawSkinNormal(true, mi, numLod); // NB: the skeleton matrix has already been setuped by CSkeletonModel // NB: the normalize flag has already been setuped by CSkeletonModel // applySkin with RawSkin. //-------- nlassert(mi->_RawSkinCache); H_AUTO( NL3D_RawSkinning ); // RawSkin do all the job in optimized way: Skinning, copy to VBHard and Geomorph. // skinning with normal, but no tangent space applyRawSkinWithNormal (lod, *(mi->_RawSkinCache), skeleton, vbDest, alphaLod); // Vertices are packed in RawSkin mode (ie no holes due to MRM!) return (sint)mi->_RawSkinCache->Geomorphs.size() + mi->_RawSkinCache->TotalSoftVertices + mi->_RawSkinCache->TotalHardVertices; } // *************************************************************************** void CMeshMRMSkinnedGeom::renderSkinGroupPrimitives(CMeshMRMSkinnedInstance *mi, uint baseVertex, std::vector &specularRdrPasses, uint skinIndex) { H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpPrimitives ); // 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 lod choosen in renderSkinGroupGeom() CLod &lod= _Lods[_LastLodComputed]; // must update primitive cache updateShiftedTriangleCache(mi, _LastLodComputed, baseVertex); nlassert(mi->_ShiftedTriangleCache); // Render Triangles with cache //=========== for(uint i=0;iMaterials[rdrPass.MaterialId]; // TestYoyo. Material Speed Test /*if( material.getDiffuse()!=CRGBA(250, 251, 252) ) { material.setDiffuse(CRGBA(250, 251, 252)); // Set all texture the same. static CSmartPtr pTexFile= new CTextureFile("fy_hom_visage_c1_fy_e1.tga"); material.setTexture(0, pTexFile ); // Remove Specular. if(material.getShader()==CMaterial::Specular) { CSmartPtr tex= material.getTexture(0); material.setShader(CMaterial::Normal); material.setTexture(0, tex ); } // Remove MakeUp material.setTexture(1, NULL); }*/ // If the material is a specular material, don't render it now! if(material.getShader()==CMaterial::Specular) { // Add it to the rdrPass to sort! CSkinSpecularRdrPass specRdrPass; specRdrPass.SkinIndex= skinIndex; specRdrPass.RdrPassIndex= i; // Get the handle of the specular Map as the sort Key ITexture *specTex= material.getTexture(1); if(!specTex) specRdrPass.SpecId= 0; else specRdrPass.SpecId= drv->getTextureHandle( *specTex ); // Append it to the list specularRdrPasses.push_back(specRdrPass); } else { // Get the shifted triangles. CShiftedTriangleCache::CRdrPass &shiftedRdrPass= mi->_ShiftedTriangleCache->RdrPass[i]; // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(mi->_ShiftedTriangleCache->RawIndices); drv->renderTriangles(material, shiftedRdrPass.Triangles, shiftedRdrPass.NumTriangles); } } } // *************************************************************************** void CMeshMRMSkinnedGeom::renderSkinGroupSpecularRdrPass(CMeshMRMSkinnedInstance *mi, uint rdrPassId) { H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpSpecularRdrPass ); // 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 lod choosen in renderSkinGroupGeom() CLod &lod= _Lods[_LastLodComputed]; // _ShiftedTriangleCache must have been computed in renderSkinGroupPrimitives nlassert(mi->_ShiftedTriangleCache); // Render Triangles with cache //=========== CRdrPass &rdrPass= lod.RdrPass[rdrPassId]; // CMaterial Ref CMaterial &material=mi->Materials[rdrPass.MaterialId]; // Get the shifted triangles. CShiftedTriangleCache::CRdrPass &shiftedRdrPass= mi->_ShiftedTriangleCache->RdrPass[rdrPassId]; // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(mi->_ShiftedTriangleCache->RawIndices); drv->renderTriangles(material, shiftedRdrPass.Triangles, shiftedRdrPass.NumTriangles); } // *************************************************************************** void CMeshMRMSkinnedGeom::updateShiftedTriangleCache(CMeshMRMSkinnedInstance *mi, sint curLodId, uint baseVertex) { // if the instance has a cache, but not sync to us, delete it. if( mi->_ShiftedTriangleCache && ( mi->_ShiftedTriangleCache->MeshDataId != _MeshDataId || mi->_ShiftedTriangleCache->LodId != curLodId || mi->_ShiftedTriangleCache->BaseVertex != baseVertex) ) { mi->clearShiftedTriangleCache(); } // If the instance has not a valid cache, must create it. if( !mi->_ShiftedTriangleCache ) { mi->_ShiftedTriangleCache= new CShiftedTriangleCache; // Fill the cache Key. mi->_ShiftedTriangleCache->MeshDataId= _MeshDataId; mi->_ShiftedTriangleCache->LodId= curLodId; mi->_ShiftedTriangleCache->BaseVertex= baseVertex; // Build list of PBlock. From Lod, or from RawSkin cache. static vector pbList; pbList.clear(); nlassert(mi->_RawSkinCache); pbList.resize(mi->_RawSkinCache->RdrPass.size()); for(uint i=0;i_RawSkinCache->RdrPass[i]; } // Build RdrPass mi->_ShiftedTriangleCache->RdrPass.resize((uint32)pbList.size()); // First pass, count number of triangles, and fill header info uint totalTri= 0; uint i; for(i=0;i_ShiftedTriangleCache->RdrPass[i].NumTriangles= pbList[i]->getNumIndexes()/3; totalTri+= pbList[i]->getNumIndexes()/3; } // Allocate triangles indices. mi->_ShiftedTriangleCache->RawIndices.setFormat(NL_SKINNED_MESH_MRM_INDEX_FORMAT); mi->_ShiftedTriangleCache->RawIndices.setNumIndexes(totalTri*3); CIndexBufferReadWrite iba; mi->_ShiftedTriangleCache->RawIndices.lock(iba); // Second pass, fill ptrs, and fill Arrays uint indexTri= 0; for(i=0;i_ShiftedTriangleCache->RdrPass[i]; dstRdrPass.Triangles= indexTri*3; // Fill the array uint numTris= pbList[i]->getNumIndexes()/3; if(numTris) { uint nIds= numTris*3; // index, and fill CIndexBufferRead ibaRead; pbList[i]->lock (ibaRead); #ifndef NL_SKINNED_MESH_MRM_INDEX16 nlassert(iba.getFormat() == CIndexBuffer::Indices32); const uint32 *pSrcTri= (const uint32 *) ibaRead.getPtr(); uint32 *pDstTri= (uint32 *) iba.getPtr() + dstRdrPass.Triangles; for(;nIds>0;nIds--,pSrcTri++,pDstTri++) *pDstTri= *pSrcTri + baseVertex; #else nlassert(iba.getFormat() == CIndexBuffer::Indices16); const uint16 *pSrcTri= (const uint16 *) ibaRead.getPtr(); uint16 *pDstTri= (uint16 *) iba.getPtr() + dstRdrPass.Triangles; for(;nIds>0;nIds--,pSrcTri++,pDstTri++) *pDstTri= *pSrcTri + baseVertex; #endif } // Next indexTri+= dstRdrPass.NumTriangles; } } } // *************************************************************************** void CMeshMRMSkinnedGeom::serial(NLMISC::IStream &f) { // because of complexity, serial is separated in save / load. /* Version 0: - base version. */ f.serialVersion(0); // serial bones names f.serialCont (_BonesName); if (f.isReading()) { // Bones index are in skeleton model id list _BoneIdComputed = false; // Must always recompute usage of parents of bones used. _BoneIdExtended = false; } // serial Basic info. // ================== f.serial(_BBox); f.serial(_LevelDetail.MaxFaceUsed); f.serial(_LevelDetail.MinFaceUsed); f.serial(_LevelDetail.DistanceFinest); f.serial(_LevelDetail.DistanceMiddle); f.serial(_LevelDetail.DistanceCoarsest); f.serial(_LevelDetail.OODistanceDelta); f.serial(_LevelDetail.DistancePow); // Prepare the VBuffer. if (f.isReading()) _VBufferFinal.contReset(); _VBufferFinal.serial(f); // serial Shadow Skin Information f.serialCont (_ShadowSkin.Vertices); f.serialCont (_ShadowSkin.Triangles); // resest the Lod arrays. NB: each Lod is empty, and ready to receive Lod data. // ================== if (f.isReading()) { contReset(_Lods); } f.serialCont (_Lods); if (f.isReading()) { // Inform that the mesh data has changed dirtMeshDataId(); // Some runtime not serialized compilation compileRunTime(); } } // *************************************************************************** float CMeshMRMSkinnedGeom::getNumTriangles (float distance) { // NB: this is an approximation, but this is continious. return _LevelDetail.getNumTriangles(distance); } // *************************************************************************** void CMeshMRMSkinnedGeom::computeBonesId (CSkeletonModel *skeleton) { // Already computed ? if (!_BoneIdComputed) { // Get a pointer on the skeleton nlassert (skeleton); if (skeleton) { // **** For each bones, compute remap std::vector remap; skeleton->remapSkinBones(_BonesName, _BonesId, remap); // **** Remap the vertices, and compute Bone Spheres. // Find the Geomorph space: to process only real vertices, not geomorphed ones. uint nGeomSpace= 0; uint lod; for (lod=0; lod<_Lods.size(); lod++) { nGeomSpace= max(nGeomSpace, (uint)_Lods[lod].Geomorphs.size()); } // Prepare Sphere compute static std::vector boneBBoxes; static std::vector boneBBEmpty; boneBBoxes.clear(); boneBBEmpty.clear(); boneBBoxes.resize(_BonesId.size()); boneBBEmpty.resize(_BonesId.size(), true); // Remap the vertex, and compute the bone spheres. see CTransform::getSkinBoneSphere() doc. // for true vertices uint vert; const uint vertexCount = _VBufferFinal.getNumVertices(); CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices(); for (vert=nGeomSpace; vert0)||(weight==0)) { // Check id uint srcId= vertices[vert].Matrices[weight]; nlassert (srcId < remap.size()); // remap vertices[vert].Matrices[weight] = 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 * vertex; // extend the bone bbox. if(boneBBEmpty[srcId]) { boneBBoxes[srcId].setCenter(p); boneBBEmpty[srcId]= false; } else { boneBBoxes[srcId].extend(p); } } } else break; } } // 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(); } } // **** Remap the vertex influence by lods for (lod=0; lod<_Lods.size(); lod++) { // For each matrix used uint matrix; for (matrix=0; matrix<_Lods[lod].MatrixInfluences.size(); matrix++) { // Check nlassert (_Lods[lod].MatrixInfluences[matrix] 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=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 CMeshMRMSkinnedGeom::compileRunTime() { _PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD; // The Mesh must follow those restrictions, to support group skinning nlassert (_VBufferFinal.getNumVertices() < NL3D_MESH_SKIN_MANAGER_MAXVERTICES); // Support Shadow SkinGrouping if Shadow setuped, and if not too many vertices. _SupportShadowSkinGrouping= !_ShadowSkin.Vertices.empty() && NL3D_SHADOW_MESH_SKIN_MANAGER_VERTEXFORMAT==CVertexBuffer::PositionFlag && _ShadowSkin.Vertices.size() <= NL3D_SHADOW_MESH_SKIN_MANAGER_MAXVERTICES; } // *************************************************************************** void CMeshMRMSkinnedGeom::profileSceneRender(CRenderTrav *rdrTrav, CTransformShape *trans, float polygonCount, uint32 rdrFlags) { // if no _Lods, no draw if(_Lods.empty()) return; // get the result of the Load Balancing. float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount); // choose the lod. float alphaLod; sint numLod= chooseLod(alphaMRM, alphaLod); // Render the choosen Lod. CLod &lod= _Lods[numLod]; // get the mesh instance. CMeshBaseInstance *mi= safe_cast(trans); // Profile all pass. uint triCount= 0; for (uint i=0;iMaterials[rdrPass.MaterialId].getBlend() == false) && (rdrFlags & IMeshGeom::RenderOpaqueMaterial) ) || ( (mi->Materials[rdrPass.MaterialId].getBlend() == true) && (rdrFlags & IMeshGeom::RenderTransparentMaterial) ) ) { triCount+= rdrPass.getNumTriangle(); } } // Profile if(triCount) { // tri per VBFormat rdrTrav->Scene->incrementProfileTriVBFormat(rdrTrav->Scene->BenchRes.MeshMRMProfileTriVBFormat, NL3D_MESH_SKIN_MANAGER_VERTEXFORMAT, triCount); rdrTrav->Scene->BenchRes.NumMeshMRMVBufferStd++; rdrTrav->Scene->BenchRes.NumMeshMRMRdrNormal++; rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrNormal+= triCount; } } // *************************************************************************** bool CMeshMRMSkinnedGeom::getSkinBoneBBox(CSkeletonModel *skeleton, NLMISC::CAABBox &bbox, uint boneId) const { bbox.setCenter(CVector::Null); bbox.setHalfSize(CVector::Null); if(!skeleton) return false; // get the bindpos of the wanted bone nlassert(boneIdBones.size()); const CMatrix &invBindPos= skeleton->Bones[boneId].getBoneBase().InvBindPos; // Find the Geomorph space: to process only real vertices, not geomorphed ones. uint nGeomSpace= 0; uint lod; for (lod=0; lod<_Lods.size(); lod++) { nGeomSpace= max(nGeomSpace, (uint)_Lods[lod].Geomorphs.size()); } // Prepare BBox compute bool bbEmpty= true; // Remap the vertex, and compute the wanted bone bbox // for true vertices const uint vertexCount = _VBufferFinal.getNumVertices(); const CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices(); for (uint vert=nGeomSpace; vert0)||(weight==0)) { // Check id is the wanted one if(vertices[vert].Matrices[weight]==boneId) { // transform the vertex pos in BoneSpace CVector p= invBindPos * vertex; // extend the bone bbox. if(bbEmpty) { bbox.setCenter(p); bbEmpty= false; } else { bbox.extend(p); } } } else break; } } // return true if some influence found return !bbEmpty; } // *************************************************************************** // *************************************************************************** // Mesh Block Render Interface // *************************************************************************** // *************************************************************************** // *************************************************************************** bool CMeshMRMSkinnedGeom::supportMeshBlockRendering () const { /* Yoyo: Don't Support It for MRM because too Slow!! The problem is that lock() unlock() on each instance, on the same VBHeap IS AS SLOWER AS VB switching. TODO_OPTIMIZE: find a way to optimize MRM. */ return false; } // *************************************************************************** // *************************************************************************** // CMeshMRMSkinned. // *************************************************************************** // *************************************************************************** // *************************************************************************** CMeshMRMSkinned::CMeshMRMSkinned() { } // *************************************************************************** bool CMeshMRMSkinned::isCompatible(const CMesh::CMeshBuild &m) { /* Optimised shape for skinned object with MRM, 1 UV coordinates, 1 to 4 skinning weight and 256 matrices Tangeant space, vertex program, mesh block rendering and vertex buffer hard are not available. */ // Vertex format if (m.VertexFlags != NL3D_MESH_MRM_SKINNED_VERTEX_FORMAT) return false; // No W if (m.NumCoords[0] != 2) return false; // No blend shape if (!m.BlendShapes.empty()) return false; // Check number of vertices if (m.Vertices.size() > NL3D_MESH_SKIN_MANAGER_MAXVERTICES) return false; // Ok return true; } // *************************************************************************** void CMeshMRMSkinned::build (CMeshBase::CMeshBaseBuild &mBase, CMesh::CMeshBuild &m, const CMRMParameters ¶ms) { nlassert (isCompatible(m)); /// copy MeshBase info: materials .... CMeshBase::buildMeshBase (mBase); // Then build the geom. _MeshMRMGeom.build (m, (uint)mBase.Materials.size(), params); } // *************************************************************************** void CMeshMRMSkinned::build (CMeshBase::CMeshBaseBuild &m, const CMeshMRMSkinnedGeom &mgeom) { /// copy MeshBase info: materials .... CMeshBase::buildMeshBase(m); // Then copy the geom. _MeshMRMGeom= mgeom; } // *************************************************************************** void CMeshMRMSkinned::optimizeMaterialUsage(std::vector &remap) { // For each material, count usage. vector materialUsed; materialUsed.resize(CMeshBase::_Materials.size(), false); for(uint lod=0;lodShape= this; // instanciate the material part of the MeshMRM, ie the CMeshBase. CMeshBase::instanciateMeshBase(mi, &scene); // do some instance init for MeshGeom _MeshMRMGeom.initInstance(mi); // init the FilterType mi->initRenderFilterType(); return mi; } // *************************************************************************** bool CMeshMRMSkinned::clip(const std::vector &pyramid, const CMatrix &worldMatrix) { return _MeshMRMGeom.clip(pyramid, worldMatrix); } // *************************************************************************** void CMeshMRMSkinned::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 _MeshMRMGeom.render(drv, trans, trans->getNumTrianglesAfterLoadBalancing(), rdrFlags, 1); } // *************************************************************************** void CMeshMRMSkinned::serial(NLMISC::IStream &f) { /* Version 0: - base version. */ (void)f.serialVersion(0); // serial Materials infos contained in CMeshBase. CMeshBase::serialMeshBase(f); // serial the geometry. _MeshMRMGeom.serial(f); } // *************************************************************************** float CMeshMRMSkinned::getNumTriangles (float distance) { return _MeshMRMGeom.getNumTriangles (distance); } // *************************************************************************** const CMeshMRMSkinnedGeom& CMeshMRMSkinned::getMeshGeom () const { return _MeshMRMGeom; } // *************************************************************************** void CMeshMRMSkinned::computeBonesId (CSkeletonModel *skeleton) { _MeshMRMGeom.computeBonesId (skeleton); } // *************************************************************************** void CMeshMRMSkinned::updateSkeletonUsage (CSkeletonModel *skeleton, bool increment) { _MeshMRMGeom.updateSkeletonUsage(skeleton, increment); } // *************************************************************************** void CMeshMRMSkinned::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest) { _MeshMRMGeom.changeMRMDistanceSetup(distanceFinest, distanceMiddle, distanceCoarsest); } // *************************************************************************** IMeshGeom *CMeshMRMSkinned::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const { return NULL; } // *************************************************************************** void CMeshMRMSkinned::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); // profile the mesh _MeshMRMGeom.profileSceneRender(rdrTrav, trans, trans->getNumTrianglesAfterLoadBalancing(), rdrFlags); } // *************************************************************************** // *************************************************************************** // CMeshMRMSkinnedGeom RawSkin optimisation // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshMRMSkinnedGeom::dirtMeshDataId() { // see updateRawSkinNormal() _MeshDataId++; } // *************************************************************************** void CMeshMRMSkinnedGeom::updateRawSkinNormal(bool enabled, CMeshMRMSkinnedInstance *mi, sint curLodId) { if(!enabled) { // if the instance cache is not cleared, must clear. mi->clearRawSkinCache(); } else { // If the instance has no RawSkin, or has a too old RawSkin cache, must delete it, and recreate if ((mi->_RawSkinCache == NULL) || (mi->_RawSkinCache->MeshDataId!=_MeshDataId)) { // first delete if too old. mi->clearRawSkinCache(); // Then recreate, and use _MeshDataId to verify that the instance works with same data. mi->_RawSkinCache= new CRawSkinnedNormalCache; mi->_RawSkinCache->MeshDataId= _MeshDataId; mi->_RawSkinCache->LodId= -1; } /* If the instance rawSkin has a different Lod (or if -1), then must recreate it. NB: The lod may change each frame per instance, but suppose not so many change, so we can cache those data. */ if( mi->_RawSkinCache->LodId != curLodId ) { H_AUTO( NL3D_CMeshMRMGeom_updateRawSkinNormal ); CRawSkinnedNormalCache &skinLod= *mi->_RawSkinCache; CLod &lod= _Lods[curLodId]; uint i; sint rawIdx; // Clear the raw skin mesh. skinLod.clearArrays(); // Cache this lod mi->_RawSkinCache->LodId= curLodId; // For each matrix influence. nlassert(NL3D_MESH_SKINNING_MAX_MATRIX==4); // For each vertex, acknowledge if it is a src for geomorph. static vector softVertices; softVertices.clear(); softVertices.resize( _VBufferFinal.getNumVertices(), 0 ); for(i=0;i vertexRemap; vertexRemap.resize( _VBufferFinal.getNumVertices() ); sint softSize[4]; sint hardSize[4]; sint softStart[4]; sint hardStart[4]; // count vertices skinLod.TotalSoftVertices= 0; skinLod.TotalHardVertices= 0; for(i=0;i<4;i++) { softSize[i]= 0; hardSize[i]= 0; // Count. for(uint j=0;j &shadowVertices, const std::vector &triangles) { _ShadowSkin.Vertices= shadowVertices; _ShadowSkin.Triangles= triangles; // update flag. Support Shadow SkinGrouping if Shadow setuped, and if not too many vertices. _SupportShadowSkinGrouping= !_ShadowSkin.Vertices.empty() && NL3D_SHADOW_MESH_SKIN_MANAGER_VERTEXFORMAT==CVertexBuffer::PositionFlag && _ShadowSkin.Vertices.size() <= NL3D_SHADOW_MESH_SKIN_MANAGER_MAXVERTICES; } // *************************************************************************** uint CMeshMRMSkinnedGeom::getNumShadowSkinVertices() const { return (uint)_ShadowSkin.Vertices.size(); } // *************************************************************************** sint CMeshMRMSkinnedGeom::renderShadowSkinGeom(CMeshMRMSkinnedInstance *mi, uint remainingVertices, uint8 *vbDest) { uint numVerts= (uint)_ShadowSkin.Vertices.size(); // if no verts, no draw if(numVerts==0) return 0; // if no lods, there should be no verts, but still ensure no bug in skinning if(_Lods.empty()) return 0; // If the Lod is too big to render in the VBufferHard if(numVerts>remainingVertices) // return Failure return -1; // get the skeleton model to which I am skinned CSkeletonModel *skeleton; skeleton = mi->getSkeletonModel(); // must be skinned for renderSkin() nlassert(skeleton); // Profiling //=========== H_AUTO_USE( NL3D_MeshMRMGeom_RenderShadow ); // Skinning. //=========== // For all matrix this Mesh use. (the shadow geometry cannot use other Matrix than the mesh use). // NB: take the best lod since the lower lods cannot use other Matrix than the higher one. static vector boneMat3x4; CLod &lod= _Lods[_Lods.size()-1]; computeBoneMatrixes3x4(boneMat3x4, lod.MatrixInfluences, skeleton); _ShadowSkin.applySkin((CVector*)vbDest, boneMat3x4); // How many vertices are added to the VBuffer ??? return numVerts; } // *************************************************************************** void CMeshMRMSkinnedGeom::renderShadowSkinPrimitives(CMeshMRMSkinnedInstance *mi, CMaterial &castMat, IDriver *drv, uint baseVertex) { nlassert(drv); if(_ShadowSkin.Triangles.empty()) return; // Profiling //=========== H_AUTO_USE( NL3D_MeshMRMGeom_RenderShadow ); // NB: the skeleton matrix has already been setuped by CSkeletonModel // NB: the normalize flag has already been setuped by CSkeletonModel // TODO_SHADOW: optim: Special triangle cache for shadow! static CIndexBuffer shiftedTris; shiftedTris.setPreferredMemory(CIndexBuffer::RAMVolatile, false); if (shiftedTris.getName().empty()) NL_SET_IB_NAME(shiftedTris, "CMeshMRMSkinnedGeom::renderShadowSkinPrimitives::shiftedTris"); //if(shiftedTris.getNumIndexes()<_ShadowSkin.Triangles.size()) //{ shiftedTris.setFormat(NL_SKINNED_MESH_MRM_INDEX_FORMAT); shiftedTris.setNumIndexes((uint32)_ShadowSkin.Triangles.size()); //} { CIndexBufferReadWrite iba; shiftedTris.lock(iba); const uint32 *src= &_ShadowSkin.Triangles[0]; TSkinnedMeshMRMIndexType *dst= (TSkinnedMeshMRMIndexType*) iba.getPtr(); for(uint n= (uint)_ShadowSkin.Triangles.size();n>0;n--, src++, dst++) { *dst= (TSkinnedMeshMRMIndexType)(*src + baseVertex); } } // Render Triangles with cache //=========== uint numTris= (uint)_ShadowSkin.Triangles.size()/3; // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(shiftedTris); drv->renderTriangles(castMat, 0, numTris); } // *************************************************************************** bool CMeshMRMSkinnedGeom::intersectSkin(CMeshMRMSkinnedInstance *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D) { // no inst/verts/lod => no intersection if(!mi || _ShadowSkin.Vertices.empty() || _Lods.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). // NB: take the best lod (_Lods.back()) since the lower lods cannot use other Matrix than the higher one. return _ShadowSkin.getRayIntersection(toRaySpace, *skeleton, _Lods.back().MatrixInfluences, dist2D, distZ, computeDist2D); } // *************************************************************************** // *************************************************************************** // CMeshMRMSkinnedGeom::CPackedVertexBuffer // *************************************************************************** // *************************************************************************** void CMeshMRMSkinnedGeom::CPackedVertexBuffer::serial(NLMISC::IStream &f) { // Version f.serialVersion(0); f.serialCont (_PackedBuffer); f.serial (_DecompactScale); } // *************************************************************************** void CMeshMRMSkinnedGeom::CPackedVertexBuffer::CPackedVertex::serial(NLMISC::IStream &f) { // Version f.serialVersion(0); f.serial (X); f.serial (Y); f.serial (Z); f.serial (Nx); f.serial (Ny); f.serial (Nz); f.serial (U); f.serial (V); uint i; for (i=0; i &skinWeight) { const uint numVertices = buffer.getNumVertices(); nlassert (numVertices == skinWeight.size()); nlassert ((buffer.getVertexFormat() & (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag)) == (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag)); _PackedBuffer.resize (numVertices); // default scale _DecompactScale = NL3D_MESH_MRM_SKINNED_DEFAULT_POS_SCALE; CVertexBufferRead vba; buffer.lock (vba); if (numVertices) { // Get the min max of the bbox CVector _min = *vba.getVertexCoordPointer(0); CVector _max = _min; // For each vertex uint i; for (i=1; i 0); vertex.Weights[0] += weightSum; } } } // *************************************************************************** void CMeshMRMSkinnedGeom::getVertexBuffer(CVertexBuffer &output) const { output.setVertexFormat (CVertexBuffer::PositionFlag|CVertexBuffer::NormalFlag|CVertexBuffer::TexCoord0Flag); const uint numVertices = _VBufferFinal.getNumVertices(); output.setNumVertices (numVertices); // Set all UV routing on 0 uint i; for (i=0; i &skinWeights) const { const uint vertexCount = _VBufferFinal.getNumVertices(); skinWeights.resize (vertexCount); const CPackedVertexBuffer::CPackedVertex *vertices = _VBufferFinal.getPackedVertices(); uint i; for (i=0; i