// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/mrm_builder.h" #include "nel/3d/mrm_parameters.h" using namespace NLMISC; using namespace std; namespace NL3D { // *************************************************************************** // *************************************************************************** // Tools Methods. // *************************************************************************** // *************************************************************************** // *************************************************************************** static bool findElement(vector &array, sint elt) { return find(array.begin(), array.end(), elt) != array.end(); } // *************************************************************************** static bool deleteElement(vector &array, sint elt) { bool found=false; vector::iterator it=array.begin(); while( (it=find(array.begin(), array.end(), elt)) != array.end() ) found=true, array.erase(it); return found; // Do not use remove<> since it desn't modify size ... (???) // This doesn't seem to work. //return remove(array.begin(), array.end(), elt)!=array.end(); } // *************************************************************************** // *************************************************************************** // Edge Cost methods. // *************************************************************************** // *************************************************************************** // *************************************************************************** bool CMRMBuilder::vertexHasOneWedge(sint numvertex) { CMRMVertex &vert= TmpVertices[numvertex]; for(sint attId=0;attId=0 && numwedge!=w) return false; else numwedge=w; } } return true; } // *************************************************************************** bool CMRMBuilder::vertexHasOneMaterial(sint numvertex) { sint matId=-1; CMRMVertex &vert= TmpVertices[numvertex]; for(sint i=0;i<(sint)vert.SharedFaces.size();i++) { sint m= TmpFaces[vert.SharedFaces[i]].MaterialId; if(matId>=0 && matId!=m) return false; else matId=m; } return true; } // *************************************************************************** bool CMRMBuilder::vertexContinue(sint numvertex) { return vertexHasOneWedge(numvertex) && vertexHasOneMaterial(numvertex); } // *************************************************************************** bool CMRMBuilder::vertexClosed(sint numvertex) { CMRMVertex &vert= TmpVertices[numvertex]; map EdgeShare; // Init to 0. sint i; for(i=0;i<(sint)vert.SharedFaces.size();i++) { CMRMFaceBuild &f=TmpFaces[vert.SharedFaces[i]]; EdgeShare[f.getEdge(0)]= 0; EdgeShare[f.getEdge(1)]= 0; EdgeShare[f.getEdge(2)]= 0; } // Inc count. for(i=0;i<(sint)vert.SharedFaces.size();i++) { CMRMFaceBuild &f=TmpFaces[vert.SharedFaces[i]]; EdgeShare[f.getEdge(0)]++; EdgeShare[f.getEdge(1)]++; EdgeShare[f.getEdge(2)]++; } // Test open edges. for(i=0;i<(sint)vert.SharedFaces.size();i++) { CMRMFaceBuild &f=TmpFaces[vert.SharedFaces[i]]; sint v0= f.Corner[0].Vertex; sint v1= f.Corner[1].Vertex; sint v2= f.Corner[2].Vertex; if(EdgeShare[f.getEdge(0)]<2 && (v0==numvertex || v1==numvertex)) return false; if(EdgeShare[f.getEdge(1)]<2 && (v1==numvertex || v2==numvertex)) return false; if(EdgeShare[f.getEdge(2)]<2 && (v0==numvertex || v2==numvertex)) return false; } return true; } // *************************************************************************** float CMRMBuilder::getDeltaFaceNormals(sint numvertex) { // return a positive value of Somme(|DeltaNormals|) / NNormals. CMRMVertex &vert= TmpVertices[numvertex]; float delta=0; CVector refNormal; sint nfaces=(sint)vert.SharedFaces.size(); for(sint i=0;i deletedFaces; sint i; for(i=0;i<(sint)Vertex1.SharedFaces.size();i++) { sint numFace= Vertex1.SharedFaces[i]; if(TmpFaces[numFace].hasVertex(v1)) deletedFaces.push_back(numFace); } sint matId=-1; // test if faces have same material. for(i=0;i<(sint)deletedFaces.size();i++) { sint m; m= TmpFaces[deletedFaces[i]].MaterialId; if(matId>=0 && matId!=m) return false; else matId=m; } // test if faces have same wedge (for all att). for(sint attId=0;attId=0 && numwedge1!=w) return false; else numwedge1=w; w= TmpFaces[deletedFaces[i]].getAssociatedWedge(attId, v1); if(numwedge2>=0 && numwedge2!=w) return false; else numwedge2=w; } } return true; } // *************************************************************************** bool CMRMBuilder::edgeNearUniqueMatFace(const CMRMEdge &edge) { sint v0= edge.v0; sint v1= edge.v1; CMRMVertex &Vertex1=TmpVertices[v0]; // build list sharing edge. vector deletedFaces; sint i; for(i=0;i<(sint)Vertex1.SharedFaces.size();i++) { sint numFace= Vertex1.SharedFaces[i]; if(TmpFaces[numFace].hasVertex(v1)) deletedFaces.push_back(numFace); } // test if faces are not isolated OneMaterial faces. for(i=0;i<(sint)deletedFaces.size();i++) { CMRMFaceBuild &f=TmpFaces[deletedFaces[i]]; if( !edgeContinue(f.getEdge(0)) && !edgeContinue(f.getEdge(1)) && !edgeContinue(f.getEdge(2))) return true; } return false; } // *************************************************************************** float CMRMBuilder::computeEdgeCost(const CMRMEdge &edge) { sint v1= edge.v0; sint v2= edge.v1; // more expensive is the edge, later it will collapse. // **** standard cost // compute size of the edge. float cost=(TmpVertices[v1].Current-TmpVertices[v2].Current).norm(); // compute "curvature" of the edge. float faceCost= (getDeltaFaceNormals(v1)+getDeltaFaceNormals(v2)); // totally plane faces (faceCost==0) must be collapsed with respect to size (and not random if cost==0). // else we may have Plane Mesh (like flags) that will collapse in a very ugly way. faceCost= max(faceCost, 0.01f); // modulate size with curvature. cost*= faceCost; // Like H.Hope, add a weight on discontinuities.. if( !vertexContinue(v1) && !vertexContinue(v2) ) { // Nb: don't do this on discontinuities edges, unless the unique material face will collapse (pffiou!!). if( edgeContinue(edge) || edgeNearUniqueMatFace(edge) ) cost*=4; } // **** Interface Sewing cost if(_HasMeshInterfaces) { // if the 2 vertices come from a Sewing Interface mesh (must be a real interface id) sint meshSewingId= TmpVertices[v1].InterfaceLink.InterfaceId; if( meshSewingId>=0 && TmpVertices[v2].InterfaceLink.InterfaceId>=0 ) { // if the 2 vertices come from the same Sewing Interface mesh if( meshSewingId == TmpVertices[v2].InterfaceLink.InterfaceId ) { // Then the edge is one of the sewing interface mesh. must do special things for it CMRMSewingMesh &sewingMesh= _SewingMeshes[meshSewingId]; uint dummy; // get the sewing edge id CMRMEdge sewingEdge; sewingEdge.v0= TmpVertices[v1].InterfaceLink.InterfaceVertexId; sewingEdge.v1= TmpVertices[v2].InterfaceLink.InterfaceVertexId; // if the current sewing lod want to collapse this edge sint collapseId= sewingMesh.mustCollapseEdge(_CurrentLodComputed, sewingEdge, dummy); if(collapseId>=0) { // Then set a negative priority (ie will collapse as soon as possible). from -N to -1. // NB: sort them according to collapseId cost= (float)(-sewingMesh.getNumCollapseEdge(_CurrentLodComputed) + collapseId); } else { // This edge must not collapse at this Lod, set an infinite priority (hope will never collapse). cost= FLT_MAX; } } else { /* The edge is between 2 interfaces but not the same. If we collide it we'll have holes! This problem arise if space beetween interfaces is small. eg: if we setup an interface beetween hair and head, and an other one beetween head and torso, then we'll have this problem in the back of the neck. The solution is to make a big big cost to hope we'll never collide them (else Holes...)!! Don't use FLT_MAX to still have a correct order if we don't have choice... */ cost*= 10000; } } } return cost; } // *************************************************************************** // *************************************************************************** // Collapse Methods. // *************************************************************************** // *************************************************************************** // *************************************************************************** bool CMRMBuilder::faceShareWedges(CMRMFaceBuild *face, sint attribId, sint numVertex1, sint numVertex2) { sint numWedge1= face->getAssociatedWedge(attribId, numVertex1); sint numWedge2= face->getAssociatedWedge(attribId, numVertex2); if(numWedge1<0) return false; if(numWedge2<0) return false; CMRMAttribute &w1= TmpAttributes[attribId][numWedge1]; CMRMAttribute &w2= TmpAttributes[attribId][numWedge2]; return w1.Shared && w2.Shared && w1.NbSharedFaces>0 && w2.NbSharedFaces>0; } // *************************************************************************** void CMRMBuilder::insertFaceIntoEdgeList(CMRMFaceBuild &f) { float len; if(f.ValidIt0) { len= computeEdgeCost(f.getEdge(0)); f. It0= EdgeCollapses.insert( TEdgeMap::value_type( len, CMRMEdgeFace(f.getEdge(0),&f) ) ); } if(f.ValidIt1) { len= computeEdgeCost(f.getEdge(1)); f. It1= EdgeCollapses.insert( TEdgeMap::value_type( len, CMRMEdgeFace(f.getEdge(1),&f) ) ); } if(f.ValidIt2) { len= computeEdgeCost(f.getEdge(2)); f. It2= EdgeCollapses.insert( TEdgeMap::value_type( len, CMRMEdgeFace(f.getEdge(2),&f) ) ); } } // *************************************************************************** void CMRMBuilder::removeFaceFromEdgeList(CMRMFaceBuild &f) { if(f.ValidIt0) EdgeCollapses.erase(f.It0); if(f.ValidIt1) EdgeCollapses.erase(f.It1); if(f.ValidIt2) EdgeCollapses.erase(f.It2); } // *************************************************************************** struct CTmpVertexWeight { uint32 MatrixId; float Weight; // For find(). bool operator==(const CTmpVertexWeight &o) const { return MatrixId==o.MatrixId; } // For sort(). bool operator<(const CTmpVertexWeight &o) const { return Weight>o.Weight; } }; // *************************************************************************** CMesh::CSkinWeight CMRMBuilder::collapseSkinWeight(const CMesh::CSkinWeight &sw1, const CMesh::CSkinWeight &sw2, float interValue) const { // If fast interpolation. if(interValue==0) return sw1; if(interValue==1) return sw2; // else, must blend a skinWeight: must identify matrix which exist in the 2 sws, and add new ones. uint nbMats1=0; uint nbMats2=0; static vector sws; sws.reserve(NL3D_MESH_SKINNING_MAX_MATRIX * 2); sws.clear(); // For all weights of sw1. uint i; for(i=0; i0) { // add it to the list. sws.push_back(vw); } // For skinning reduction. if(sw1.Weights[i]>0) nbMats1++; } // For all weights of sw1. for(i=0; i0) { // add it or add influence to the matrix. vector::iterator it= find(sws.begin(), sws.end(), vw); if(it== sws.end()) sws.push_back(vw); else it->Weight+= vw.Weight; } // For skinning reduction. if(sw2.Weights[i]>0) nbMats2++; } // Then keep just the best. // sort by Weight decreasing order. sort(sws.begin(), sws.end()); // clamp the result to the wanted max matrix. uint nbMatsOut; switch(_SkinReduction) { case CMRMParameters::SkinReductionMin: nbMatsOut= min(nbMats1, nbMats2); break; case CMRMParameters::SkinReductionMax: nbMatsOut= max(nbMats1, nbMats2); break; case CMRMParameters::SkinReductionBest: nbMatsOut= min( (uint)sws.size(), (uint)NL3D_MESH_SKINNING_MAX_MATRIX ); break; default: nlstop; }; // For security. nbMatsOut= min(nbMatsOut, (uint)sws.size()); nlassert(nbMatsOut<=NL3D_MESH_SKINNING_MAX_MATRIX); // Then output the result to the skinWeight, normalizing. float sumWeight=0; for(i= 0; i=0 || Vertex2.InterfaceLink.InterfaceId>=0) ) { // If this is an edge of a mesh sewing interface if(Vertex1.InterfaceLink.InterfaceId==Vertex2.InterfaceLink.InterfaceId) { // Then the edge is one of the sewing interface mesh. must do special things for it CMRMSewingMesh &sewingMesh= _SewingMeshes[Vertex1.InterfaceLink.InterfaceId]; // get the sewing edge id CMRMEdge sewingEdge; sewingEdge.v0= Vertex1.InterfaceLink.InterfaceVertexId; sewingEdge.v1= Vertex2.InterfaceLink.InterfaceVertexId; // Get the edge in the sewing mesh which is said to be collapsed uint vertToCollapse; sint collapseId= sewingMesh.mustCollapseEdge(_CurrentLodComputed, sewingEdge, vertToCollapse); // if exist if(collapseId>=0) { // if it is v0 which must collapse, then InterValue=1 if(vertToCollapse==(uint)sewingEdge.v0) InterValue= 1; else InterValue= 0; } else { // This should not happens. But it is still possible if this edge don't want to collapse but if their // is no more choice. Take a default value InterValue= 0; } } else { // must collapse to the vertex on the sewing interface (as if it was open) if(Vertex1.InterfaceLink.InterfaceId>=0) { // NB: it is possible that both vertices are on a different sewing interface... still collapse (must have to) InterValue= 0; } else { // Then Vertex2 is on a sewing interface, collapse to it InterValue= 1; } } } // **** Else, on special cases, it is much more efficient to interpolate at start or at end of edge. else { // If one vertex is "open", ie his shared faces do not represent a closed Fan, then interpolate to this one, // so the mesh has the same silhouette. bool vc1= vertexClosed(edgeV1); bool vc2= vertexClosed(edgeV2); if(!vc1 && vc2) InterValue=0; else if(vc1 && !vc2) InterValue=1; else { // Do the same test but with vertex continue: it is preferable to not move the boundaries // of a material, or a mapping. bool vc1= vertexContinue(edgeV1); bool vc2= vertexContinue(edgeV2); if(!vc1 && vc2) InterValue=0; if(vc1 && !vc2) InterValue=1; } } /*BENCH_TotalCollapses++; if(InterValue==0.5) BENCH_MiddleCollapses++;*/ // Collapse the Vertex. //======================== Vertex1.Current= Vertex1.Current*(1-InterValue) + Vertex2.Current*InterValue; for (i = 0; i < (sint)Vertex1.BSCurrent.size(); ++i) Vertex1.BSCurrent[i] = Vertex1.BSCurrent[i]*(1-InterValue) + Vertex2.BSCurrent[i]*InterValue; Vertex2.CollapsedTo= edgeV1; if(_Skinned) Vertex1.CurrentSW= collapseSkinWeight(Vertex1.CurrentSW, Vertex2.CurrentSW, InterValue); if( _HasMeshInterfaces ) Vertex1.InterfaceLink= InterValue<0.5f? Vertex1.InterfaceLink : Vertex2.InterfaceLink; // \todo yoyo: TODO_BUG: Don't know why, but vertices may point on deleted faces. // Temp: we destroy here thoses face from SharedFaces... for(i=0;i<(sint)Vertex1.SharedFaces.size();i++) { sint numFace= Vertex1.SharedFaces[i]; if(TmpFaces[numFace].Deleted) deleteElement(Vertex1.SharedFaces, numFace), i--; } for(i=0;i<(sint)Vertex2.SharedFaces.size();i++) { sint numFace= Vertex2.SharedFaces[i]; if(TmpFaces[numFace].Deleted) deleteElement(Vertex2.SharedFaces, numFace), i--; } // Build Neighbor faces. vector neighboorFaces; for(i=0;i<(sint)Vertex1.SharedFaces.size();i++) { sint numFace= Vertex1.SharedFaces[i]; if(!findElement(neighboorFaces, numFace)) neighboorFaces.push_back(numFace); } for(i=0;i<(sint)Vertex2.SharedFaces.size();i++) { sint numFace= Vertex2.SharedFaces[i]; if(!findElement(neighboorFaces, numFace)) neighboorFaces.push_back(numFace); } // Build faces which will be destroyed (may 1 or 2, maybe more for non conventionnal meshes). vector deletedFaces; for(i=0;i<(sint)Vertex1.SharedFaces.size();i++) { sint numFace= Vertex1.SharedFaces[i]; nlassert(!TmpFaces[numFace].Deleted); if(TmpFaces[numFace].hasVertex(edgeV2)) deletedFaces.push_back(numFace); } // 1. Collapse the wedges. //======================== // For ALL Attributes. for(sint attId=0;attId wedges; for(i=0;i<(sint)neighboorFaces.size();i++) { CMRMFaceBuild &face= TmpFaces[neighboorFaces[i]]; sint numWedge; numWedge= face.getAssociatedWedge(attId, edgeV1); if(numWedge>=0 && !findElement(wedges, numWedge)) wedges.push_back(numWedge); numWedge= face.getAssociatedWedge(attId, edgeV2); if(numWedge>=0 && !findElement(wedges, numWedge)) wedges.push_back(numWedge); } // c/ Count numFaces which point on those wedges. (- deleted faces). //------------------------------------------------------------------ for(i=0;i<(sint)wedges.size();i++) { sint numWedge= wedges[i]; CMRMAttribute &wedge= TmpAttributes[attId][numWedge]; wedge.NbSharedFaces=0; wedge.Shared=false; // Count total ref count. for(j=0;j<(sint)neighboorFaces.size();j++) { if(TmpFaces[neighboorFaces[j]].hasWedge(attId, numWedge)) wedge.NbSharedFaces++; } // Minus deleted faces. for(j=0;j<(sint)deletedFaces.size();j++) { if(TmpFaces[deletedFaces[j]].hasWedge(attId, numWedge)) { wedge.NbSharedFaces--; wedge.Shared=true; wedge.InterpolatedFace=deletedFaces[j]; } } } // d/ Collapse wedge following 3 possibles cases. //----------------------------------------------- for(i=0;i<(sint)wedges.size();i++) { sint numWedge= wedges[i]; CMRMAttribute &wedge= TmpAttributes[attId][numWedge]; // if wedge not shared... if(!wedge.Shared) { // We've got an "exterior wedge" which lost no corner => do not merge it nor delete it. // Leave it as the same value (extrapolate it may not be a good solution). } else { // if wedge dissapears, notify. if(wedge.NbSharedFaces==0) { wedge.CollapsedTo=-2; // Do not change his value. (as specified in Hope article). } else { CMRMFaceBuild &face= TmpFaces[wedge.InterpolatedFace]; // Must interpolate it. wedge.Current= face.InterpolatedAttribute; wedge.BSCurrent = face.BSInterpolated; // Must merge the wedge of the second vertex on first // ONLY IF 2 interpolated wedges are shared and NbSharedFaces!=0. if( numWedge==face.getAssociatedWedge(attId, edgeV2) && faceShareWedges(&face, attId, edgeV1, edgeV2) ) { wedge.CollapsedTo= face.getAssociatedWedge(attId, edgeV1); } } } } } // 3. collapse faces. //=================== // delete face shared by edge. for(i=0;i<(sint)deletedFaces.size();i++) { sint numFace= deletedFaces[i]; TmpFaces[numFace].Deleted=true; // release edges from list. removeFaceFromEdgeList(TmpFaces[numFace]); // invalid all it!! TmpFaces[numFace].invalidAllIts(EdgeCollapses); // delete from vertex1 and 2 the deleted faces. deleteElement( Vertex1.SharedFaces, numFace); deleteElement( Vertex2.SharedFaces, numFace); } // must ref correctly the faces. for(i=0;i<(sint)neighboorFaces.size();i++) { CMRMFaceBuild &face=TmpFaces[neighboorFaces[i]]; // good vertices if(face.Corner[0].Vertex ==edgeV2) face.Corner[0].Vertex=edgeV1; if(face.Corner[1].Vertex ==edgeV2) face.Corner[1].Vertex=edgeV1; if(face.Corner[2].Vertex ==edgeV2) face.Corner[2].Vertex=edgeV1; // nb: doesn't matter if deletedFaces are modified... // good wedges for(sint attId=0;attId=0) face.Corner[0].Attributes[attId]= newWedge; newWedge= TmpAttributes[attId][ face.Corner[1].Attributes[attId] ].CollapsedTo; if(newWedge>=0) face.Corner[1].Attributes[attId]= newWedge; newWedge= TmpAttributes[attId][ face.Corner[2].Attributes[attId] ].CollapsedTo; if(newWedge>=0) face.Corner[2].Attributes[attId]= newWedge; } // good edges. /* Those ones are updated in collapseEdges(): they are removed from the edgeCollapseList, then they are re-inserted with good Vertex indices. */ } // The vertex1 has now the shared env of vertex2. Vertex1.SharedFaces.insert(Vertex1.SharedFaces.end(), Vertex2.SharedFaces.begin(), Vertex2.SharedFaces.end()); return (sint)deletedFaces.size(); } // *************************************************************************** sint CMRMBuilder::followVertex(sint i) { CMRMVertex &vert=TmpVertices[i]; if(vert.CollapsedTo>=0) return followVertex(vert.CollapsedTo); else return i; } // *************************************************************************** sint CMRMBuilder::followWedge(sint attribId, sint i) { CMRMAttribute &wedge= TmpAttributes[attribId][i]; if(wedge.CollapsedTo>=0) return followWedge(attribId, wedge.CollapsedTo); else return i; } // *************************************************************************** // *************************************************************************** // Mesh Level method. // *************************************************************************** // *************************************************************************** // *************************************************************************** CMRMBuilder::CMRMBuilder() { NumAttributes= 0; _Skinned= false; _HasMeshInterfaces= false; } // *************************************************************************** void CMRMBuilder::init(const CMRMMesh &baseMesh) { sint i, attId; // First clear ALL. TmpVertices.clear(); for(attId=0;attIdnWantedFaces) { bug0++; EdgeIt= EdgeCollapses.begin(); if(EdgeIt== EdgeCollapses.end()) break; // 0. Look if edge already deleted //================================ CMRMEdge edge=(*EdgeIt).second; // Is it valid?? (ie his vertices exist yet??). if(TmpVertices[ edge.v0 ].CollapsedTo>=0 || TmpVertices[ edge.v1 ].CollapsedTo>=0) { // \todo yoyo: TODO_BUG: potential bug here... CMRMFaceBuild &f= *(EdgeIt->second.Face); nlassert(f.validEdgeIt(EdgeIt->second)); f.invalidEdgeIt(EdgeIt->second, EdgeCollapses); EdgeCollapses.erase(EdgeIt); bug2++; continue; } // \todo yoyo: TODO_BUG: potential bug here... // If a mesh is "open" it will crash if a "hole collapse"... if(edge.v0==edge.v1) { CMRMFaceBuild &f= *(EdgeIt->second.Face); nlassert(f.validEdgeIt(EdgeIt->second)); f.invalidEdgeIt(EdgeIt->second, EdgeCollapses); EdgeCollapses.erase(EdgeIt); bug3++; continue; } // 1. else, OK, collapse it!! //=========================== sint vertexCollapsed= edge.v0; nCurrentFaces-= collapseEdge(edge); // 2. Must reorder all his neighborhood. //====================================== CMRMVertex &vert=TmpVertices[vertexCollapsed]; sint i; // we delete from list modified edges, and we re-add them with their new value. for(i=0;i<(sint)vert.SharedFaces.size();i++) { CMRMFaceBuild &f= TmpFaces[vert.SharedFaces[i]]; removeFaceFromEdgeList(f); insertFaceIntoEdgeList(f); } } } // *************************************************************************** void CMRMBuilder::saveCoarserMesh(CMRMMesh &coarserMesh) { sint i,attId,index; // First clear ALL. coarserMesh.Vertices.clear(); coarserMesh.SkinWeights.clear(); coarserMesh.InterfaceLinks.clear(); for(attId=0;attId=0); // Attributes. for(attId=0;attId=0); } } coarserMesh.Faces.push_back(newFace); } } } // *************************************************************************** void CMRMBuilder::makeLODMesh(CMRMMeshGeom &lodMesh) { sint i,j,attId,index,coidx; // for all faces of this mesh, find target in the coarser mesh. for(i=0;i<(sint)lodMesh.CoarserFaces.size();i++) { CMRMFace &face= lodMesh.CoarserFaces[i]; // For 3 corners. for(j=0;j<3;j++) { // Vertex. // The index is yet the index in the finer mesh. index= face.Corner[j].Vertex; // the index in the coarser mesh is vert.CoarserIndex. coidx= TmpVertices[index].CoarserIndex; // but if this vertex is collapsed, must find the good index (yet in the finer mesh) if(coidx==-1) { // find to which we must collapse. index= followVertex(index); // and so we have the coarser index. this one must be valid. coidx= TmpVertices[index].CoarserIndex; nlassert(coidx>=0); } // update corner of CoarserFace. face.Corner[j].Vertex= coidx; // Do exactly same thing for all attributes. for(attId=0;attId=0); } face.Corner[j].Attributes[attId]= coidx; } } } } // *************************************************************************** // Transform source blend shapes to source blend shapes modified (just calculate new vertex/attr position) /*void CMRMBuilder::computeBsVerticesAttributes(vector &srcBsMeshs, vector &bsMeshsMod) { sint i, j, k, attId; bsMeshsMod.resize (srcBsMeshs.size()); for (k = 0; k < (sint)srcBsMeshs.size(); ++k) { CMRMMesh &rBsMesh = srcBsMeshs[k]; CMRMMesh &rBsMeshMod = bsMeshsMod[k]; // Calculate modified vertices with the linear equation back tracking help rBsMeshMod.Vertices.resize (rBsMesh.Vertices.size()); for (i = 0; i < (sint)rBsMesh.Vertices.size(); ++i) { CLinearEquation &LinEq = TmpVertices[i].CurrentLinEq; rBsMeshMod.Vertices[i] = CVector(0.0f, 0.0f, 0.0f); for (j = 0; j < (sint)LinEq.Elts.size(); ++j) { rBsMeshMod.Vertices[i] += LinEq.Elts[j].factor * rBsMesh.Vertices[LinEq.Elts[j].index]; } } // All attributes rBsMeshMod.NumAttributes = NumAttributes; for (attId = 0; attId < NumAttributes; attId++) { rBsMeshMod.Attributes[attId].resize (rBsMesh.Attributes[attId].size()); for (i = 0; i < (sint)rBsMesh.Attributes[attId].size(); ++i) { CLinearEquation &LinEq = TmpAttributes[attId][i].CurrentLinEq; rBsMeshMod.Attributes[attId][i] = CVectorH(0.0f, 0.0f, 0.0f, 0.0f); for (j = 0; j < (sint)LinEq.Elts.size(); ++j) { rBsMeshMod.Attributes[attId][i].x += LinEq.Elts[j].factor * rBsMesh.Attributes[attId][LinEq.Elts[j].index].x; rBsMeshMod.Attributes[attId][i].y += LinEq.Elts[j].factor * rBsMesh.Attributes[attId][LinEq.Elts[j].index].y; rBsMeshMod.Attributes[attId][i].z += LinEq.Elts[j].factor * rBsMesh.Attributes[attId][LinEq.Elts[j].index].z; rBsMeshMod.Attributes[attId][i].w += LinEq.Elts[j].factor * rBsMesh.Attributes[attId][LinEq.Elts[j].index].w; } } } } }*/ // *************************************************************************** // Transform source Blend Shape Meshes Modified into coarser blend shape mesh (compact vertices) void CMRMBuilder::makeCoarserBS (vector &csBsMeshs) { uint32 i, k; sint32 nSizeVert, nSizeAttr, attId; // Calculate size of vertices array nSizeVert = 0; for (i = 0; i < TmpVertices.size(); ++i) if(TmpVertices[i].CoarserIndex > nSizeVert) nSizeVert = TmpVertices[i].CoarserIndex; ++nSizeVert; for (k = 0; k < csBsMeshs.size(); ++k) { CMRMBlendShape &rBsCoarserMesh = csBsMeshs[k]; rBsCoarserMesh.Vertices.resize (nSizeVert); rBsCoarserMesh.NumAttributes = NumAttributes; // Vertices for(i = 0; i < TmpVertices.size(); ++i) { CMRMVertex &vert = TmpVertices[i]; if (vert.CoarserIndex != -1) { rBsCoarserMesh.Vertices[vert.CoarserIndex] = vert.BSCurrent[k]; } } for (attId = 0; attId < NumAttributes; attId++) { // Calculate size of attribute attId array nSizeAttr = 0; for(i = 0; i < TmpAttributes[attId].size(); i++) if (TmpAttributes[attId][i].CoarserIndex > nSizeAttr) nSizeAttr = TmpAttributes[attId][i].CoarserIndex; ++nSizeAttr; rBsCoarserMesh.Attributes[attId].resize (nSizeAttr); for (i = 0; i < TmpAttributes[attId].size(); i++) { CMRMAttribute &wedge = TmpAttributes[attId][i]; if (wedge.CoarserIndex != -1) { rBsCoarserMesh.Attributes[attId][wedge.CoarserIndex] = wedge.BSCurrent[k]; } } } } } // *************************************************************************** void CMRMBuilder::makeFromMesh(const CMRMMesh &baseMesh, CMRMMeshGeom &lodMesh, CMRMMesh &coarserMesh, sint nWantedFaces) { // Init Tmp values in MRM builder. init(baseMesh); // compute MRM too next tgt face. collapseEdges(nWantedFaces); // save the coarser mesh. saveCoarserMesh(coarserMesh); // Build coarser BlendShapes. coarserMesh.BlendShapes.resize(baseMesh.BlendShapes.size()); makeCoarserBS(coarserMesh.BlendShapes); // build the lodMesh (baseMesh, with vertex/Attributes collapse infos). lodMesh= baseMesh; makeLODMesh(lodMesh); // end for this level. } // *************************************************************************** // *************************************************************************** // Global MRM Level method. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CMRMBuilder::buildAllLods(const CMRMMesh &baseMesh, std::vector &lodMeshs, uint nWantedLods, uint divisor) { sint nFaces= (sint)baseMesh.Faces.size(); sint nBaseFaces; sint i; CMRMMesh srcMesh = baseMesh; // coarsest LOD will have those number of faces. nBaseFaces=nFaces/divisor; nBaseFaces=max(nBaseFaces,4); // must have at least 2 LOD to be really intersting. But the rest of the process work too with only one Lod!! nlassert(nWantedLods>=1); lodMeshs.resize(nWantedLods); // If only one lod asked, must init some Tmp Global values (like NumAttributes) if(nWantedLods==1) { _CurrentLodComputed= 0; init(baseMesh); } // must fill all LODs, from end to start. do not proces last lod since it will be the coarsest mesh. for(i=nWantedLods-1;i>0;i--) { sint nbWantedFaces; // for sewing computing _CurrentLodComputed= i; // Linear. nbWantedFaces= nBaseFaces + (nFaces-nBaseFaces) * (i-1)/(nWantedLods-1); nbWantedFaces=max(nbWantedFaces,4); // Build this LOD. CMRMMesh csMesh; // The mesh makeFromMesh(srcMesh, lodMeshs[i], csMesh, nbWantedFaces); // next mesh to process is csMesh. srcMesh = csMesh; } // the first lodMedsh gets the coarsest mesh. lodMeshs[0]= srcMesh; } // *************************************************************************** void CMRMBuilder::buildFinalMRM(std::vector &lodMeshs, CMRMMeshFinal &finalMRM) { sint i,j; sint lodId, attId; sint nLods= (sint)lodMeshs.size(); // Init. // =============== finalMRM.reset(); finalMRM.NumAttributes= NumAttributes; finalMRM.Skinned= _Skinned; CMRMMeshFinal::CWedge::NumAttributesToCompare= NumAttributes; CMRMMeshFinal::CWedge::CompareSkinning= _Skinned; finalMRM.Lods.resize(nLods); // Build Wedges, and faces index. // =============== // for all lods. for(lodId=0; lodId0) { // fill wedgeEnd with values from coarser lodMesh. wedgeEnd.Vertex= lodMeshPrec.Vertices[cornerCoarser.Vertex]; if(_Skinned) wedgeEnd.VertexSkin= lodMeshPrec.SkinWeights[cornerCoarser.Vertex]; for(attId=0; attIdsecond; // store this Geom Id in the corner. corner.WedgeGeomId= geomDest; } } } // take the max. sglmGeomMax= max(sglmGeomMax, sglmGeom); } // inform the finalMRM. finalMRM.NGeomSpace= sglmGeomMax; // decal all wedges/ face index. // =============== // insert an empty space for dest geomorph. finalMRM.Wedges.insert(finalMRM.Wedges.begin(), sglmGeomMax, CMRMMeshFinal::CWedge()); // Parse all faces corner of All lods, and decal Start/End Wedge index. for(lodId=0; lodId0); wedge.NSkinMatUsed= j; } } // Blend Shape Stuff finalMRM.MRMBlendShapesFinals.resize (lodMeshs[0].BlendShapes.size()); for (lodId = 0; lodId < nLods; ++lodId) { CMRMMeshGeom &lodMesh= lodMeshs[lodId]; CMRMMeshGeom &lodMeshPrec= lodMeshs[lodId==0?0:lodId-1]; // for all face corner. for (i = 0; i < (sint)lodMesh.Faces.size(); ++i) { // The current face. CMRMFace &face = lodMesh.Faces[i]; // the current face, but which points to the prec LOD vertices/attributes. CMRMFace &faceCoarser = lodMesh.CoarserFaces[i]; // for 3 corners. for (j = 0; j < 3; ++j) { CMRMCorner &corner = face.Corner[j]; CMRMCorner &cornerCoarser = faceCoarser.Corner[j]; sint startDestIndex = corner.WedgeStartId; for (sint k = 0; k < (sint)finalMRM.MRMBlendShapesFinals.size(); ++k) { CMRMMeshFinal::CMRMBlendShapeFinal &rBSFinal = finalMRM.MRMBlendShapesFinals[k]; rBSFinal.Wedges.resize (finalMRM.Wedges.size()); // Fill WedgeStart used by this corner. rBSFinal.Wedges[startDestIndex].Vertex = lodMesh.BlendShapes[k].Vertices[corner.Vertex]; for (attId = 0; attId < NumAttributes; ++attId) { rBSFinal.Wedges[startDestIndex].Attributes[attId] = lodMesh.BlendShapes[k].Attributes[attId][corner.Attributes[attId]]; } // If geomorph, must fill the end too if(lodId>0 && corner.WedgeStartId != corner.WedgeEndId) { sint endDestIndex = corner.WedgeEndId; rBSFinal.Wedges[endDestIndex].Vertex = lodMeshPrec.BlendShapes[k].Vertices[cornerCoarser.Vertex]; for (attId = 0; attId < NumAttributes; ++attId) { rBSFinal.Wedges[endDestIndex].Attributes[attId] = lodMeshPrec.BlendShapes[k].Attributes[attId][cornerCoarser.Attributes[attId]]; } } } } } } } // *************************************************************************** // *************************************************************************** // Interface to MeshBuild Part. // *************************************************************************** // *************************************************************************** // *************************************************************************** sint CMRMBuilder::findInsertAttributeInBaseMesh(CMRMMesh &baseMesh, sint attId, sint vertexId, const CVectorH &att) { // find this attribute in the map. CAttributeKey key; key.VertexId= vertexId; key.Attribute= att; TAttributeMap::iterator it= _AttributeMap[attId].find(key); // if attribute not found in the map, then insert a new one. if(it==_AttributeMap[attId].end()) { sint idx= (sint)baseMesh.Attributes[attId].size(); // insert into the array. baseMesh.Attributes[attId].push_back(att); // insert into the map. _AttributeMap[attId].insert(make_pair(key, idx)); return idx; } else { // return the one found. return it->second; } } // *************************************************************************** sint CMRMBuilder::findInsertNormalInBaseMesh(CMRMMesh &baseMesh, sint attId, sint vertexId, const CVector &normal) { CVectorH att; att= normal; att.w= 0; return findInsertAttributeInBaseMesh(baseMesh, attId, vertexId, att); } // *************************************************************************** sint CMRMBuilder::findInsertColorInBaseMesh(CMRMMesh &baseMesh, sint attId, sint vertexId, CRGBA col) { CVectorH att; att.x= col.R; att.y= col.G; att.z= col.B; att.w= col.A; return findInsertAttributeInBaseMesh(baseMesh, attId, vertexId, att); } // *************************************************************************** sint CMRMBuilder::findInsertUvwInBaseMesh(CMRMMesh &baseMesh, sint attId, sint vertexId, const NLMISC::CUVW &uvw) { CVectorH att; att.x= uvw.U; att.y= uvw.V; att.z= uvw.W; att.w= 0; return findInsertAttributeInBaseMesh(baseMesh, attId, vertexId, att); } // *************************************************************************** CRGBA CMRMBuilder::attToColor(const CVectorH &att) const { CRGBA ret; float tmp; tmp= att.x; clamp(tmp, 0, 255); ret.R= (uint8)(uint)tmp; tmp= att.y; clamp(tmp, 0, 255); ret.G= (uint8)(uint)tmp; tmp= att.z; clamp(tmp, 0, 255); ret.B= (uint8)(uint)tmp; tmp= att.w; clamp(tmp, 0, 255); ret.A= (uint8)(uint)tmp; return ret; } // *************************************************************************** NLMISC::CUVW CMRMBuilder::attToUvw(const CVectorH &att) const { return CUVW(att.x, att.y, att.z); } // *************************************************************************** uint32 CMRMBuilder::buildMrmBaseMesh(const CMesh::CMeshBuild &mbuild, CMRMMesh &baseMesh) { sint i,j,k; sint nFaces; sint attId; // build the supported VertexFormat. uint32 retVbFlags= CVertexBuffer::PositionFlag; // reset the baseMesh. baseMesh= CMRMMesh(); // reset Tmp. for(attId=0; attId sws; sws.reserve(NL3D_MESH_SKINNING_MAX_MATRIX); sws.clear(); // For all weights of sw1. uint i; for(i=0; i0) { // add it to the list. sws.push_back(vw); nbMats++; } } // sort by Weight decreasing order. sort(sws.begin(), sws.end()); // Then output the result to the skinWeight, normalizing. float sumWeight=0; for(i= 0; i matCount; // resize, and reset to 0. matCount.clear(); matCount.resize(nbMats, 0); // For each face of this Lods, incr the mat face counter. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { sint matId= srcLod.Faces[j].MaterialId; nlassert(matId>=0); nlassert(matId<(sint)nbMats); // increment the refcount of this material by this LOD. matCount[matId]++; } // Then for each material not empty, create a rdrPass, and ref it for this material. vector rdrPassIndex; // material to rdrPass map. rdrPassIndex.resize(nbMats); for(j=0; j<(sint)nbMats; j++) { if(matCount[j]==0) rdrPassIndex[j]= -1; else { // map material to rdrPass. sint idRdrPass= (sint)destLod.RdrPass.size(); rdrPassIndex[j]= idRdrPass; // create a rdrPass. destLod.RdrPass.push_back(CMeshMRMGeom::CRdrPass()); // assign the good materialId to this rdrPass. destLod.RdrPass[idRdrPass].MaterialId= j; // reserve the array of faces of this rdrPass. destLod.RdrPass[idRdrPass].PBlock.reserve(3*matCount[j]); } } // Then for each face, add it to the good rdrPass of this Lod. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { sint matId= srcLod.Faces[j].MaterialId; sint idRdrPass= rdrPassIndex[matId]; // add this face to the good rdrPass. sint w0= srcLod.Faces[j].WedgeId[0]; sint w1= srcLod.Faces[j].WedgeId[1]; sint w2= srcLod.Faces[j].WedgeId[2]; CIndexBuffer &ib = destLod.RdrPass[idRdrPass].PBlock; uint index = ib.getNumIndexes(); ib.setNumIndexes(index+3); CIndexBufferReadWrite ibaWrite; ib.lock (ibaWrite); ibaWrite.setTri(index, w0, w1, w2); } // Build skin info for this Lod. //--------- for(j=0; j wedgeInfSet; // First, build the list of vertices influenced by this Lod. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { for(k=0; k<3; k++) { sint wedgeId= srcLod.Faces[j].WedgeId[k]; // If it is a geomorph if(wedgeId MatrixInfId. map matrixInfMap; // For all influenced vertices, flags matrix they use. uint iSkinMat; for(iSkinMat= 0; iSkinMat0 ); } else { nlassert( matWeight==0 ); } // if not null influence. if(matWeight>0) { uint matId= wedge.VertexSkin.MatrixId[k]; // search/insert the matrixInfId. map::iterator it= matrixInfMap.find(matId); if( it==matrixInfMap.end() ) { uint matInfId= (uint)destLod.MatrixInfluences.size(); matrixInfMap.insert( make_pair(matId, matInfId) ); // create the new MatrixInfluence. destLod.MatrixInfluences.push_back(matId); } } } } } } } // Indicate Skinning. mbuild.Skinned= _Skinned; bool useTgSpace = mb.MeshVertexProgram != NULL ? mb.MeshVertexProgram->needTangentSpace() : false; // Construct Blend Shapes //// mbuild <- finalMRM mbuild.BlendShapes.resize (finalMRM.MRMBlendShapesFinals.size()); for (k = 0; k < (sint)mbuild.BlendShapes.size(); ++k) { CBlendShape &rBS = mbuild.BlendShapes[k]; sint32 nNbVertVB = (sint32)finalMRM.Wedges.size(); bool bIsDeltaPos = false; rBS.deltaPos.resize (nNbVertVB, CVector(0.0f,0.0f,0.0f)); bool bIsDeltaNorm = false; rBS.deltaNorm.resize (nNbVertVB, CVector(0.0f,0.0f,0.0f)); bool bIsDeltaUV = false; rBS.deltaUV.resize (nNbVertVB, CUV(0.0f,0.0f)); bool bIsDeltaCol = false; rBS.deltaCol.resize (nNbVertVB, CRGBAF(0.0f,0.0f,0.0f,0.0f)); bool bIsDeltaTgSpace = false; if (useTgSpace) { rBS.deltaTgSpace.resize(nNbVertVB, CVector::Null); } rBS.VertRefs.resize (nNbVertVB, 0xffffffff); for (i = 0; i < nNbVertVB; i++) { const CMRMMeshFinal::CWedge &rWedgeRef = finalMRM.Wedges[i]; const CMRMMeshFinal::CWedge &rWedgeTar = finalMRM.MRMBlendShapesFinals[k].Wedges[i]; CVector delta = rWedgeTar.Vertex - rWedgeRef.Vertex; CVectorH attr; if (delta.norm() > 0.001f) { rBS.deltaPos[i] = delta; rBS.VertRefs[i] = i; bIsDeltaPos = true; } attId = 0; if (vbFlags & CVertexBuffer::NormalFlag) { attr = rWedgeRef.Attributes[attId]; CVector NormRef = CVector(attr.x, attr.y, attr.z); attr = rWedgeTar.Attributes[attId]; CVector NormTar = CVector(attr.x, attr.y, attr.z); delta = NormTar - NormRef; if (delta.norm() > 0.001f) { rBS.deltaNorm[i] = delta; rBS.VertRefs[i] = i; bIsDeltaNorm = true; } attId++; } if (vbFlags & CVertexBuffer::PrimaryColorFlag) { attr = rWedgeRef.Attributes[attId]; CRGBAF RGBARef = CRGBAF(attr.x/255.0f, attr.y/255.0f, attr.z/255.0f, attr.w/255.0f); attr = rWedgeTar.Attributes[attId]; CRGBAF RGBATar = CRGBAF(attr.x/255.0f, attr.y/255.0f, attr.z/255.0f, attr.w/255.0f); CRGBAF deltaRGBA = RGBATar - RGBARef; if ((deltaRGBA.R*deltaRGBA.R + deltaRGBA.G*deltaRGBA.G + deltaRGBA.B*deltaRGBA.B + deltaRGBA.A*deltaRGBA.A) > 0.0001f) { rBS.deltaCol[i] = deltaRGBA; rBS.VertRefs[i] = i; bIsDeltaCol = true; } attId++; } if (vbFlags & CVertexBuffer::SecondaryColorFlag) { // Nothing to do ! attId++; } // Do that only for the UV0 if (vbFlags & CVertexBuffer::TexCoord0Flag) { attr = rWedgeRef.Attributes[attId]; CUV UVRef = CUV(attr.x, attr.y); attr = rWedgeTar.Attributes[attId]; CUV UVTar = CUV(attr.x, attr.y); CUV deltaUV = UVTar - UVRef; if ((deltaUV.U*deltaUV.U + deltaUV.V*deltaUV.V) > 0.0001f) { rBS.deltaUV[i] = deltaUV; rBS.VertRefs[i] = i; bIsDeltaUV = true; } attId++; } if (useTgSpace) { attr = rWedgeRef.Attributes[attId]; CVector TgSpaceRef = CVector(attr.x, attr.y, attr.z); attr = rWedgeTar.Attributes[attId]; CVector TgSpaceTar = CVector(attr.x, attr.y, attr.z); delta = TgSpaceTar - TgSpaceRef; if (delta.norm() > 0.001f) { rBS.deltaTgSpace[i] = delta; rBS.VertRefs[i] = i; bIsDeltaTgSpace = true; } attId++; } } // End of all vertices added in blend shape // Delete unused items and calculate the number of vertex used (blended) sint32 nNbVertUsed = nNbVertVB; sint32 nDstPos = 0; for (j = 0; j < nNbVertVB; ++j) { if (rBS.VertRefs[j] == 0xffffffff) // Is vertex UNused { --nNbVertUsed; } else // Vertex used { if (nDstPos != j) { rBS.VertRefs[nDstPos] = rBS.VertRefs[j]; rBS.deltaPos[nDstPos] = rBS.deltaPos[j]; rBS.deltaNorm[nDstPos] = rBS.deltaNorm[j]; rBS.deltaUV[nDstPos] = rBS.deltaUV[j]; rBS.deltaCol[nDstPos] = rBS.deltaCol[j]; if (useTgSpace) { rBS.deltaTgSpace[nDstPos] = rBS.deltaTgSpace[j]; } } ++nDstPos; } } if (bIsDeltaPos) rBS.deltaPos.resize (nNbVertUsed); else rBS.deltaPos.resize (0); if (bIsDeltaNorm) rBS.deltaNorm.resize (nNbVertUsed); else rBS.deltaNorm.resize (0); if (bIsDeltaUV) rBS.deltaUV.resize (nNbVertUsed); else rBS.deltaUV.resize (0); if (bIsDeltaCol) rBS.deltaCol.resize (nNbVertUsed); else rBS.deltaCol.resize (0); if (bIsDeltaTgSpace) rBS.deltaTgSpace.resize (nNbVertUsed); else rBS.deltaTgSpace.resize (0); rBS.VertRefs.resize (nNbVertUsed); } } // *************************************************************************** void CMRMBuilder::buildMeshBuildMrm(const CMRMMeshFinal &finalMRM, CMeshMRMSkinnedGeom::CMeshBuildMRM &mbuild, uint32 vbFlags, uint32 nbMats, const CMesh::CMeshBuild &mb) { sint i,j,k; sint attId; // reset the mbuild. mbuild= CMeshMRMSkinnedGeom::CMeshBuildMRM(); // Setup VB. bool useFormatExt = false; // Check whether there are texture coordinates with more than 2 compnents, which force us to use an extended vertex format for (k = 0; k < CVertexBuffer::MaxStage; ++k) { if ( (vbFlags & (CVertexBuffer::TexCoord0Flag << k)) && mb.NumCoords[k] != 2) { useFormatExt = true; break; } } uint numTexCoordUsed = 0; for (k = 0; k < CVertexBuffer::MaxStage; ++k) { if (vbFlags & (CVertexBuffer::TexCoord0Flag << k)) { numTexCoordUsed = k; } } if (!useFormatExt) { // setup standard format mbuild.VBuffer.setVertexFormat(vbFlags); } else // setup extended format { mbuild.VBuffer.clearValueEx(); if (vbFlags & CVertexBuffer::PositionFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::Position, CVertexBuffer::Float3); if (vbFlags & CVertexBuffer::NormalFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::Normal, CVertexBuffer::Float3); if (vbFlags & CVertexBuffer::PrimaryColorFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::PrimaryColor, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::SecondaryColorFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::SecondaryColor, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::WeightFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::Weight, CVertexBuffer::Float4); if (vbFlags & CVertexBuffer::PaletteSkinFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::PaletteSkin, CVertexBuffer::UChar4); if (vbFlags & CVertexBuffer::FogFlag) mbuild.VBuffer.addValueEx(CVertexBuffer::Fog, CVertexBuffer::Float1); for (k = 0; k < CVertexBuffer::MaxStage; ++k) { if (vbFlags & (CVertexBuffer::TexCoord0Flag << k)) { switch(mb.NumCoords[k]) { case 2: mbuild.VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float2); break; case 3: mbuild.VBuffer.addValueEx((CVertexBuffer::TValue) (CVertexBuffer::TexCoord0 + k), CVertexBuffer::Float3); break; default: nlassert(0); break; } } } mbuild.VBuffer.initEx(); } // Copy the UVRouting for (i=0; i matCount; // resize, and reset to 0. matCount.clear(); matCount.resize(nbMats, 0); // For each face of this Lods, incr the mat face counter. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { sint matId= srcLod.Faces[j].MaterialId; nlassert(matId>=0); nlassert(matId<(sint)nbMats); // increment the refcount of this material by this LOD. matCount[matId]++; } // Then for each material not empty, create a rdrPass, and ref it for this material. vector rdrPassIndex; // material to rdrPass map. rdrPassIndex.resize(nbMats); for(j=0; j<(sint)nbMats; j++) { if(matCount[j]==0) rdrPassIndex[j]= -1; else { // map material to rdrPass. sint idRdrPass= (sint)destLod.RdrPass.size(); rdrPassIndex[j]= idRdrPass; // create a rdrPass. destLod.RdrPass.push_back(CMeshMRMSkinnedGeom::CRdrPass()); // assign the good materialId to this rdrPass. destLod.RdrPass[idRdrPass].MaterialId= j; // reserve the array of faces of this rdrPass. destLod.RdrPass[idRdrPass].PBlock.reserve(3*matCount[j]); } } // Then for each face, add it to the good rdrPass of this Lod. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { sint matId= srcLod.Faces[j].MaterialId; sint idRdrPass= rdrPassIndex[matId]; // add this face to the good rdrPass. sint w0= srcLod.Faces[j].WedgeId[0]; sint w1= srcLod.Faces[j].WedgeId[1]; sint w2= srcLod.Faces[j].WedgeId[2]; destLod.RdrPass[idRdrPass].PBlock.push_back (w0); destLod.RdrPass[idRdrPass].PBlock.push_back (w1); destLod.RdrPass[idRdrPass].PBlock.push_back (w2); } // Build skin info for this Lod. //--------- for(j=0; j wedgeInfSet; // First, build the list of vertices influenced by this Lod. for(j= 0; j<(sint)srcLod.Faces.size(); j++) { for(k=0; k<3; k++) { sint wedgeId= srcLod.Faces[j].WedgeId[k]; // If it is a geomorph if(wedgeId MatrixInfId. map matrixInfMap; // For all influenced vertices, flags matrix they use. uint iSkinMat; for(iSkinMat= 0; iSkinMat0 ); } else { nlassert( matWeight==0 ); } // if not null influence. if(matWeight>0) { uint matId= wedge.VertexSkin.MatrixId[k]; // search/insert the matrixInfId. map::iterator it= matrixInfMap.find(matId); if( it==matrixInfMap.end() ) { uint matInfId= (uint)destLod.MatrixInfluences.size(); matrixInfMap.insert( make_pair(matId, matInfId) ); // create the new MatrixInfluence. destLod.MatrixInfluences.push_back(matId); } } } } } } } // Indicate Skinning. mbuild.Skinned= _Skinned; bool useTgSpace = mb.MeshVertexProgram != NULL ? mb.MeshVertexProgram->needTangentSpace() : false; // Construct Blend Shapes //// mbuild <- finalMRM mbuild.BlendShapes.resize (finalMRM.MRMBlendShapesFinals.size()); for (k = 0; k < (sint)mbuild.BlendShapes.size(); ++k) { CBlendShape &rBS = mbuild.BlendShapes[k]; sint32 nNbVertVB = (sint32)finalMRM.Wedges.size(); bool bIsDeltaPos = false; rBS.deltaPos.resize (nNbVertVB, CVector(0.0f,0.0f,0.0f)); bool bIsDeltaNorm = false; rBS.deltaNorm.resize (nNbVertVB, CVector(0.0f,0.0f,0.0f)); bool bIsDeltaUV = false; rBS.deltaUV.resize (nNbVertVB, CUV(0.0f,0.0f)); bool bIsDeltaCol = false; rBS.deltaCol.resize (nNbVertVB, CRGBAF(0.0f,0.0f,0.0f,0.0f)); bool bIsDeltaTgSpace = false; if (useTgSpace) { rBS.deltaTgSpace.resize(nNbVertVB, CVector::Null); } rBS.VertRefs.resize (nNbVertVB, 0xffffffff); for (i = 0; i < nNbVertVB; i++) { const CMRMMeshFinal::CWedge &rWedgeRef = finalMRM.Wedges[i]; const CMRMMeshFinal::CWedge &rWedgeTar = finalMRM.MRMBlendShapesFinals[k].Wedges[i]; CVector delta = rWedgeTar.Vertex - rWedgeRef.Vertex; CVectorH attr; if (delta.norm() > 0.001f) { rBS.deltaPos[i] = delta; rBS.VertRefs[i] = i; bIsDeltaPos = true; } attId = 0; if (vbFlags & CVertexBuffer::NormalFlag) { attr = rWedgeRef.Attributes[attId]; CVector NormRef = CVector(attr.x, attr.y, attr.z); attr = rWedgeTar.Attributes[attId]; CVector NormTar = CVector(attr.x, attr.y, attr.z); delta = NormTar - NormRef; if (delta.norm() > 0.001f) { rBS.deltaNorm[i] = delta; rBS.VertRefs[i] = i; bIsDeltaNorm = true; } attId++; } if (vbFlags & CVertexBuffer::PrimaryColorFlag) { attr = rWedgeRef.Attributes[attId]; CRGBAF RGBARef = CRGBAF(attr.x/255.0f, attr.y/255.0f, attr.z/255.0f, attr.w/255.0f); attr = rWedgeTar.Attributes[attId]; CRGBAF RGBATar = CRGBAF(attr.x/255.0f, attr.y/255.0f, attr.z/255.0f, attr.w/255.0f); CRGBAF deltaRGBA = RGBATar - RGBARef; if ((deltaRGBA.R*deltaRGBA.R + deltaRGBA.G*deltaRGBA.G + deltaRGBA.B*deltaRGBA.B + deltaRGBA.A*deltaRGBA.A) > 0.0001f) { rBS.deltaCol[i] = deltaRGBA; rBS.VertRefs[i] = i; bIsDeltaCol = true; } attId++; } if (vbFlags & CVertexBuffer::SecondaryColorFlag) { // Nothing to do ! attId++; } // Do that only for the UV0 if (vbFlags & CVertexBuffer::TexCoord0Flag) { attr = rWedgeRef.Attributes[attId]; CUV UVRef = CUV(attr.x, attr.y); attr = rWedgeTar.Attributes[attId]; CUV UVTar = CUV(attr.x, attr.y); CUV deltaUV = UVTar - UVRef; if ((deltaUV.U*deltaUV.U + deltaUV.V*deltaUV.V) > 0.0001f) { rBS.deltaUV[i] = deltaUV; rBS.VertRefs[i] = i; bIsDeltaUV = true; } attId++; } if (useTgSpace) { attr = rWedgeRef.Attributes[attId]; CVector TgSpaceRef = CVector(attr.x, attr.y, attr.z); attr = rWedgeTar.Attributes[attId]; CVector TgSpaceTar = CVector(attr.x, attr.y, attr.z); delta = TgSpaceTar - TgSpaceRef; if (delta.norm() > 0.001f) { rBS.deltaTgSpace[i] = delta; rBS.VertRefs[i] = i; bIsDeltaTgSpace = true; } attId++; } } // End of all vertices added in blend shape // Delete unused items and calculate the number of vertex used (blended) sint32 nNbVertUsed = nNbVertVB; sint32 nDstPos = 0; for (j = 0; j < nNbVertVB; ++j) { if (rBS.VertRefs[j] == 0xffffffff) // Is vertex UNused { --nNbVertUsed; } else // Vertex used { if (nDstPos != j) { rBS.VertRefs[nDstPos] = rBS.VertRefs[j]; rBS.deltaPos[nDstPos] = rBS.deltaPos[j]; rBS.deltaNorm[nDstPos] = rBS.deltaNorm[j]; rBS.deltaUV[nDstPos] = rBS.deltaUV[j]; rBS.deltaCol[nDstPos] = rBS.deltaCol[j]; if (useTgSpace) { rBS.deltaTgSpace[nDstPos] = rBS.deltaTgSpace[j]; } } ++nDstPos; } } if (bIsDeltaPos) rBS.deltaPos.resize (nNbVertUsed); else rBS.deltaPos.resize (0); if (bIsDeltaNorm) rBS.deltaNorm.resize (nNbVertUsed); else rBS.deltaNorm.resize (0); if (bIsDeltaUV) rBS.deltaUV.resize (nNbVertUsed); else rBS.deltaUV.resize (0); if (bIsDeltaCol) rBS.deltaCol.resize (nNbVertUsed); else rBS.deltaCol.resize (0); if (bIsDeltaTgSpace) rBS.deltaTgSpace.resize (nNbVertUsed); else rBS.deltaTgSpace.resize (0); rBS.VertRefs.resize (nNbVertUsed); } } // *************************************************************************** void CMRMBuilder::buildBlendShapes (CMRMMesh& baseMesh, std::vector &bsList, uint32 VertexFlags) { uint32 i, j, k, m, destIndex; uint32 attId; CVectorH vh; vector &bsMeshes= baseMesh.BlendShapes; bsMeshes.resize (bsList.size()); for (i = 0; i < bsList.size(); ++i) { // Construct a blend shape like a mrm mesh nlassert (baseMesh.Vertices.size() == bsList[i]->Vertices.size()); bsMeshes[i].Vertices.resize (baseMesh.Vertices.size()); bsMeshes[i].Vertices = bsList[i]->Vertices; bsMeshes[i].NumAttributes = baseMesh.NumAttributes; for (j = 0; j < (uint32)bsMeshes[i].NumAttributes; ++j) bsMeshes[i].Attributes[j].resize(baseMesh.Attributes[j].size()); // For all corners parse the faces (given by the baseMesh) and construct blend shape mrm meshes for (j = 0; j < baseMesh.Faces.size(); ++j) for (k = 0; k < 3; ++k) { const CMesh::CCorner &srcCorner = bsList[i]->Faces[j].Corner[k]; CMRMCorner &neutralCorner = baseMesh.Faces[j].Corner[k]; attId= 0; if (VertexFlags & CVertexBuffer::NormalFlag) { destIndex = neutralCorner.Attributes[attId]; vh.x = srcCorner.Normal.x; vh.y = srcCorner.Normal.y; vh.z = srcCorner.Normal.z; vh.w = 0.0f; bsMeshes[i].Attributes[attId].operator[](destIndex) = vh; attId++; } if (VertexFlags & CVertexBuffer::PrimaryColorFlag) { destIndex = neutralCorner.Attributes[attId]; vh.x = srcCorner.Color.R; vh.y = srcCorner.Color.G; vh.z = srcCorner.Color.B; vh.w = srcCorner.Color.A; bsMeshes[i].Attributes[attId].operator[](destIndex) = vh; attId++; } if (VertexFlags & CVertexBuffer::SecondaryColorFlag) { destIndex = neutralCorner.Attributes[attId]; vh.x = srcCorner.Specular.R; vh.y = srcCorner.Specular.G; vh.z = srcCorner.Specular.B; vh.w = srcCorner.Specular.A; bsMeshes[i].Attributes[attId].operator[](destIndex) = vh; attId++; } for (m = 0; m < CVertexBuffer::MaxStage; ++m) { if (VertexFlags & (CVertexBuffer::TexCoord0Flag< &bsList, const CMRMParameters ¶ms, CMeshMRMGeom::CMeshBuildMRM &mrmMesh, uint numMaxMaterial) { // Temp data. CMRMMesh baseMesh; vector lodMeshs; CMRMMeshFinal finalMRM; vector finalBsMRM; uint32 vbFlags; nlassert(params.DistanceFinest>=0); nlassert(params.DistanceMiddle > params.DistanceFinest); nlassert(params.DistanceCoarsest > params.DistanceMiddle); // Copy some parameters. _SkinReduction= params.SkinReduction; // Skinning?? _Skinned= ((mbuild.VertexFlags & CVertexBuffer::PaletteSkinFlag)==CVertexBuffer::PaletteSkinFlag); // Skinning is OK only if SkinWeights are of same size as vertices. _Skinned= _Skinned && ( mbuild.Vertices.size()==mbuild.SkinWeights.size() ); // MeshInterface setuped ? _HasMeshInterfaces= buildMRMSewingMeshes(mbuild, params.NLods, params.Divisor); // from mbuild, build an internal MRM mesh representation. // vbFlags returned is the VBuffer format supported by CMRMBuilder. // NB: skinning is removed because skinning is made in software in CMeshMRMGeom. vbFlags= buildMrmBaseMesh(mbuild, baseMesh); // Construct all blend shapes in the same way we have constructed the basemesh mrm buildBlendShapes (baseMesh, bsList, vbFlags); // If skinned, must ensure that skin weights have weights in ascending order. if(_Skinned) { normalizeBaseMeshSkin(baseMesh); } // from this baseMesh, builds all LODs of the MRM, with geomorph info. NB: vertices/wedges are duplicated. buildAllLods ( baseMesh, lodMeshs, params.NLods, params.Divisor ); // From this array of LOD, build a finalMRM, by regrouping identical vertices/wedges, and compute index geomorphs. buildFinalMRM(lodMeshs, finalMRM); // From this finalMRM, build output: a CMeshBuildMRM. buildMeshBuildMrm(finalMRM, mrmMesh, vbFlags, numMaxMaterial, mbuild); // Copy degradation control params. mrmMesh.DistanceFinest= params.DistanceFinest; mrmMesh.DistanceMiddle= params.DistanceMiddle; mrmMesh.DistanceCoarsest= params.DistanceCoarsest; } // *************************************************************************** void CMRMBuilder::compileMRM(const CMesh::CMeshBuild &mbuild, std::vector &bsList, const CMRMParameters ¶ms, CMeshMRMSkinnedGeom::CMeshBuildMRM &mrmMesh, uint numMaxMaterial) { // Temp data. CMRMMesh baseMesh; vector lodMeshs; CMRMMeshFinal finalMRM; vector finalBsMRM; uint32 vbFlags; nlassert(params.DistanceFinest>=0); nlassert(params.DistanceMiddle > params.DistanceFinest); nlassert(params.DistanceCoarsest > params.DistanceMiddle); // Copy some parameters. _SkinReduction= params.SkinReduction; // Skinning?? _Skinned= ((mbuild.VertexFlags & CVertexBuffer::PaletteSkinFlag)==CVertexBuffer::PaletteSkinFlag); // Skinning is OK only if SkinWeights are of same size as vertices. _Skinned= _Skinned && ( mbuild.Vertices.size()==mbuild.SkinWeights.size() ); // MeshInterface setuped ? _HasMeshInterfaces= buildMRMSewingMeshes(mbuild, params.NLods, params.Divisor); // from mbuild, build an internal MRM mesh representation. // vbFlags returned is the VBuffer format supported by CMRMBuilder. // NB: skinning is removed because skinning is made in software in CMeshMRMGeom. vbFlags= buildMrmBaseMesh(mbuild, baseMesh); // Construct all blend shapes in the same way we have constructed the basemesh mrm buildBlendShapes (baseMesh, bsList, vbFlags); // If skinned, must ensure that skin weights have weights in ascending order. if(_Skinned) { normalizeBaseMeshSkin(baseMesh); } // from this baseMesh, builds all LODs of the MRM, with geomorph info. NB: vertices/wedges are duplicated. buildAllLods ( baseMesh, lodMeshs, params.NLods, params.Divisor ); // From this array of LOD, build a finalMRM, by regrouping identical vertices/wedges, and compute index geomorphs. buildFinalMRM(lodMeshs, finalMRM); // From this finalMRM, build output: a CMeshBuildMRM. buildMeshBuildMrm(finalMRM, mrmMesh, vbFlags, numMaxMaterial, mbuild); // Copy degradation control params. mrmMesh.DistanceFinest= params.DistanceFinest; mrmMesh.DistanceMiddle= params.DistanceMiddle; mrmMesh.DistanceCoarsest= params.DistanceCoarsest; } // *************************************************************************** // *************************************************************************** // MRM Interface system // *************************************************************************** // *************************************************************************** // *************************************************************************** bool CMRMBuilder::buildMRMSewingMeshes(const CMesh::CMeshBuild &mbuild, uint nWantedLods, uint divisor) { nlassert(nWantedLods>=1); nlassert(divisor>=1); if(mbuild.Interfaces.size()==0) return false; // must have same size if(mbuild.InterfaceLinks.size()!=mbuild.Vertices.size()) return false; // **** For each interface, MRM-ize it and store. _SewingMeshes.resize(mbuild.Interfaces.size()); for(uint i=0;i