// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // 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 <http://www.gnu.org/licenses/>. #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.h" #include "nel/3d/mrm_builder.h" #include "nel/3d/mrm_parameters.h" #include "nel/3d/mesh_mrm_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_skin.h" #include "nel/3d/shifted_triangle_cache.h" #include "nel/3d/texture_file.h" using namespace NLMISC; using namespace std; namespace NL3D { H_AUTO_DECL( NL3D_MeshMRMGeom_RenderShadow ) // *************************************************************************** // *************************************************************************** // CMeshMRMGeom::CLod // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshMRMGeom::CLod::serial(NLMISC::IStream &f) { /* Version 2: - precompute of triangle order. (nothing more to load). Version 1: - add VertexBlocks; Version 0: - base vdrsion. */ sint ver= f.serialVersion(2); 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<NL3D_MESH_SKINNING_MAX_MATRIX; i++) { f.serialCont(InfluencedVertices[i]); } if(ver>=1) f.serialCont(SkinVertexBlocks); else buildSkinVertexBlocks(); // if >= version 2, reorder of triangles is precomputed, else compute it now. if(ver<2) optimizeTriangleOrder(); } // *************************************************************************** void CMeshMRMGeom::CLod::buildSkinVertexBlocks() { contReset(SkinVertexBlocks); // The list of vertices. true if used by this lod. vector<bool> vertexMap; vertexMap.resize(NWedges, false); // from InfluencedVertices, aknoledge what vertices are used. uint i; for(i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;i++) { uint nInf= (uint)InfluencedVertices[i].size(); if( nInf==0 ) continue; uint32 *infPtr= &(InfluencedVertices[i][0]); // for all InfluencedVertices only. for(;nInf>0;nInf--, infPtr++) { uint index= *infPtr; vertexMap[index]= true; } } // For all vertices, test if they are used, and build the according SkinVertexBlocks; CVertexBlock *vBlock= NULL; for(i=0; i<vertexMap.size();i++) { if(vertexMap[i]) { // preceding block? if(vBlock) { // yes, extend it. vBlock->NVertices++; } else { // no, append a new one. SkinVertexBlocks.push_back(CVertexBlock()); vBlock= &SkinVertexBlocks[SkinVertexBlocks.size()-1]; vBlock->VertexStart= i; vBlock->NVertices= 1; } } else { // Finish the preceding block (if any). vBlock= NULL; } } } // *************************************************************************** void CMeshMRMGeom::CLod::optimizeTriangleOrder() { CStripifier stripifier; // for all rdrpass for(uint rp=0; rp<RdrPass.size(); rp++ ) { // stripify list of triangles of this pass. CRdrPass &pass= RdrPass[rp]; stripifier.optimizeTriangles(pass.PBlock, pass.PBlock); } } // *************************************************************************** // *************************************************************************** // CMeshMRMGeom. // *************************************************************************** // *************************************************************************** // *************************************************************************** static NLMISC::CAABBoxExt makeBBox(const std::vector<CVector> &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; } // *************************************************************************** CMeshMRMGeom::CMeshMRMGeom() { _Skinned= false; _NbLodLoaded= 0; _BoneIdComputed = false; _BoneIdExtended = false; _PreciseClipping= false; _SupportSkinGrouping= false; _MeshDataId= 0; _SupportMeshBlockRendering= false; _MBRCurrentLodId= 0; _SupportShadowSkinGrouping= false; } // *************************************************************************** CMeshMRMGeom::~CMeshMRMGeom() { } // *************************************************************************** void CMeshMRMGeom::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 CMeshMRMGeom::build(CMesh::CMeshBuild &m, std::vector<CMesh::CMeshBuild*> &bsList, uint numMaxMaterial, const CMRMParameters ¶ms) { // Empty geometry? if(m.Vertices.size()==0 || m.Faces.size()==0) { _VBufferFinal.setNumVertices(0); _VBufferFinal.reserve(0); _Lods.clear(); _BBox.setCenter(CVector::Null); _BBox.setSize(CVector::Null); return; } nlassert(numMaxMaterial>0); // SmartPtr Copy VertexProgram effect. //================================================ this->_MeshVertexProgram= m.MeshVertexProgram; /// 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; mrmBuilder.compileMRM(m, bsList, params, meshBuildMRM, numMaxMaterial); // Then just copy result! //================================================ _VBufferFinal= meshBuildMRM.VBuffer; _Lods= meshBuildMRM.Lods; _Skinned= meshBuildMRM.Skinned; _SkinWeights= meshBuildMRM.SkinWeights; // 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(); // Build the _LodInfos. //================================================ _LodInfos.resize(_Lods.size()); uint32 precNWedges= 0; uint i; for(i=0;i<_Lods.size();i++) { _LodInfos[i].StartAddWedge= precNWedges; _LodInfos[i].EndAddWedges= _Lods[i].NWedges; precNWedges= _Lods[i].NWedges; // LodOffset is filled in serial() when stream is input. } // After build, all lods are present in memory. _NbLodLoaded= (uint)_Lods.size(); // 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<firstLod.RdrPass.size(); pb++) { CRdrPass &pass= firstLod.RdrPass[pb]; // Sum tri _LevelDetail.MinFaceUsed+= pass.PBlock.getNumIndexes ()/3; } // Compute MaxFaces. CLod &lastLod= _Lods[_Lods.size()-1]; for (pb=0; pb<lastLod.RdrPass.size(); pb++) { CRdrPass &pass= lastLod.RdrPass[pb]; // Sum tri _LevelDetail.MaxFaceUsed+= pass.PBlock.getNumIndexes ()/3; } } // For skinning. //================================================ if( _Skinned ) { bkupOriginalSkinVertices(); } // Inform that the mesh data has changed dirtMeshDataId(); // For AGP SKinning optim, and for Render optim //================================================ for(i=0;i<_Lods.size();i++) { _Lods[i].buildSkinVertexBlocks(); // sort triangles for better cache use. _Lods[i].optimizeTriangleOrder(); } // Copy Blend Shapes //================================================ _MeshMorpher.BlendShapes = meshBuildMRM.BlendShapes; // Compact bone id and build a bone id names //================================================ // Skinned ? if (_Skinned) { // Remap std::map<uint, uint> remap; // Current bone uint currentBone = 0; // Reserve memory _BonesName.reserve (m.BonesNames.size()); // For each vertices uint vert; for (vert=0; vert<_SkinWeights.size(); vert++) { // Found one ? bool found=false; // For each weight uint weight; for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++) { // Active ? if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0)) { // Look for it std::map<uint, uint>::iterator ite = remap.find (_SkinWeights[vert].MatrixId[weight]); // Find ? if (ite == remap.end()) { // Insert it remap.insert (std::map<uint, uint>::value_type (_SkinWeights[vert].MatrixId[weight], currentBone)); // Check the id nlassert (_SkinWeights[vert].MatrixId[weight]<m.BonesNames.size()); // Set the bone name _BonesName.push_back (m.BonesNames[_SkinWeights[vert].MatrixId[weight]]); // Set the local bone id _SkinWeights[vert].MatrixId[weight] = currentBone++; } else { // Set the local bone id _SkinWeights[vert].MatrixId[weight] = ite->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<uint, uint>::iterator ite = remap.find (_Lods[lod].MatrixInfluences[matrix]); // Find ? nlassert (ite != remap.end()); // Remap _Lods[lod].MatrixInfluences[matrix] = ite->second; } } } // Misc. //=================== // Some runtime not serialized compilation compileRunTime(); } // *************************************************************************** void CMeshMRMGeom::applyMaterialRemap(const std::vector<sint> &remap) { for(uint lod=0;lod<getNbLod();lod++) { for(uint rp=0;rp<getNbRdrPass(lod);rp++) { // remap uint32 &matId= _Lods[lod].RdrPass[rp].MaterialId; nlassert(remap[matId]>=0); matId= remap[matId]; } } } // *************************************************************************** void CMeshMRMGeom::applyGeomorph(std::vector<CMRMWedgeGeom> &geoms, float alphaLod) { applyGeomorphWithVBHardPtr(geoms, alphaLod, NULL); } // *************************************************************************** void CMeshMRMGeom::applyGeomorphWithVBHardPtr(std::vector<CMRMWedgeGeom> &geoms, float alphaLod, uint8 *vertexDestPtr) { // no geomorphs? quit. if(geoms.size()==0) return; clamp(alphaLod, 0.f, 1.f); float a= alphaLod; float a1= 1 - alphaLod; // info from VBuffer. CVertexBufferReadWrite vba; _VBufferFinal.lock (vba); uint8 *vertexPtr= (uint8*)vba.getVertexCoordPointer(); uint flags= _VBufferFinal.getVertexFormat(); sint32 vertexSize= _VBufferFinal.getVertexSize(); // because of the unrolled code for 4 first UV, must assert this. nlassert(CVertexBuffer::MaxStage>=4); // must have XYZ. nlassert(flags & CVertexBuffer::PositionFlag); // If VBuffer Hard disabled if(vertexDestPtr==NULL) { // write into vertexPtr. vertexDestPtr= vertexPtr; } // if it is a common format if( flags== (CVertexBuffer::PositionFlag | CVertexBuffer::NormalFlag | CVertexBuffer::TexCoord0Flag) && _VBufferFinal.getValueType(CVertexBuffer::TexCoord0) == CVertexBuffer::Float2 ) { // use a faster method applyGeomorphPosNormalUV0(geoms, vertexPtr, vertexDestPtr, vertexSize, a, a1); } else { // for color interp uint i; uint ua= (uint)(a*256); clamp(ua, (uint)0, (uint)256); uint ua1= 256 - ua; // if an offset is 0, it means that the component is not in the VBuffer. sint32 normalOff; sint32 colorOff; sint32 specularOff; sint32 uvOff[CVertexBuffer::MaxStage]; bool has3Coords[CVertexBuffer::MaxStage]; // Compute offset of each component of the VB. if(flags & CVertexBuffer::NormalFlag) normalOff= _VBufferFinal.getNormalOff(); else normalOff= 0; if(flags & CVertexBuffer::PrimaryColorFlag) colorOff= _VBufferFinal.getColorOff(); else colorOff= 0; if(flags & CVertexBuffer::SecondaryColorFlag) specularOff= _VBufferFinal.getSpecularOff(); else specularOff= 0; for(i= 0; i<CVertexBuffer::MaxStage;i++) { if(flags & (CVertexBuffer::TexCoord0Flag<<i)) { uvOff[i]= _VBufferFinal.getTexCoordOff(i); has3Coords[i] = (_VBufferFinal.getValueType(i + CVertexBuffer::TexCoord0) == CVertexBuffer::Float3); } else { uvOff[i]= 0; } } // For all geomorphs. uint nGeoms= (uint)geoms.size(); CMRMWedgeGeom *ptrGeom= &(geoms[0]); uint8 *destPtr= vertexDestPtr; /* NB: optimisation: lot of "if" in this Loop, but because of BTB, they always cost nothing (prediction is good). NB: this also is why we unroll the 4 1st Uv. The other (if any), are done in the other loop. NB: optimisation for AGP write cominers: the order of write (vertex, normal, uvs...) is important for good use of AGP write combiners. We have 2 version : one that tests for 3 coordinates texture coords, and one that doesn't */ if (!has3Coords[0] && !has3Coords[1] && !has3Coords[2] && !has3Coords[3]) { // there are no texture coordinate of dimension 3 for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize ) { uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize; uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize; // Vertex. { CVector *start= (CVector*)startPtr; CVector *end= (CVector*)endPtr; CVector *dst= (CVector*)destPtr; *dst= *start * a + *end * a1; } // Normal. if(normalOff) { CVector *start= (CVector*)(startPtr + normalOff); CVector *end= (CVector*)(endPtr + normalOff); CVector *dst= (CVector*)(destPtr + normalOff); *dst= *start * a + *end * a1; } // Uvs. // uv[0]. if(uvOff[0]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[0]); CUV *end= (CUV*)(endPtr + uvOff[0]); CUV *dst= (CUV*)(destPtr + uvOff[0]); *dst= *start * a + *end * a1; } // uv[1]. if(uvOff[1]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[1]); CUV *end= (CUV*)(endPtr + uvOff[1]); CUV *dst= (CUV*)(destPtr + uvOff[1]); *dst= *start * a + *end * a1; } // uv[2]. if(uvOff[2]) { CUV *start= (CUV*)(startPtr + uvOff[2]); CUV *end= (CUV*)(endPtr + uvOff[2]); CUV *dst= (CUV*)(destPtr + uvOff[2]); *dst= *start * a + *end * a1; } // uv[3]. if(uvOff[3]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[3]); CUV *end= (CUV*)(endPtr + uvOff[3]); CUV *dst= (CUV*)(destPtr + uvOff[3]); *dst= *start * a + *end * a1; } } } else // THERE ARE TEXTURE COORDINATES OF DIMENSION 3 { for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize ) { uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize; uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize; // Vertex. { CVector *start= (CVector*)startPtr; CVector *end= (CVector*)endPtr; CVector *dst= (CVector*)destPtr; *dst= *start * a + *end * a1; } // Normal. if(normalOff) { CVector *start= (CVector*)(startPtr + normalOff); CVector *end= (CVector*)(endPtr + normalOff); CVector *dst= (CVector*)(destPtr + normalOff); *dst= *start * a + *end * a1; } // Uvs. // uv[0]. if(uvOff[0]) { if (!has3Coords[0]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[0]); CUV *end= (CUV*)(endPtr + uvOff[0]); CUV *dst= (CUV*)(destPtr + uvOff[0]); *dst= *start * a + *end * a1; } else { // Uv. CUVW *start= (CUVW*)(startPtr + uvOff[0]); CUVW *end= (CUVW*)(endPtr + uvOff[0]); CUVW *dst= (CUVW*)(destPtr + uvOff[0]); *dst= *start * a + *end * a1; } } // uv[1]. if(uvOff[1]) { if (!has3Coords[1]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[1]); CUV *end= (CUV*)(endPtr + uvOff[1]); CUV *dst= (CUV*)(destPtr + uvOff[1]); *dst= *start * a + *end * a1; } else { // Uv. CUVW *start= (CUVW*)(startPtr + uvOff[1]); CUVW *end= (CUVW*)(endPtr + uvOff[1]); CUVW *dst= (CUVW*)(destPtr + uvOff[1]); *dst= *start * a + *end * a1; } } // uv[2]. if(uvOff[2]) { if (!has3Coords[2]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[2]); CUV *end= (CUV*)(endPtr + uvOff[2]); CUV *dst= (CUV*)(destPtr + uvOff[2]); *dst= *start * a + *end * a1; } else { // Uv. CUVW *start= (CUVW*)(startPtr + uvOff[2]); CUVW *end= (CUVW*)(endPtr + uvOff[2]); CUVW *dst= (CUVW*)(destPtr + uvOff[2]); *dst= *start * a + *end * a1; } } // uv[3]. if(uvOff[3]) { if (!has3Coords[3]) { // Uv. CUV *start= (CUV*)(startPtr + uvOff[3]); CUV *end= (CUV*)(endPtr + uvOff[3]); CUV *dst= (CUV*)(destPtr + uvOff[3]); *dst= *start * a + *end * a1; } else { // Uv. CUVW *start= (CUVW*)(startPtr + uvOff[3]); CUVW *end= (CUVW*)(endPtr + uvOff[3]); CUVW *dst= (CUVW*)(destPtr + uvOff[3]); *dst= *start * a + *end * a1; } } // color. if(colorOff) { CRGBA *start= (CRGBA*)(startPtr + colorOff); CRGBA *end= (CRGBA*)(endPtr + colorOff); CRGBA *dst= (CRGBA*)(destPtr + colorOff); dst->blendFromui(*start, *end, ua1); } // specular. if(specularOff) { CRGBA *start= (CRGBA*)(startPtr + specularOff); CRGBA *end= (CRGBA*)(endPtr + specularOff); CRGBA *dst= (CRGBA*)(destPtr + specularOff); dst->blendFromui(*start, *end, ua1); } } } // Process extra UVs (maybe never, so don't bother optims :)). // For all stages after 4. for(i=4;i<CVertexBuffer::MaxStage;i++) { uint nGeoms= (uint)geoms.size(); CMRMWedgeGeom *ptrGeom= &(geoms[0]); uint8 *destPtr= vertexDestPtr; if(uvOff[i]==0) continue; // For all geomorphs. for(; nGeoms>0; nGeoms--, ptrGeom++, destPtr+= vertexSize ) { uint8 *startPtr= vertexPtr + ptrGeom->Start*vertexSize; uint8 *endPtr= vertexPtr + ptrGeom->End*vertexSize; // uv[i]. // Uv. if (!has3Coords[i]) { CUV *start= (CUV*)(startPtr + uvOff[i]); CUV *end= (CUV*)(endPtr + uvOff[i]); CUV *dst= (CUV*)(destPtr + uvOff[i]); *dst= *start * a + *end * a1; } else { CUVW *start= (CUVW*)(startPtr + uvOff[i]); CUVW *end= (CUVW*)(endPtr + uvOff[i]); CUVW *dst= (CUVW*)(destPtr + uvOff[i]); *dst= *start * a + *end * a1; } } } } } // *************************************************************************** void CMeshMRMGeom::applyGeomorphPosNormalUV0(std::vector<CMRMWedgeGeom> &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 CMeshMRMGeom::initInstance(CMeshBaseInstance *mbi) { // init the instance with _MeshVertexProgram infos if(_MeshVertexProgram) _MeshVertexProgram->initInstance(mbi); } // *************************************************************************** bool CMeshMRMGeom::clip(const std::vector<CPlane> &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 CMeshMRMGeom::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); } // If lod chosen is not loaded, take the best loaded. if(numLod>=(sint)_NbLodLoaded) { numLod= _NbLodLoaded-1; alphaLod= 1; } return numLod; } // *************************************************************************** void CMeshMRMGeom::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<CMeshBaseInstance*>(trans); // get a ptr on scene CScene *ownerScene= mi->getOwnerScene(); // get a ptr on renderTrav CRenderTrav *renderTrav= &ownerScene->getRenderTrav(); // 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; // Update the vertexBufferHard (if possible). // \toto yoyo: TODO_OPTIMIZE: allocate only what is needed for the current Lod (Max of all instances, like // the loading....) (see loadHeader()). // 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_MeshMRMGeom_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(&_VBufferOriginal, &_VBufferFinal, 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(&_VBufferOriginal, &_VBufferFinal, useTangentSpace); _MeshMorpher.update (mi->getBlendShapeFactors()); } } // Skinning. //=========== // if mesh is skinned (but here skin not applied), we must copy vertices/normals from original vertices. if (_Skinned) { // do it for this Lod only, and if cache say it is necessary. if (!lod.OriginalSkinRestored) restoreOriginalSkinPart(lod); } // 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 //=========== // 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, mi->getOwnerScene(), mi, invertedObjectMatrix, renderTrav->CamPos); } // Render the lod. //=========== // active VB. drv->activeVertexBuffer(_VBufferFinal); // 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;i<lod.RdrPass.size();i++) { CRdrPass &rdrPass= lod.RdrPass[i]; if ( ( (mi->Materials[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, &_VBufferFinal); } // Render drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); // Resetup material/driver blender.restoreRender(material, drv, gaDisableZWrite); } } } else { for(uint i=0;i<lod.RdrPass.size();i++) { CRdrPass &rdrPass= lod.RdrPass[i]; if ( ( (mi->Materials[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, &_VBufferFinal); } // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } } } // End VertexProgram effect if(useMeshVP) { // end it. _MeshVertexProgram->end(drv); } // bkup force normalisation. drv->forceNormalize(bkupNorm); } // *************************************************************************** void CMeshMRMGeom::renderSkin(CTransformShape *trans, float alphaMRM) { H_AUTO( NL3D_MeshMRMGeom_renderSkin ); if(_Lods.size()==0) return; // get the meshMRM instance. only CMeshMRMInstance is possible when skinned (not MultiLod) CMeshMRMInstance *mi= safe_cast<CMeshMRMInstance*>(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); // choose the lod. float alphaLod; sint numLod= chooseLod(alphaMRM, alphaLod); // Render the choosen Lod. CLod &lod= _Lods[numLod]; if(lod.RdrPass.size()==0) return; /* YOYO: renderSkin() no more support vertexBufferHard()!!! for AGP Memory optimisation concern. AGP Skin rendering is made when supportSkinGrouping() is true Hence if a skin is to be rendered here, because it doesn't have a good vertex format, or it has MeshVertexProgram etc..., it will be rendered WITHOUT VBHard => slower. */ // get the skeleton model to which I am skinned CSkeletonModel *skeleton; skeleton = mi->getSkeletonModel(); // must be skinned for renderSkin() nlassert(_Skinned && mi->isSkinned() && skeleton); bool bMorphApplied = _MeshMorpher.BlendShapes.size() > 0; bool useNormal= (_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)!=0; bool useTangentSpace = _MeshVertexProgram && _MeshVertexProgram->needTangentSpace(); // Profiling //=========== H_AUTO( NL3D_MeshMRMGeom_renderSkin_go ); // Morphing // ======== if (bMorphApplied) { // Since Skinned we must update original skin vertices and normals because skinning use it _MeshMorpher.initSkinned(&_VBufferOriginal, &_VBufferFinal, useTangentSpace, &_OriginalSkinVertices, &_OriginalSkinNormals, useTangentSpace ? &_OriginalTGSpace : NULL, true ); _MeshMorpher.updateSkinned (mi->getBlendShapeFactors()); } // Skinning. //=========== // Never use RawSkin. Actually used in skinGrouping. updateRawSkinNormal(false, mi, numLod); // applySkin. //-------- // If skin without normal (rare/useful?) always simple (slow) case. if(!useNormal) { // skinning with just position applySkin (lod, skeleton); } else { // apply skin for this Lod only. if (!useTangentSpace) { // skinning with normal, but no tangent space applySkinWithNormal (lod, skeleton); } else { // Tangent space stored in the last texture coordinate applySkinWithTangentSpace(lod, skeleton, _VBufferFinal.getNumTexCoordUsed() - 1); } } // endSkin. //-------- // dirt this lod part. (NB: this is not optimal, but sufficient :) ). lod.OriginalSkinRestored= false; // NB: the skeleton matrix has already been setuped by CSkeletonModel // NB: the normalize flag has already been setuped by CSkeletonModel // Geomorph. //=========== // Geomorph the choosen Lod (if not the coarser mesh). if(numLod>0) { applyGeomorph(lod.Geomorphs, alphaLod); } // 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, mi->getOwnerScene(), mi, invertedObjectMatrix, renderTrav->CamPos); } // Render the lod. //=========== // active VB. drv->activeVertexBuffer(_VBufferFinal); // Render all pass. for(uint i=0;i<lod.RdrPass.size();i++) { CRdrPass &rdrPass= lod.RdrPass[i]; // CMaterial Ref CMaterial &material=mi->Materials[rdrPass.MaterialId]; // Setup VP material if (useMeshVP) { _MeshVertexProgram->setupForMaterial(material, drv, ownerScene, &_VBufferFinal); } // Render with the Materials of the MeshInstance. drv->activeIndexBuffer(rdrPass.PBlock); drv->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } // End VertexProgram effect if(useMeshVP) { // end it. _MeshVertexProgram->end(drv); } } // *************************************************************************** bool CMeshMRMGeom::supportSkinGrouping() const { return _SupportSkinGrouping; } // *************************************************************************** sint CMeshMRMGeom::renderSkinGroupGeom(CMeshMRMInstance *mi, float alphaMRM, uint remainingVertices, uint8 *vbDest) { H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom ) // NB: not need to test if _Lods.empty(), because already done through supportSkinGrouping() // 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(_Skinned && mi->isSkinned() && skeleton); bool bMorphApplied = _MeshMorpher.BlendShapes.size() > 0; bool useNormal= (_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag)!=0; nlassert(useNormal); // Profiling //=========== H_AUTO( NL3D_MeshMRMGeom_rdrSkinGrpGeom_go ); // Morphing // ======== // Use RawSkin?. compute before morphing, cause of updateRawSkin updateRawSkinNormal(true, mi, numLod); nlassert(mi->_RawSkinCache); // Apply morph if (bMorphApplied) { // No need to manage lod.OriginalSkinRestored, since in case of SkinGroupGeom, the VBuffer final is not modified. // copy directly from the original VB, and apply BlendShapes. Dest is directly the RawSkin _MeshMorpher.updateRawSkin(&_VBufferFinal, mi->_RawSkinCache->VertexRemap, mi->getBlendShapeFactors()); } // Skinning. //=========== // NB: the skeleton matrix has already been setuped by CSkeletonModel // NB: the normalize flag has already been setuped by CSkeletonModel // applySkin with RawSkin. //-------- // always RawSkin now in SkinGrouping, even with MeshMorpher { 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 CMeshMRMGeom::renderSkinGroupPrimitives(CMeshMRMInstance *mi, uint baseVertex, std::vector<CSkinSpecularRdrPass> &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;i<lod.RdrPass.size();i++) { CRdrPass &rdrPass= lod.RdrPass[i]; // CMaterial Ref CMaterial &material=mi->Materials[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<ITexture> pTexFile= new CTextureFile("fy_hom_visage_c1_fy_e1.tga"); material.setTexture(0, pTexFile ); // Remove Specular. if(material.getShader()==CMaterial::Specular) { CSmartPtr<ITexture> 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 CMeshMRMGeom::renderSkinGroupSpecularRdrPass(CMeshMRMInstance *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 CMeshMRMGeom::updateShiftedTriangleCache(CMeshMRMInstance *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<CIndexBuffer*> pbList; pbList.clear(); if(mi->_RawSkinCache) { pbList.resize(mi->_RawSkinCache->RdrPass.size()); for(uint i=0;i<pbList.size();i++) { pbList[i]= &mi->_RawSkinCache->RdrPass[i]; } } else { CLod &lod= _Lods[curLodId]; pbList.resize(lod.RdrPass.size()); for(uint i=0;i<pbList.size();i++) { pbList[i]= &lod.RdrPass[i].PBlock; } } // 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<pbList.size();i++) { mi->_ShiftedTriangleCache->RdrPass[i].NumTriangles= pbList[i]->getNumIndexes()/3; totalTri+= pbList[i]->getNumIndexes()/3; } // Allocate triangles indices. mi->_ShiftedTriangleCache->RawIndices.setFormat(NL_MESH_MRM_INDEX_FORMAT); mi->_ShiftedTriangleCache->RawIndices.setNumIndexes(totalTri*3); // Lock the index buffer CIndexBufferReadWrite ibaWrite; mi->_ShiftedTriangleCache->RawIndices.lock (ibaWrite); if (ibaWrite.getFormat() == CIndexBuffer::Indices32) { uint32 *dstPtr = (uint32 *) ibaWrite.getPtr(); // Second pass, fill ptrs, and fill Arrays uint indexTri= 0; for(i=0;i<pbList.size();i++) { CShiftedTriangleCache::CRdrPass &dstRdrPass= mi->_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); nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32); const uint32 *pSrcTri= (const uint32 *) ibaRead.getPtr(); uint32 *pDstTri= dstPtr+dstRdrPass.Triangles; for(;nIds>0;nIds--,pSrcTri++,pDstTri++) *pDstTri= *pSrcTri + baseVertex; } // Next indexTri+= dstRdrPass.NumTriangles; } } else { nlassert(ibaWrite.getFormat() == CIndexBuffer::Indices16); uint16 *dstPtr = (uint16 *) ibaWrite.getPtr(); // Second pass, fill ptrs, and fill Arrays uint indexTri= 0; for(i=0;i<pbList.size();i++) { CShiftedTriangleCache::CRdrPass &dstRdrPass= mi->_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); nlassert(ibaRead.getFormat() == CIndexBuffer::Indices16); const uint16 *pSrcTri= (const uint16 *) ibaRead.getPtr(); uint16 *pDstTri= (uint16 *) dstPtr+dstRdrPass.Triangles; for(;nIds>0;nIds--,pSrcTri++,pDstTri++) *pDstTri= *pSrcTri + baseVertex; } // Next indexTri+= dstRdrPass.NumTriangles; } } } } // *************************************************************************** void CMeshMRMGeom::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { // because of complexity, serial is separated in save / load. if(f.isReading()) load(f); else save(f); } // *************************************************************************** sint CMeshMRMGeom::loadHeader(NLMISC::IStream &f) throw(NLMISC::EStream) { /* Version 5: - Shadow Skinning Version 4: - serial SkinWeights per MRM, not per Lod Version 3: - Bones names. Version 2: - Mesh Vertex Program. Version 1: - added blend shapes Version 0: - base version. */ sint ver= f.serialVersion(5); // if >= version 3, serial boens names if(ver>=3) { f.serialCont (_BonesName); } // Version3-: Bones index are in skeleton model id list _BoneIdComputed = (ver < 3); // Must always recompute usage of parents of bones used. _BoneIdExtended = false; // Mesh Vertex Program. if (ver >= 2) { IMeshVertexProgram *mvp= NULL; f.serialPolyPtr(mvp); _MeshVertexProgram= mvp; } else { // release vp _MeshVertexProgram= NULL; } // blend shapes if (ver >= 1) f.serial (_MeshMorpher); // serial Basic info. // ================== f.serial(_Skinned); 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); // preload the Lods. f.serialCont(_LodInfos); // read/save number of wedges. /* NB: prepare memory space too for vertices. \todo yoyo: TODO_OPTIMIZE. for now there is no Lod memory profit with vertices / skinWeights. But resizing arrays is a problem because of reallocation... */ uint32 nWedges; f.serial(nWedges); // Prepare the VBuffer. _VBufferFinal.serialHeader(f); // If skinned, must allocate skinWeights. contReset(_SkinWeights); if(_Skinned) { _SkinWeights.resize(nWedges); } // If new version, serial SkinWeights in header, not in lods. if(ver >= 4) { f.serialCont(_SkinWeights); } // if >= version 5, serial Shadow Skin Information if(ver>=5) { f.serialCont (_ShadowSkin.Vertices); f.serialCont (_ShadowSkin.Triangles); } // Serial lod offsets. // ================== // This is the reference pos, to load / save relative offsets. sint32 startPos = f.getPos(); // Those are the lodOffsets, relative to startPos. vector<sint32> lodOffsets; lodOffsets.resize(_LodInfos.size(), 0); // read all relative offsets, and build the absolute offset of LodInfos. for(uint i=0;i<_LodInfos.size(); i++) { f.serial(lodOffsets[i]); _LodInfos[i].LodOffset= startPos + lodOffsets[i]; } // resest the Lod arrays. NB: each Lod is empty, and ready to receive Lod data. // ================== contReset(_Lods); _Lods.resize(_LodInfos.size()); // Flag the fact that no lod is loaded for now. _NbLodLoaded= 0; // Inform that the mesh data has changed dirtMeshDataId(); // Some runtime not serialized compilation compileRunTime(); // return version of the header return ver; } // *************************************************************************** void CMeshMRMGeom::load(NLMISC::IStream &f) throw(NLMISC::EStream) { // Load the header of the stream. // ================== sint verHeader= loadHeader(f); // Read All lod subsets. // ================== for(uint i=0;i<_LodInfos.size(); i++) { // read the lod face data. f.serial(_Lods[i]); // read the lod vertex data. serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges); // if reading, must bkup all original vertices from VB. // this is done in serialLodVertexData(). by subset } // Now, all lods are loaded. _NbLodLoaded= (uint)_Lods.size(); // If version doen't have boneNames, must build BoneId now. if(verHeader <= 2) { buildBoneUsageVer2 (); } } // *************************************************************************** void CMeshMRMGeom::save(NLMISC::IStream &f) throw(NLMISC::EStream) { /* Version 5: - Shadow Skinning Version 4: - serial SkinWeights per MRM, not per Lod Version 3: - Bones names. Version 2: - Mesh Vertex Program. Version 1: - added blend shapes Version 0: - base version. */ sint ver= f.serialVersion(5); uint i; // if >= version 3, serial bones names f.serialCont (_BonesName); // Warning, if you have skinned this shape, you can't write it anymore because skinning id have been changed! nlassert (_BoneIdComputed==false); // Mesh Vertex Program. if (ver >= 2) { IMeshVertexProgram *mvp= NULL; mvp= _MeshVertexProgram; f.serialPolyPtr(mvp); } // blend shapes if (ver >= 1) f.serial (_MeshMorpher); // must have good original Skinned Vertex before writing. if( _Skinned ) { restoreOriginalSkinVertices(); } // serial Basic info. // ================== f.serial(_Skinned); 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); f.serialCont(_LodInfos); // save number of wedges. uint32 nWedges; nWedges= _VBufferFinal.getNumVertices(); f.serial(nWedges); // Save the VBuffer header. _VBufferFinal.serialHeader(f); // If new version, serial SkinWeights in header, not in lods. if(ver >= 4) { f.serialCont(_SkinWeights); } // if >= version 5, serial Shadow Skin Information if(ver>=5) { f.serialCont (_ShadowSkin.Vertices); f.serialCont (_ShadowSkin.Triangles); } // Serial lod offsets. // ================== // This is the reference pos, to load / save relative offsets. sint32 startPos = f.getPos(); // Those are the lodOffsets, relative to startPos. vector<sint32> lodOffsets; lodOffsets.resize(_LodInfos.size(), 0); // write all dummy offset. For now (since we don't know what to set), compute the offset of // the sint32 to come back in serial lod parts below. for(i=0;i<_LodInfos.size(); i++) { lodOffsets[i]= f.getPos(); f.serial(lodOffsets[i]); } // Serial lod subsets. // ================== // Save all the lods. for(i=0;i<_LodInfos.size(); i++) { // get current absolute position. sint32 absCurPos= f.getPos(); // come back to "relative lodOffset" absolute position in the stream. (temp stored in lodOffset[i]). f.seek(lodOffsets[i], NLMISC::IStream::begin); // write the relative position of the lod to the stream. sint32 relCurPos= absCurPos - startPos; f.serial(relCurPos); // come back to absCurPos, to save the lod. f.seek(absCurPos, NLMISC::IStream::begin); // And so now, save the lod. // write the lod face data. f.serial(_Lods[i]); // write the lod vertex data. serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges); } } // *************************************************************************** void CMeshMRMGeom::serialLodVertexData(NLMISC::IStream &f, uint startWedge, uint endWedge) { /* Version 1: - serial SkinWeights per MRM, not per Lod */ sint ver= f.serialVersion(1); // VBuffer part. _VBufferFinal.serialSubset(f, startWedge, endWedge); // SkinWeights. if(_Skinned) { // Serialize SkinWeight per lod only for old versions. if(ver<1) { for(uint i= startWedge; i<endWedge; i++) { f.serial(_SkinWeights[i]); } } // if reading, must copy original vertices from VB. if( f.isReading()) { bkupOriginalSkinVerticesSubset(startWedge, endWedge); } } } // *************************************************************************** void CMeshMRMGeom::loadFirstLod(NLMISC::IStream &f) { // Load the header of the stream. // ================== sint verHeader= loadHeader(f); // If empty MRM, quit. if(_LodInfos.size()==0) return; /* If the version is <4, then SkinWeights are serialised per Lod. But for computebonesId(), we must have all SkinWeights RIGHT NOW. Hence, if too old version (<4), serialize all the MRM.... */ uint numLodToLoad; if(verHeader<4) numLodToLoad= (uint)_LodInfos.size(); else numLodToLoad= 1; // Read lod subset(s). // ================== for(uint i=0;i<numLodToLoad; i++) { // read the lod face data. f.serial(_Lods[i]); // read the lod vertex data. serialLodVertexData(f, _LodInfos[i].StartAddWedge, _LodInfos[i].EndAddWedges); // if reading, must bkup all original vertices from VB. // this is done in serialLodVertexData(). by subset } // Now, just first lod is loaded (but if too old file) _NbLodLoaded= numLodToLoad; // If version doen't have boneNames, must build BoneId now. if(verHeader <= 2) { buildBoneUsageVer2 (); } } // *************************************************************************** void CMeshMRMGeom::loadNextLod(NLMISC::IStream &f) { // If all is loaded, quit. if(getNbLodLoaded() == getNbLod()) return; // Set pos to good lod. f.seek(_LodInfos[_NbLodLoaded].LodOffset, NLMISC::IStream::begin); // Serial this lod data. // read the lod face data. f.serial(_Lods[_NbLodLoaded]); // read the lod vertex data. serialLodVertexData(f, _LodInfos[_NbLodLoaded].StartAddWedge, _LodInfos[_NbLodLoaded].EndAddWedges); // if reading, must bkup all original vertices from VB. // this is done in serialLodVertexData(). by subset // Inc LodLoaded count. _NbLodLoaded++; } // *************************************************************************** void CMeshMRMGeom::unloadNextLod(NLMISC::IStream &f) { // If just first lod remain (or no lod), quit if(getNbLodLoaded() <= 1) return; // Reset the entire Lod object. (Free Memory). contReset(_Lods[_NbLodLoaded-1]); // Dec LodLoaded count. _NbLodLoaded--; } // *************************************************************************** void CMeshMRMGeom::bkupOriginalSkinVertices() { nlassert(_Skinned); // bkup the entire array. bkupOriginalSkinVerticesSubset(0, _VBufferFinal.getNumVertices()); } // *************************************************************************** void CMeshMRMGeom::bkupOriginalSkinVerticesSubset(uint wedgeStart, uint wedgeEnd) { nlassert(_Skinned); CVertexBufferReadWrite vba; _VBufferFinal.lock (vba); // Copy VBuffer content into Original vertices normals. if(_VBufferFinal.getVertexFormat() & CVertexBuffer::PositionFlag) { // copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter). _OriginalSkinVertices.resize(_VBufferFinal.getNumVertices()); for(uint i=wedgeStart; i<wedgeEnd;i++) { _OriginalSkinVertices[i]= *vba.getVertexCoordPointer(i); } } if(_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag) { // copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter). _OriginalSkinNormals.resize(_VBufferFinal.getNumVertices()); for(uint i=wedgeStart; i<wedgeEnd;i++) { _OriginalSkinNormals[i]= *vba.getNormalCoordPointer(i); } } // is there tangent space added ? if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace()) { // yes, backup it nlassert(_VBufferFinal.getNumTexCoordUsed() > 0); uint tgSpaceStage = _VBufferFinal.getNumTexCoordUsed() - 1; _OriginalTGSpace.resize(_VBufferFinal.getNumVertices()); for(uint i=wedgeStart; i<wedgeEnd;i++) { _OriginalTGSpace[i]= *(CVector*)vba.getTexCoordPointer(i, tgSpaceStage); } } } // *************************************************************************** void CMeshMRMGeom::restoreOriginalSkinVertices() { nlassert(_Skinned); CVertexBufferReadWrite vba; _VBufferFinal.lock (vba); // Copy VBuffer content into Original vertices normals. if(_VBufferFinal.getVertexFormat() & CVertexBuffer::PositionFlag) { // copy vertices from VBuffer. (NB: useless geomorphed vertices are still copied, but doesn't matter). for(uint i=0; i<_VBufferFinal.getNumVertices();i++) { *vba.getVertexCoordPointer(i)= _OriginalSkinVertices[i]; } } if(_VBufferFinal.getVertexFormat() & CVertexBuffer::NormalFlag) { // copy normals from VBuffer. (NB: useless geomorphed normals are still copied, but doesn't matter). for(uint i=0; i<_VBufferFinal.getNumVertices();i++) { *vba.getNormalCoordPointer(i)= _OriginalSkinNormals[i]; } } if (_MeshVertexProgram && _MeshVertexProgram->needTangentSpace()) { uint numTexCoords = _VBufferFinal.getNumTexCoordUsed(); nlassert(numTexCoords >= 2); nlassert(_OriginalTGSpace.size() == _VBufferFinal.getNumVertices()); // copy tangent space vectors for(uint i = 0; i < _VBufferFinal.getNumVertices(); ++i) { *(CVector*)vba.getTexCoordPointer(i, numTexCoords - 1)= _OriginalTGSpace[i]; } } } // *************************************************************************** void CMeshMRMGeom::restoreOriginalSkinPart(CLod &lod) { nlassert(_Skinned); /* YOYO: _Skinned mrms no more support vertexBufferHard see note in renderSkin() */ // get vertexPtr / normalOff. //=========================== CVertexBufferReadWrite vba; _VBufferFinal.lock (vba); uint8 *destVertexPtr= (uint8*)vba.getVertexCoordPointer(); uint flags= _VBufferFinal.getVertexFormat(); sint32 vertexSize= _VBufferFinal.getVertexSize(); // must have XYZ. nlassert(flags & CVertexBuffer::PositionFlag); // Compute offset of each component of the VB. sint32 normalOff; if(flags & CVertexBuffer::NormalFlag) normalOff= _VBufferFinal.getNormalOff(); else normalOff= 0; // compute src array. CVector *srcVertexPtr; CVector *srcNormalPtr= NULL; srcVertexPtr= &_OriginalSkinVertices[0]; if(normalOff) srcNormalPtr= &(_OriginalSkinNormals[0]); // copy skinning. //=========================== for(uint i=0;i<NL3D_MESH_SKINNING_MAX_MATRIX;i++) { uint nInf= (uint)lod.InfluencedVertices[i].size(); if( nInf==0 ) continue; uint32 *infPtr= &(lod.InfluencedVertices[i][0]); // for all InfluencedVertices only. for(;nInf>0;nInf--, infPtr++) { uint index= *infPtr; CVector *srcVertex= srcVertexPtr + index; CVector *srcNormal= srcNormalPtr + index; uint8 *dstVertexVB= destVertexPtr + index * vertexSize; CVector *dstVertex= (CVector*)(dstVertexVB); CVector *dstNormal= (CVector*)(dstVertexVB + normalOff); // Vertex. *dstVertex= *srcVertex; // Normal. if(normalOff) *dstNormal= *srcNormal; } } // clean this lod part. (NB: this is not optimal, but sufficient :) ). lod.OriginalSkinRestored= true; } // *************************************************************************** float CMeshMRMGeom::getNumTriangles (float distance) { // NB: this is an approximation, but this is continious. return _LevelDetail.getNumTriangles(distance); } // *************************************************************************** void CMeshMRMGeom::computeBonesId (CSkeletonModel *skeleton) { // Already computed ? if (!_BoneIdComputed) { // Get a pointer on the skeleton nlassert (skeleton); if (skeleton) { // **** For each bones, compute remap std::vector<uint> 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 nlassert(_OriginalSkinVertices.size() == _SkinWeights.size()); static std::vector<CAABBox> boneBBoxes; static std::vector<bool> 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; for (vert=nGeomSpace; vert<_SkinWeights.size(); vert++) { // get the vertex position. CVector vertex= _OriginalSkinVertices[vert]; // For each weight uint weight; for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++) { // Active ? if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0)) { // Check id uint srcId= _SkinWeights[vert].MatrixId[weight]; nlassert (srcId < remap.size()); // remap _SkinWeights[vert].MatrixId[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]<remap.size()); // Remap _Lods[lod].MatrixInfluences[matrix] = remap[_Lods[lod].MatrixInfluences[matrix]]; } } // **** Remap Shadow Vertices. for(vert=0;vert<_ShadowSkin.Vertices.size();vert++) { CShadowVertex &v= _ShadowSkin.Vertices[vert]; // Check id nlassert (v.MatrixId < remap.size()); v.MatrixId= remap[v.MatrixId]; } // Computed _BoneIdComputed = true; } } // Already extended ? if (!_BoneIdExtended) { nlassert (skeleton); if (skeleton) { // the total bone Usage of the mesh. vector<bool> 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.size();i++) { // if the bone is used by the mesh, add it to BoneIdExt. if(boneUsage[i]) _BonesIdExt.push_back(i); } } // Extended _BoneIdExtended= true; } } // *************************************************************************** void CMeshMRMGeom::buildBoneUsageVer2 () { if(_Skinned) { // parse all vertices, couting MaxBoneId used. uint32 maxBoneId= 0; // for each vertex uint vert; for (vert=0; vert<_SkinWeights.size(); vert++) { // For each weight for (uint weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++) { // Active ? if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0)) { maxBoneId= max(_SkinWeights[vert].MatrixId[weight], maxBoneId); } } } // alloc an array of maxBoneId+1, reset to 0. std::vector<uint8> boneUsage; boneUsage.resize(maxBoneId+1, 0); // reparse all vertices, counting usage for each bone. for (vert=0; vert<_SkinWeights.size(); vert++) { // For each weight for (uint weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++) { // Active ? if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0)) { // mark this bone as used. boneUsage[_SkinWeights[vert].MatrixId[weight]]= 1; } } } // For each bone used _BonesId.clear(); for(uint i=0; i<boneUsage.size();i++) { // if the bone is used by the mesh, add it to BoneId. if(boneUsage[i]) _BonesId.push_back(i); } } } // *************************************************************************** void CMeshMRMGeom::updateSkeletonUsage(CSkeletonModel *sm, bool increment) { // For all Bones used. 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 CMeshMRMGeom::compileRunTime() { _PreciseClipping= _BBox.getRadius() >= NL3D_MESH_PRECISE_CLIP_THRESHOLD; // Compute if can support SkinGrouping rendering if(_Lods.size()==0 || !_Skinned) { _SupportSkinGrouping= false; _SupportShadowSkinGrouping= false; } else { // The Mesh must follow those restrictions, to support group skinning _SupportSkinGrouping= _VBufferFinal.getVertexFormat() == NL3D_MESH_SKIN_MANAGER_VERTEXFORMAT && _VBufferFinal.getNumVertices() < NL3D_MESH_SKIN_MANAGER_MAXVERTICES && !_MeshVertexProgram; // 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; } // Support MeshBlockRendering only if not skinned/meshMorphed. _SupportMeshBlockRendering= !_Skinned && _MeshMorpher.BlendShapes.size()==0; // \todo yoyo: support later MeshVertexProgram _SupportMeshBlockRendering= _SupportMeshBlockRendering && _MeshVertexProgram==NULL; } // *************************************************************************** void CMeshMRMGeom::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<CMeshBaseInstance*>(trans); // Profile all pass. uint triCount= 0; for (uint i=0;i<lod.RdrPass.size();i++) { CRdrPass &rdrPass= lod.RdrPass[i]; // Profile with the Materials of the MeshInstance. if ( ( (mi->Materials[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.MeshMRMProfileTriVBFormat, _VBufferFinal.getVertexFormat(), triCount); // VBHard if(_VBufferFinal.getPreferredMemory()!=CVertexBuffer::RAMPreferred) rdrTrav->Scene->BenchRes.NumMeshMRMVBufferHard++; else rdrTrav->Scene->BenchRes.NumMeshMRMVBufferStd++; // 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.NumMeshMRMRdrBlockWithVBHeap++; rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrBlockWithVBHeap+= triCount; } else { rdrTrav->Scene->BenchRes.NumMeshMRMRdrBlock++; rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrBlock+= triCount; } } else { rdrTrav->Scene->BenchRes.NumMeshMRMRdrNormal++; rdrTrav->Scene->BenchRes.NumMeshMRMTriRdrNormal+= triCount; } } } // *************************************************************************** bool CMeshMRMGeom::buildGeometryForLod(uint lodId, std::vector<CVector> &vertices, std::vector<uint32> &triangles) const { if(lodId>=_Lods.size()) return false; // avoid slow lock if(_VBufferFinal.isResident()) return false; // clear first vertices.clear(); triangles.clear(); // **** First, for the lod indicate what vertex is used or not. Also index geomorphs to know what real vertex is used static vector<sint> vertexRemap; vertexRemap.clear(); // -1 means "not used" vertexRemap.resize(_VBufferFinal.getNumVertices(), -1); // count also total number of indices uint numTotalIndices= 0; // Parse all triangles. uint i; for(i=0;i<getNbRdrPass(lodId);i++) { const CIndexBuffer &pb = getRdrPassPrimitiveBlock(lodId, i); // avoid slow lock if(pb.isResident()) return false; CIndexBufferRead iba; pb.lock (iba); nlassert(iba.getFormat()==NL_MESH_MRM_INDEX_FORMAT); TMeshMRMIndexType *src= (TMeshMRMIndexType *) iba.getPtr(); for(uint n= pb.getNumIndexes();n>0;n--, src++) { uint idx= *src; // Flag the vertex with its own index => used. vertexRemap[idx]= idx; } // total num indices numTotalIndices+= pb.getNumIndexes(); } // Special for Geomorphs: must take The End target vertex. const std::vector<CMRMWedgeGeom> &geomorphs= getGeomorphs(lodId); for(i=0;i<geomorphs.size();i++) { uint trueIdx= geomorphs[i].End; // map to the Geomorph Target. vertexRemap[i]= trueIdx; // mark also the real vertex used as used. vertexRemap[trueIdx]= trueIdx; } // if no index, abort if(numTotalIndices==0) return false; // **** count number of vertices really used (skip geomorphs) uint numUsedVertices=0; for(i=(uint)geomorphs.size();i<vertexRemap.size();i++) { if(vertexRemap[i]>=0) numUsedVertices++; } // if none, abort if(numUsedVertices==0) return false; // Then we have our vertices size vertices.resize(numUsedVertices); // **** Fill the vertex geometry { // Lock input VB CVertexBufferRead vba; _VBufferFinal.lock(vba); // get the start vert, beginning at end of geomorphs const uint8 *pSrcVert= (const uint8*)vba.getVertexCoordPointer((uint)geomorphs.size()); uint32 vertSize= _VBufferFinal.getVertexSize(); CVector *pDstVert= &vertices[0]; uint dstIndex= 0; // Then run all input vertices (skip geomorphs) for(i=(uint)geomorphs.size();i<vertexRemap.size();i++) { // if the vertex is used if(vertexRemap[i]>=0) { // Final remaping of vertex to final index vertexRemap[i]= dstIndex; // copy to dest *pDstVert= *(CVector*)pSrcVert; // next dest pDstVert++; dstIndex++; } // next src pSrcVert+= vertSize; } // should have fill the entire buffer nlassert(uint(pDstVert-(&vertices[0]))==numUsedVertices); // Resolve remapping for geomorphs for(i=0;i<geomorphs.size();i++) { // use the same final index as the EndGeomorph vertex uint endGeomIdx= vertexRemap[i]; vertexRemap[i]= vertexRemap[endGeomIdx]; } } // **** Fill the indices triangles.resize(numTotalIndices); uint32 *pDstIndex= &triangles[0]; // for all render pass for(i=0;i<getNbRdrPass(lodId);i++) { const CIndexBuffer &pb = getRdrPassPrimitiveBlock(lodId, i); CIndexBufferRead iba; pb.lock (iba); TMeshMRMIndexType *src= (TMeshMRMIndexType *) iba.getPtr(); for(uint n= pb.getNumIndexes();n>0;n--, src++) { // resolve any geomorph, and final remapping uint remapedIdx= vertexRemap[*src]; nlassert(remapedIdx<vertices.size()); *pDstIndex= remapedIdx; pDstIndex++; } } // should have filled the entire buffer nlassert(uint(pDstIndex-(&triangles[0]))==numTotalIndices); return true; } // *************************************************************************** // *************************************************************************** // Mesh Block Render Interface // *************************************************************************** // *************************************************************************** // *************************************************************************** bool CMeshMRMGeom::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; //return _SupportMeshBlockRendering; } // *************************************************************************** bool CMeshMRMGeom::sortPerMaterial() const { // Can't do it per material since 2 lods may not have the same number of RdrPass!! return false; } // *************************************************************************** uint CMeshMRMGeom::getNumRdrPassesForMesh() const { // not used... return 0; } // *************************************************************************** uint CMeshMRMGeom::getNumRdrPassesForInstance(CMeshBaseInstance *inst) const { return (uint)_Lods[_MBRCurrentLodId].RdrPass.size(); } // *************************************************************************** void CMeshMRMGeom::beginMesh(CMeshGeomRenderContext &rdrCtx) { if(_Lods.empty()) return; IDriver *drv= rdrCtx.Driver; if(rdrCtx.RenderThroughVBHeap) { // Don't setup VB in this case, since use the VBHeap setuped one. } else { drv->activeVertexBuffer(_VBufferFinal); } // force normalisation of normals.. _MBRBkupNormalize= drv->isForceNormalize(); drv->forceNormalize(true); } // *************************************************************************** void CMeshMRMGeom::activeInstance(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *inst, float polygonCount, void *vbDst) { H_AUTO( NL3D_MeshMRMGeom_RenderNormal ); if(_Lods.empty()) return; // get the result of the Load Balancing. float alphaMRM= _LevelDetail.getLevelDetailFromPolyCount(polygonCount); // choose the lod. float alphaLod; _MBRCurrentLodId= chooseLod(alphaMRM, alphaLod); // Geomorph the choosen Lod (if not the coarser mesh). if(_MBRCurrentLodId>0) { if(rdrCtx.RenderThroughVBHeap) applyGeomorphWithVBHardPtr(_Lods[_MBRCurrentLodId].Geomorphs, alphaLod, (uint8*)vbDst); else applyGeomorph(_Lods[_MBRCurrentLodId].Geomorphs, alphaLod); } // set the instance worldmatrix. rdrCtx.Driver->setupModelMatrix(inst->getWorldMatrix()); // setupLighting. inst->changeLightSetup(rdrCtx.RenderTrav); } // *************************************************************************** void CMeshMRMGeom::renderPass(CMeshGeomRenderContext &rdrCtx, CMeshBaseInstance *mi, float polygonCount, uint rdrPassId) { if(_Lods.empty()) return; CLod &lod= _Lods[_MBRCurrentLodId]; CRdrPass &rdrPass= lod.RdrPass[rdrPassId]; if ( mi->Materials[rdrPass.MaterialId].getBlend() == false ) { // CMaterial Ref CMaterial &material=mi->Materials[rdrPass.MaterialId]; // Render with the Materials of the MeshInstance. if(rdrCtx.RenderThroughVBHeap) { // render shifted primitives rdrCtx.Driver->activeIndexBuffer(rdrPass.VBHeapPBlock); rdrCtx.Driver->renderTriangles(material, 0, rdrPass.VBHeapPBlock.getNumIndexes()/3); } else { rdrCtx.Driver->activeIndexBuffer(rdrPass.PBlock); rdrCtx.Driver->renderTriangles(material, 0, rdrPass.PBlock.getNumIndexes()/3); } } } // *************************************************************************** void CMeshMRMGeom::endMesh(CMeshGeomRenderContext &rdrCtx) { if(_Lods.empty()) return; // bkup force normalisation. rdrCtx.Driver->forceNormalize(_MBRBkupNormalize); } // *************************************************************************** bool CMeshMRMGeom::getVBHeapInfo(uint &vertexFormat, uint &numVertices) { // CMeshMRMGeom support VBHeap rendering, assuming _SupportMeshBlockRendering is true vertexFormat= _VBufferFinal.getVertexFormat(); numVertices= _VBufferFinal.getNumVertices(); return _SupportMeshBlockRendering; } // *************************************************************************** void CMeshMRMGeom::computeMeshVBHeap(void *dst, uint indexStart) { // Fill dst with Buffer content. CVertexBufferRead vba; _VBufferFinal.lock (vba); memcpy(dst, vba.getVertexCoordPointer(), _VBufferFinal.getNumVertices()*_VBufferFinal.getVertexSize() ); // For All Lods for(uint lodId=0; lodId<_Lods.size();lodId++) { CLod &lod= _Lods[lodId]; // For all rdrPass. for(uint i=0;i<lod.RdrPass.size();i++) { // shift the PB CIndexBuffer &srcPb= lod.RdrPass[i].PBlock; CIndexBuffer &dstPb= lod.RdrPass[i].VBHeapPBlock; uint j; // Tris. CIndexBufferRead ibaRead; srcPb.lock (ibaRead); CIndexBufferReadWrite ibaWrite; srcPb.lock (ibaWrite); dstPb.setNumIndexes(srcPb.getNumIndexes()); // nico : apparently not used, so don't manage 16 bit index here nlassert(ibaRead.getIndexNumBytes() == sizeof(uint32)); nlassert(ibaWrite.getIndexNumBytes() == sizeof(uint32)); nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32); // nico : apparently not called for now const uint32 *srcTriPtr= (const uint32 *) ibaRead.getPtr(); uint32 *dstTriPtr= (uint32 *) ibaWrite.getPtr(); for(j=0; j<dstPb.getNumIndexes();j++) { dstTriPtr[j]= srcTriPtr[j]+indexStart; } } } } // *************************************************************************** bool CMeshMRMGeom::isActiveInstanceNeedVBFill() const { // Yes, need it for geomorph return true; } // *************************************************************************** // *************************************************************************** // CMeshMRM. // *************************************************************************** // *************************************************************************** // *************************************************************************** CMeshMRM::CMeshMRM() { } // *************************************************************************** void CMeshMRM::build (CMeshBase::CMeshBaseBuild &mBase, CMesh::CMeshBuild &m, std::vector<CMesh::CMeshBuild*> &listBS, const CMRMParameters ¶ms) { /// copy MeshBase info: materials .... CMeshBase::buildMeshBase (mBase); // Then build the geom. _MeshMRMGeom.build (m, listBS, (uint)mBase.Materials.size(), params); } // *************************************************************************** void CMeshMRM::build (CMeshBase::CMeshBaseBuild &m, const CMeshMRMGeom &mgeom) { /// copy MeshBase info: materials .... CMeshBase::buildMeshBase(m); // Then copy the geom. _MeshMRMGeom= mgeom; } // *************************************************************************** void CMeshMRM::optimizeMaterialUsage(std::vector<sint> &remap) { // For each material, count usage. vector<bool> materialUsed; materialUsed.resize(CMeshBase::_Materials.size(), false); for(uint lod=0;lod<getNbLod();lod++) { for(uint rp=0;rp<getNbRdrPass(lod);rp++) { uint matId= getRdrPassMaterial(lod, rp); // flag as used. materialUsed[matId]= true; } } // Apply it to meshBase CMeshBase::applyMaterialUsageOptim(materialUsed, remap); // Apply lut to meshGeom. _MeshMRMGeom.applyMaterialRemap(remap); } // *************************************************************************** CTransformShape *CMeshMRM::createInstance(CScene &scene) { // Create a CMeshMRMInstance, an instance of a mesh. //=============================================== CMeshMRMInstance *mi= (CMeshMRMInstance*)scene.createModel(NL3D::MeshMRMInstanceId); mi->Shape= 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 CMeshMRM::clip(const std::vector<CPlane> &pyramid, const CMatrix &worldMatrix) { return _MeshMRMGeom.clip(pyramid, worldMatrix); } // *************************************************************************** void CMeshMRM::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 CMeshMRM::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { /* Version 0: - base version. */ (void)f.serialVersion(0); // serial Materials infos contained in CMeshBase. CMeshBase::serialMeshBase(f); // serial the geometry. _MeshMRMGeom.serial(f); } // *************************************************************************** float CMeshMRM::getNumTriangles (float distance) { return _MeshMRMGeom.getNumTriangles (distance); } // *************************************************************************** const CMeshMRMGeom& CMeshMRM::getMeshGeom () const { return _MeshMRMGeom; } // *************************************************************************** void CMeshMRM::computeBonesId (CSkeletonModel *skeleton) { _MeshMRMGeom.computeBonesId (skeleton); } // *************************************************************************** void CMeshMRM::updateSkeletonUsage (CSkeletonModel *skeleton, bool increment) { _MeshMRMGeom.updateSkeletonUsage(skeleton, increment); } // *************************************************************************** void CMeshMRM::changeMRMDistanceSetup(float distanceFinest, float distanceMiddle, float distanceCoarsest) { _MeshMRMGeom.changeMRMDistanceSetup(distanceFinest, distanceMiddle, distanceCoarsest); } // *************************************************************************** IMeshGeom *CMeshMRM::supportMeshBlockRendering (CTransformShape *trans, float &polygonCount ) const { // Ok if meshGeom is ok. if(_MeshMRMGeom.supportMeshBlockRendering()) { polygonCount= trans->getNumTrianglesAfterLoadBalancing(); return (IMeshGeom*)&_MeshMRMGeom; } else return NULL; } // *************************************************************************** void CMeshMRM::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); } // *************************************************************************** void CMeshMRM::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(_MeshMRMGeom.isSkinned()) return; // Choose the best Lod available for system geometry if(_MeshMRMGeom.getNbLodLoaded()==0) return; uint lodId= _MeshMRMGeom.getNbLodLoaded()-1; // retrieve geometry (if VB/IB not resident) if( !_MeshMRMGeom.buildGeometryForLod(lodId, _SystemGeometry.Vertices, _SystemGeometry.Triangles) ) { _SystemGeometry.clear(); } // TestYoyo /*static uint32 totalMem= 0; totalMem+= _SystemGeometry.Vertices.size()*sizeof(CVector); totalMem+= _SystemGeometry.Triangles.size()*sizeof(uint32); nlinfo("CMeshMRM: TotalMem: %d", totalMem);*/ } // *************************************************************************** // *************************************************************************** // CMeshMRMGeom RawSkin optimisation // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshMRMGeom::dirtMeshDataId() { // see updateRawSkinNormal() _MeshDataId++; } // *************************************************************************** void CMeshMRMGeom::updateRawSkinNormal(bool enabled, CMeshMRMInstance *mi, sint curLodId) { if(!enabled) { // if the instance cache is not cleared, must clear. if(mi->_RawSkinCache) mi->clearRawSkinCache(); } else { // If the instance has no RawSkin, or has a too old RawSkin cache, must delete it, and recreate if( !mi->_RawSkinCache || mi->_RawSkinCache->MeshDataId!=_MeshDataId) { // first delete if too old. if(mi->_RawSkinCache) mi->clearRawSkinCache(); // Then recreate, and use _MeshDataId to verify that the instance works with same data. mi->_RawSkinCache= new CRawSkinNormalCache; 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 ); CVertexBufferRead vba; _VBufferFinal.lock (vba); CRawSkinNormalCache &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<uint8> softVertices; softVertices.clear(); softVertices.resize( _VBufferFinal.getNumVertices(), 0 ); for(i=0;i<lod.Geomorphs.size();i++) { softVertices[lod.Geomorphs[i].Start]= 1; softVertices[lod.Geomorphs[i].End]= 1; } // The remap from old index in _VBufferFinal to RawSkin vertices (without Geomorphs). static vector<TMeshMRMIndexType> 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<lod.InfluencedVertices[i].size();j++) { uint vid= lod.InfluencedVertices[i][j]; if(softVertices[vid]) softSize[i]++; else hardSize[i]++; } skinLod.TotalSoftVertices+= softSize[i]; skinLod.TotalHardVertices+= hardSize[i]; skinLod.SoftVertices[i]= softSize[i]; skinLod.HardVertices[i]= hardSize[i]; } // compute offsets softStart[0]= 0; hardStart[0]= skinLod.TotalSoftVertices; for(i=1;i<4;i++) { softStart[i]= softStart[i-1]+softSize[i-1]; hardStart[i]= hardStart[i-1]+hardSize[i-1]; } // compute remap for(i=0;i<4;i++) { uint softIdx= softStart[i]; uint hardIdx= hardStart[i]; for(uint j=0;j<lod.InfluencedVertices[i].size();j++) { uint vid= lod.InfluencedVertices[i][j]; if(softVertices[vid]) vertexRemap[vid]= softIdx++; else vertexRemap[vid]= hardIdx++; } } // Resize the dest array. skinLod.Vertices1.resize((uint32)lod.InfluencedVertices[0].size()); skinLod.Vertices2.resize((uint32)lod.InfluencedVertices[1].size()); skinLod.Vertices3.resize((uint32)lod.InfluencedVertices[2].size()); skinLod.Vertices4.resize((uint32)lod.InfluencedVertices[3].size()); // Remap for BlendShape. Lasts 2 bits tells what RawSkin Array to seek (1 to 4), // low Bits indicate the number in them. 0xFFFFFFFF is a special value indicating "NotUsed in this lod" static vector<uint32> vertexFinalRemap; vertexFinalRemap.clear(); vertexFinalRemap.resize(vertexRemap.size(), 0xFFFFFFFF); // 1 Matrix skinning. //======== for(i=0;i<skinLod.Vertices1.size();i++) { // get the dest vertex. uint vid= lod.InfluencedVertices[0][i]; // where to store? rawIdx= vertexRemap[vid]; if(softVertices[vid]) rawIdx-= softStart[0]; else rawIdx+= softSize[0]-hardStart[0]; // for BlendShapes remapping vertexFinalRemap[vid]= (0<<30) + rawIdx; // fill raw struct skinLod.Vertices1[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0]; skinLod.Vertices1[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid]; skinLod.Vertices1[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid]; skinLod.Vertices1[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid); } // 2 Matrix skinning. //======== for(i=0;i<skinLod.Vertices2.size();i++) { // get the dest vertex. uint vid= lod.InfluencedVertices[1][i]; // where to store? rawIdx= vertexRemap[vid]; if(softVertices[vid]) rawIdx-= softStart[1]; else rawIdx+= softSize[1]-hardStart[1]; // for BlendShapes remapping vertexFinalRemap[vid]= (1<<30) + rawIdx; // fill raw struct skinLod.Vertices2[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0]; skinLod.Vertices2[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1]; skinLod.Vertices2[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0]; skinLod.Vertices2[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1]; skinLod.Vertices2[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid]; skinLod.Vertices2[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid]; skinLod.Vertices2[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid); } // 3 Matrix skinning. //======== for(i=0;i<skinLod.Vertices3.size();i++) { // get the dest vertex. uint vid= lod.InfluencedVertices[2][i]; // where to store? rawIdx= vertexRemap[vid]; if(softVertices[vid]) rawIdx-= softStart[2]; else rawIdx+= softSize[2]-hardStart[2]; // for BlendShapes remapping vertexFinalRemap[vid]= (2<<30) + rawIdx; // fill raw struct skinLod.Vertices3[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0]; skinLod.Vertices3[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1]; skinLod.Vertices3[rawIdx].MatrixId[2]= _SkinWeights[vid].MatrixId[2]; skinLod.Vertices3[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0]; skinLod.Vertices3[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1]; skinLod.Vertices3[rawIdx].Weights[2]= _SkinWeights[vid].Weights[2]; skinLod.Vertices3[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid]; skinLod.Vertices3[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid]; skinLod.Vertices3[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid); } // 4 Matrix skinning. //======== for(i=0;i<skinLod.Vertices4.size();i++) { // get the dest vertex. uint vid= lod.InfluencedVertices[3][i]; // where to store? rawIdx= vertexRemap[vid]; if(softVertices[vid]) rawIdx-= softStart[3]; else rawIdx+= softSize[3]-hardStart[3]; // for BlendShapes remapping vertexFinalRemap[vid]= (3<<30) + rawIdx; // fill raw struct skinLod.Vertices4[rawIdx].MatrixId[0]= _SkinWeights[vid].MatrixId[0]; skinLod.Vertices4[rawIdx].MatrixId[1]= _SkinWeights[vid].MatrixId[1]; skinLod.Vertices4[rawIdx].MatrixId[2]= _SkinWeights[vid].MatrixId[2]; skinLod.Vertices4[rawIdx].MatrixId[3]= _SkinWeights[vid].MatrixId[3]; skinLod.Vertices4[rawIdx].Weights[0]= _SkinWeights[vid].Weights[0]; skinLod.Vertices4[rawIdx].Weights[1]= _SkinWeights[vid].Weights[1]; skinLod.Vertices4[rawIdx].Weights[2]= _SkinWeights[vid].Weights[2]; skinLod.Vertices4[rawIdx].Weights[3]= _SkinWeights[vid].Weights[3]; skinLod.Vertices4[rawIdx].Vertex.Pos= _OriginalSkinVertices[vid]; skinLod.Vertices4[rawIdx].Vertex.Normal= _OriginalSkinNormals[vid]; skinLod.Vertices4[rawIdx].Vertex.UV= *vba.getTexCoordPointer(vid); } // Remap Geomorphs. //======== uint numGeoms= (uint)lod.Geomorphs.size(); skinLod.Geomorphs.resize( numGeoms ); for(i=0;i<numGeoms;i++) { // NB: don't add "numGeoms" to the index because RawSkin look in a TempArray in RAM, which start at 0... skinLod.Geomorphs[i].Start= vertexRemap[lod.Geomorphs[i].Start]; skinLod.Geomorphs[i].End= vertexRemap[lod.Geomorphs[i].End]; } // Remap RdrPass. //======== skinLod.RdrPass.resize(lod.RdrPass.size()); for(i=0;i<skinLod.RdrPass.size();i++) { // NB: since RawSkin is possible only with SkinGrouping, and since SkniGrouping is // possible only with no Quads/Lines, we should have only Tris here. // remap tris. skinLod.RdrPass[i].setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT); skinLod.RdrPass[i].setNumIndexes(lod.RdrPass[i].PBlock.getNumIndexes()); CIndexBufferRead ibaRead; lod.RdrPass[i].PBlock.lock (ibaRead); CIndexBufferReadWrite ibaWrite; skinLod.RdrPass[i].lock (ibaWrite); if (skinLod.RdrPass[i].getFormat() == CIndexBuffer::Indices32) { nlassert(ibaRead.getFormat() == CIndexBuffer::Indices32); const uint32 *srcTriPtr= (const uint32 *) ibaRead.getPtr(); uint32 *dstTriPtr= (uint32 *) ibaWrite.getPtr(); uint32 numIndices= lod.RdrPass[i].PBlock.getNumIndexes(); for(uint j=0;j<numIndices;j++, srcTriPtr++, dstTriPtr++) { uint vid= *srcTriPtr; // If this index refers to a Geomorphed vertex, don't modify! if(vid<numGeoms) *dstTriPtr= vid; else *dstTriPtr= vertexRemap[vid] + numGeoms; } } else { nlassert(ibaRead.getFormat() == CIndexBuffer::Indices16); const uint16 *srcTriPtr= (const uint16 *) ibaRead.getPtr(); uint16 *dstTriPtr= (uint16 *) ibaWrite.getPtr(); uint32 numIndices= lod.RdrPass[i].PBlock.getNumIndexes(); for(uint j=0;j<numIndices;j++, srcTriPtr++, dstTriPtr++) { uint vid= *srcTriPtr; // If this index refers to a Geomorphed vertex, don't modify! if(vid<numGeoms) *dstTriPtr= vid; else *dstTriPtr= vertexRemap[vid] + numGeoms; } } } // Case of MeshMorpher //======== if(_MeshMorpher.BlendShapes.size()>0) { skinLod.VertexRemap.resize((uint32)vertexFinalRemap.size()); for(i=0;i<vertexFinalRemap.size();i++) { uint vfr= vertexFinalRemap[i]; if(vfr!=0xFFFFFFFF) { uint rsArrayId= vfr >> 30; uint rsIndex= vfr & ((1<<30)-1); switch(rsArrayId) { case 0: skinLod.VertexRemap[i]= &skinLod.Vertices1[rsIndex].Vertex; break; case 1: skinLod.VertexRemap[i]= &skinLod.Vertices2[rsIndex].Vertex; break; case 2: skinLod.VertexRemap[i]= &skinLod.Vertices3[rsIndex].Vertex; break; case 3: skinLod.VertexRemap[i]= &skinLod.Vertices4[rsIndex].Vertex; break; }; } else skinLod.VertexRemap[i]= NULL; } } } } } // *************************************************************************** // *************************************************************************** // CMeshMRMGeom Shadow Skin Rendering // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMeshMRMGeom::setShadowMesh(const std::vector<CShadowVertex> &shadowVertices, const std::vector<uint32> &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 CMeshMRMGeom::getNumShadowSkinVertices() const { return (uint)_ShadowSkin.Vertices.size(); } // *************************************************************************** sint CMeshMRMGeom::renderShadowSkinGeom(CMeshMRMInstance *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. //=========== // skinning with normal, but no tangent space // 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<CMatrix3x4> 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 CMeshMRMGeom::renderShadowSkinPrimitives(CMeshMRMInstance *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; NL_SET_IB_NAME(shiftedTris, "CMeshMRMGeom::renderShadowSkinPrimitives::shiftedTris"); if(shiftedTris.getNumIndexes()<_ShadowSkin.Triangles.size()) { shiftedTris.setFormat(NL_MESH_MRM_INDEX_FORMAT); shiftedTris.setNumIndexes((uint32)_ShadowSkin.Triangles.size()); } shiftedTris.setPreferredMemory(CIndexBuffer::RAMVolatile, false); { CIndexBufferReadWrite iba; shiftedTris.lock(iba); const uint32 *src= &_ShadowSkin.Triangles[0]; TMeshMRMIndexType *dst= (TMeshMRMIndexType *) iba.getPtr(); for(uint n= (uint)_ShadowSkin.Triangles.size();n>0;n--, src++, dst++) { *dst= (TMeshMRMIndexType)(*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 CMeshMRMGeom::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(boneId<skeleton->Bones.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 Sphere compute nlassert(_OriginalSkinVertices.size() == _SkinWeights.size()); bool bbEmpty= true; // Remap the vertex, and compute the wanted bone bbox // for true vertices for (uint vert=nGeomSpace; vert<_SkinWeights.size(); vert++) { // get the vertex position. CVector vertex= _OriginalSkinVertices[vert]; // For each weight uint weight; for (weight=0; weight<NL3D_MESH_SKINNING_MAX_MATRIX; weight++) { // Active ? if ((_SkinWeights[vert].Weights[weight]>0)||(weight==0)) { // Check id is the wanted one if(_SkinWeights[vert].MatrixId[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; } // *************************************************************************** bool CMeshMRMGeom::intersectSkin(CMeshMRMInstance *mi, const CMatrix &toRaySpace, float &dist2D, float &distZ, bool computeDist2D) { // for MeshMRM, Use the Shadow Skinning (simple and light version). // 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); } } // NL3D