// 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 "nel/misc/path.h" #include "nel/3d/mesh_mrm_skinned.h" #include "nel/3d/mesh_mrm.h" #include "nel/3d/mesh.h" #include "nel/misc/file.h" #include "nel/misc/path.h" #include "nel/3d/stripifier.h" #include "nel/3d/register_3d.h" using namespace NLMISC; using namespace NL3D; using namespace std; // *************************************************************************** const CIndexBuffer *getRdrPassPrimitiveBlock(const CMeshMRMGeom *mesh, uint lodId, uint renderPass) { return &(mesh->getRdrPassPrimitiveBlock(lodId, renderPass)); } // *************************************************************************** const CIndexBuffer *getRdrPassPrimitiveBlock(const CMeshMRMSkinnedGeom *mesh, uint lodId, uint renderPass) { static CIndexBuffer block; mesh->getRdrPassPrimitiveBlock(lodId, renderPass, block); return █ } // *************************************************************************** template<class T> void addShadowMesh(T *meshIn, float paramFaceRatio, sint paramMaxFace, const std::vector<CMesh::CSkinWeight> &skinWeights, const CVertexBuffer &vertexBuffer) { CVertexBufferRead vba; vertexBuffer.lock (vba); uint i, j; // **** Select the Lod. uint numLods= meshIn->getNbLod(); // get the max tris displayed float numMeshFacesMin= (float)meshIn->getLevelDetail().MinFaceUsed; float numMeshFacesMax= (float)meshIn->getLevelDetail().MaxFaceUsed; float maxFaceWanted= numMeshFacesMax * paramFaceRatio; // minimize with maxFace param maxFaceWanted= min(maxFaceWanted, (float)paramMaxFace); // find the lod float fLod= ( (maxFaceWanted-numMeshFacesMin) / (numMeshFacesMax-numMeshFacesMin) )*(numLods-1); // round the lod wanted. sint lodId= (sint)floor(0.5f + fLod); clamp(lodId, 0, (sint)numLods-1); // **** First, for the best lod indicate what vertex is used or not. Also index geomorphs to know what real vertex is used vector<sint> vertexUsed; // -1 means "not used" vertexUsed.resize(skinWeights.size(), -1); // Parse all triangles. for(i=0;i<meshIn->getNbRdrPass(lodId);i++) { const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i); CIndexBufferRead iba; pb->lock (iba); if (iba.getFormat() == CIndexBuffer::Indices32) { const uint32 *triPtr= (const uint32 *) iba.getPtr(); for(j=0;j<pb->getNumIndexes();j++) { uint idx= *triPtr; // Flag the vertex with its own index => used. vertexUsed[idx]= idx; triPtr++; } } else { const uint16 *triPtr= (const uint16 *) iba.getPtr(); for(j=0;j<pb->getNumIndexes();j++) { uint idx= *triPtr; // Flag the vertex with its own index => used. vertexUsed[idx]= idx; triPtr++; } } } // Special for Geomorphs: must take The End target vertex. const std::vector<CMRMWedgeGeom> &geomorphs= meshIn->getGeomorphs(lodId); for(i=0;i<geomorphs.size();i++) { uint trueIdx= geomorphs[i].End; // map to the Geomorph Target. vertexUsed[i]= trueIdx; // mark also the real vertex used as used. vertexUsed[trueIdx]= trueIdx; } // **** For all vertices used (not geomorphs), compute vertex Skins. vector<CShadowVertex> shadowVertices; vector<sint> vertexToVSkin; vertexToVSkin.resize(vertexUsed.size()); shadowVertices.reserve(vertexUsed.size()); // use a map to remove duplicates (because of UV/normal discontinuities before!!) map<CShadowVertex, uint> shadowVertexMap; uint numMerged= 0; // Skip Geomorphs. for(i=(uint)geomorphs.size();i<vertexUsed.size();i++) { // If this vertex is used. if(vertexUsed[i]!=-1) { // Build the vertex CShadowVertex shadowVert; shadowVert.Vertex= *(CVector*)vba.getVertexCoordPointer(i); // Select the best Matrix. CMesh::CSkinWeight sw= skinWeights[i]; float maxW= 0; uint matId= 0; for(j=0;j<NL3D_MESH_SKINNING_MAX_MATRIX;j++) { // if no more matrix influenced, stop if(sw.Weights[j]==0) break; if(sw.Weights[j]>maxW) { matId= sw.MatrixId[j]; maxW= sw.Weights[j]; } } shadowVert.MatrixId= matId; // If dont find the shadowVertex in the map. map<CShadowVertex, uint>::iterator it= shadowVertexMap.find(shadowVert); if(it==shadowVertexMap.end()) { // Append uint index= (uint)shadowVertices.size(); vertexToVSkin[i]= index; shadowVertices.push_back(shadowVert); shadowVertexMap.insert(make_pair(shadowVert, index)); } else { // Ok, map. vertexToVSkin[i]= it->second; numMerged++; } } } // Some Info on what have been merged. if(shadowVertices.size()) { // TestYoyo. /*nlinfo("%d Vertices have been merged. => %d %%", numMerged, 100*numMerged / (numMerged+shadowVertices.size()));*/ } // **** Get All Faces // Final List Of Triangles that match the bone. vector<uint32> shadowTriangles; shadowTriangles.reserve(1000); // Parse all input tri of the mesh. for(i=0;i<meshIn->getNbRdrPass(lodId);i++) { const CIndexBuffer *pb = getRdrPassPrimitiveBlock(meshIn, lodId, i); CIndexBufferRead iba; pb->lock (iba); if (iba.getFormat() == CIndexBuffer::Indices32) { const uint32 *triPtr= (const uint32 *) iba.getPtr(); for(j=0;j<pb->getNumIndexes();j++) { uint idx= *triPtr; // Get the real Vertex (ie not the geomporhed one). idx= vertexUsed[idx]; // Get the ShadowVertex associated idx= vertexToVSkin[idx]; shadowTriangles.push_back(idx); triPtr++; } } else { const uint16 *triPtr= (const uint16 *) iba.getPtr(); for(j=0;j<pb->getNumIndexes();j++) { uint idx= *triPtr; // Get the real Vertex (ie not the geomporhed one). idx= vertexUsed[idx]; // Get the ShadowVertex associated idx= vertexToVSkin[idx]; shadowTriangles.push_back(idx); triPtr++; } } } // Re-Optim VertexCache Hard usage if(shadowTriangles.size()) { CIndexBuffer pb; // fill pb.setNumIndexes((uint32)shadowTriangles.size()); { CIndexBufferReadWrite iba; pb.lock (iba); for(i=0;i<shadowTriangles.size()/3;i++) { iba.setTri(i*3, shadowTriangles[i*3 + 0], shadowTriangles[i*3 + 1], shadowTriangles[i*3 + 2]); } } // optimize CStripifier stripifier; stripifier.optimizeTriangles(pb, pb); // get. { CIndexBufferReadWrite iba; pb.lock (iba); if (iba.getFormat() == CIndexBuffer::Indices32) { const uint32 *triPtr= (const uint32 *) iba.getPtr(); for(i=0;i<shadowTriangles.size();i++) { shadowTriangles[i]= *triPtr; triPtr++; } } else { const uint16 *triPtr= (const uint16 *) iba.getPtr(); for(i=0;i<shadowTriangles.size();i++) { shadowTriangles[i]= *triPtr; triPtr++; } } } } // set meshIn->setShadowMesh(shadowVertices, shadowTriangles); } // *************************************************************************** void addShadowMeshIntro(CMeshMRMGeom *meshIn, float paramFaceRatio, sint paramMaxFace) { const std::vector<CMesh::CSkinWeight> &skinWeights= meshIn->getSkinWeights(); const CVertexBuffer &vertexBuffer= meshIn->getVertexBuffer(); addShadowMesh (meshIn, paramFaceRatio, paramMaxFace, skinWeights, vertexBuffer); } // *************************************************************************** void addShadowMeshIntro(CMeshMRMSkinnedGeom *meshIn, float paramFaceRatio, sint paramMaxFace) { std::vector<CMesh::CSkinWeight> skinWeights; meshIn->getSkinWeights(skinWeights); CVertexBuffer vertexBuffer; meshIn->getVertexBuffer(vertexBuffer); addShadowMesh (meshIn, paramFaceRatio, paramMaxFace, skinWeights, vertexBuffer); } // *************************************************************************** int main(int argc, char *argv[]) { // Filter addSearchPath NLMISC::createDebug(); NLMISC::InfoLog->addNegativeFilter ("adding the path"); NL3D::registerSerial3d(); if (argc <3 ) { string execName= CFile::getFilename(argv[0]); printf("%s add a ShadowSkin to a Skinned MRM Mesh\n", execName.c_str()); printf(" usage: %s shape_in shape_out [facePercentage] [maxFaces] \n", execName.c_str()); printf(" NB: shape_in and shape_out can be same file\n"); printf(" NB: facePercentage is a number between 0 and 100. It computes the maxFaces for the shadow according to the input Mesh.\n"); printf(" NB: maxFaces can be given manually. The min of the 2 parameters is taken.\n"); exit(-1); } // Parse args. string shapeNameIn= argv[1]; string shapeNameOut= argv[2]; sint percentageFace= 100; sint maxFace= INT_MAX; if(argc>=4) NLMISC::fromString(argv[3], percentageFace); if(argc>=5) NLMISC::fromString(argv[4], maxFace); // **** Load the Mesh In. IShape *pResult = NULL; CMeshMRM *pMesh; CShapeStream shapeStreamIn; CIFile shapeFileIn; if(!shapeFileIn.open(shapeNameIn)) { nlwarning("ERROR: cannot open input file: %s", shapeNameIn.c_str()); exit(-1); } shapeStreamIn.serial(shapeFileIn); pMesh= dynamic_cast<CMeshMRM*>(shapeStreamIn.getShapePointer()); shapeFileIn.close(); if(!pMesh) { CMeshMRMSkinned *pMeshSkinned= dynamic_cast<CMeshMRMSkinned*>(shapeStreamIn.getShapePointer()); if(!pMeshSkinned) { nlwarning("ERROR: Not a MRM Mesh nor a MRM Mesh skinned: %s", shapeNameIn.c_str()); exit(-1); } CMeshMRMSkinnedGeom *pMeshGeom= (CMeshMRMSkinnedGeom*)&pMeshSkinned->getMeshGeom(); // **** Add Shadow mesh. float faceRatio; clamp(percentageFace, 0, 100); faceRatio= (float)percentageFace/100; maxFace= max(0, maxFace); addShadowMeshIntro(pMeshGeom, faceRatio, maxFace); pResult = pMeshSkinned; } else { CMeshMRMGeom *pMeshGeom= (CMeshMRMGeom*)&pMesh->getMeshGeom(); if( !pMeshGeom->isSkinned() ) { nlwarning("ERROR: Not a Skinned MRM Mesh: %s", shapeNameIn.c_str()); exit(-1); } // **** Add Shadow mesh. float faceRatio; clamp(percentageFace, 0, 100); faceRatio= (float)percentageFace/100; maxFace= max(0, maxFace); addShadowMeshIntro(pMeshGeom, faceRatio, maxFace); pResult = pMesh; } // **** Save It CShapeStream shapeStreamOut; COFile shapeFileOut; if(!shapeFileOut.open(shapeNameOut) ) { nlwarning("ERROR: cannot open output file: %s", shapeNameOut.c_str()); exit(-1); } nlassert (pResult); shapeStreamOut.setShapePointer(pResult); shapeStreamOut.serial(shapeFileOut); shapeFileOut.close(); return 0; }