/******************************************************************************* Copyright (c) 2005-2009 David Williams This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. *******************************************************************************/ namespace PolyVox { template MeshDecimator::MeshDecimator(SurfaceMesh* pInputMesh/*, SurfaceMesh* pMeshOutput*/) :m_pInputMesh(pInputMesh) //,m_pOutputMesh(pOutputMesh) { fMinDotProductForCollapse = 0.999; } template void MeshDecimator::execute() { buildConnectivityData(); fillInitialVertexMetadata(m_vecInitialVertexMetadata); uint32_t noOfEdgesCollapsed; do { noOfEdgesCollapsed = performDecimationPass(fMinDotProductForCollapse); m_pInputMesh->removeDegenerateTris(); if(noOfEdgesCollapsed > 0) { //Build the connectivity data for the next pass. If this is slow, then look //at adjusting it (based on vertex mapper?) rather than bulding from scratch. buildConnectivityData(); } //m_pInputMesh->removeUnusedVertices(); }while(noOfEdgesCollapsed > 0); //Decimation will have invalidated LOD levels. m_pInputMesh->m_vecLodRecords.clear(); LodRecord lodRecord; lodRecord.beginIndex = 0; lodRecord.endIndex = m_pInputMesh->getNoOfIndices(); m_pInputMesh->m_vecLodRecords.push_back(lodRecord); } template void MeshDecimator::buildConnectivityData(void) { m_vecTriangles.clear(); m_vecTriangles.resize(m_pInputMesh->m_vecTriangleIndices.size() / 3); for(int triCt = 0; triCt < m_vecTriangles.size(); triCt++) { m_vecTriangles[triCt].v0 = m_pInputMesh->m_vecTriangleIndices[triCt * 3 + 0]; m_vecTriangles[triCt].v1 = m_pInputMesh->m_vecTriangleIndices[triCt * 3 + 1]; m_vecTriangles[triCt].v2 = m_pInputMesh->m_vecTriangleIndices[triCt * 3 + 2]; Vector3DFloat v0Pos = m_pInputMesh->m_vecVertices[m_vecTriangles[triCt].v0].position; Vector3DFloat v1Pos = m_pInputMesh->m_vecVertices[m_vecTriangles[triCt].v1].position; Vector3DFloat v2Pos = m_pInputMesh->m_vecVertices[m_vecTriangles[triCt].v2].position; Vector3DFloat v0v1 = v1Pos - v0Pos; Vector3DFloat v0v2 = v2Pos - v0Pos; Vector3DFloat normal = v0v1.cross(v0v2); normal.normalise(); m_vecTriangles[triCt].normal = normal; } trianglesUsingVertex.clear(); trianglesUsingVertex.resize(m_pInputMesh->m_vecVertices.size()); for(int ct = 0; ct < m_vecTriangles.size(); ct++) { trianglesUsingVertex[m_vecTriangles[ct].v0].push_back(ct); trianglesUsingVertex[m_vecTriangles[ct].v1].push_back(ct); trianglesUsingVertex[m_vecTriangles[ct].v2].push_back(ct); } } void MeshDecimator::fillInitialVertexMetadata(std::vector& vecVertexMetadata) { vecVertexMetadata.clear(); vecVertexMetadata.resize(m_pInputMesh->m_vecVertices.size()); //Initialise the metadata for(int ct = 0; ct < vecVertexMetadata.size(); ct++) { vecVertexMetadata[ct].normal.setElements(0,0,0); vecVertexMetadata[ct].isOnMaterialEdge = false; vecVertexMetadata[ct].vertexFlags.reset(); } for(int outerCt = 0; outerCt < m_pInputMesh->m_vecVertices.size()-1; outerCt++) { for(int innerCt = outerCt+1; innerCt < m_pInputMesh->m_vecVertices.size(); innerCt++) { if((m_pInputMesh->m_vecVertices[innerCt].position - m_pInputMesh->m_vecVertices[outerCt].position).lengthSquared() < 0.001f) { vecVertexMetadata[innerCt].isOnMaterialEdge = true; vecVertexMetadata[outerCt].isOnMaterialEdge = true; } } } for(int ct = 0; ct < m_pInputMesh->m_vecVertices.size(); ct++) { Vector3DFloat sumOfNormals(0.0f,0.0f,0.0f); for(list::const_iterator iter = trianglesUsingVertex[ct].cbegin(); iter != trianglesUsingVertex[ct].cend(); iter++) { sumOfNormals += m_vecTriangles[*iter].normal; } vecVertexMetadata[ct].normal = sumOfNormals; vecVertexMetadata[ct].normal.normalise(); } for(int ct = 0; ct < vecVertexMetadata.size(); ct++) { Region regTransformed = m_pInputMesh->m_Region; regTransformed.shift(regTransformed.getLowerCorner() * static_cast(-1)); //Plus and minus X vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_X, m_pInputMesh->m_vecVertices[ct].getPosition().getX() < regTransformed.getLowerCorner().getX() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_X, m_pInputMesh->m_vecVertices[ct].getPosition().getX() > regTransformed.getUpperCorner().getX() - 0.001f); //Plus and minus Y vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_Y, m_pInputMesh->m_vecVertices[ct].getPosition().getY() < regTransformed.getLowerCorner().getY() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_Y, m_pInputMesh->m_vecVertices[ct].getPosition().getY() > regTransformed.getUpperCorner().getY() - 0.001f); //Plus and minus Z vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_Z, m_pInputMesh->m_vecVertices[ct].getPosition().getZ() < regTransformed.getLowerCorner().getZ() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_Z, m_pInputMesh->m_vecVertices[ct].getPosition().getZ() > regTransformed.getUpperCorner().getZ() - 0.001f); } } void MeshDecimator::fillInitialVertexMetadata(std::vector& vecVertexMetadata) { vecVertexMetadata.clear(); vecVertexMetadata.resize(m_pInputMesh->m_vecVertices.size()); //Initialise the metadata for(int ct = 0; ct < vecVertexMetadata.size(); ct++) { vecVertexMetadata[ct].vertexFlags.reset(); vecVertexMetadata[ct].isOnMaterialEdge = false; vecVertexMetadata[ct].normal = m_pInputMesh->m_vecVertices[ct].normal; } for(int ct = 0; ct < vecVertexMetadata.size(); ct++) { Region regTransformed = m_pInputMesh->m_Region; regTransformed.shift(regTransformed.getLowerCorner() * static_cast(-1)); //Plus and minus X vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_X, m_pInputMesh->m_vecVertices[ct].getPosition().getX() < regTransformed.getLowerCorner().getX() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_X, m_pInputMesh->m_vecVertices[ct].getPosition().getX() > regTransformed.getUpperCorner().getX() - 0.001f); //Plus and minus Y vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_Y, m_pInputMesh->m_vecVertices[ct].getPosition().getY() < regTransformed.getLowerCorner().getY() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_Y, m_pInputMesh->m_vecVertices[ct].getPosition().getY() > regTransformed.getUpperCorner().getY() - 0.001f); //Plus and minus Z vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_NEG_Z, m_pInputMesh->m_vecVertices[ct].getPosition().getZ() < regTransformed.getLowerCorner().getZ() + 0.001f); vecVertexMetadata[ct].vertexFlags.set(VF_ON_GEOMETRY_EDGE_POS_Z, m_pInputMesh->m_vecVertices[ct].getPosition().getZ() > regTransformed.getUpperCorner().getZ() - 0.001f); } //If all three vertices have the same material then we are not on a material edge. If any vertex has a different //material then all three vertices are on a material edge. E.g. If one vertex has material 'a' and the other two //have material 'b', then the two 'b's are still on an edge (with 'a') even though they are the same as eachother. for(int ct = 0; ct < m_vecTriangles.size(); ct++) { uint32_t v0 = m_vecTriangles[ct].v0; uint32_t v1 = m_vecTriangles[ct].v1; uint32_t v2 = m_vecTriangles[ct].v2; bool allMatch = (m_pInputMesh->m_vecVertices[v0].material == m_pInputMesh->m_vecVertices[v1].material) && (m_pInputMesh->m_vecVertices[v1].material == m_pInputMesh->m_vecVertices[v2].material); if(!allMatch) { vecVertexMetadata[v0].isOnMaterialEdge = true; vecVertexMetadata[v1].isOnMaterialEdge = true; vecVertexMetadata[v2].isOnMaterialEdge = true; } } } template uint32_t MeshDecimator::performDecimationPass(float fMinDotProductForCollapse) { // Count how many edges we have collapsed uint32_t noOfEdgesCollapsed = 0; // The vertex mapper track whick vertices collapse onto which. vertexMapper.clear(); vertexMapper.resize(m_pInputMesh->m_vecVertices.size()); // Once a vertex is involved in a collapse (either because it // moves onto a different vertex, or because a different vertex // moves onto it) it is forbidden to take part in another collapse // this pass. We enforce this by setting the vertex locked flag. vertexLocked.clear(); vertexLocked.resize(m_pInputMesh->m_vecVertices.size()); // Initialise the vectors for(uint32_t ct = 0; ct < m_pInputMesh->m_vecVertices.size(); ct++) { // Initiall all vertices points to themselves vertexMapper[ct] = ct; // All vertices are initially unlocked vertexLocked[ct] = false; } //For each triangle... for(int ctIter = 0; ctIter < m_vecTriangles.size(); ctIter++) { if(attemptEdgeCollapse(m_vecTriangles[ctIter].v0, m_vecTriangles[ctIter].v1)) { ++noOfEdgesCollapsed; } if(attemptEdgeCollapse(m_vecTriangles[ctIter].v1, m_vecTriangles[ctIter].v2)) { ++noOfEdgesCollapsed; } if(attemptEdgeCollapse(m_vecTriangles[ctIter].v2, m_vecTriangles[ctIter].v0)) { ++noOfEdgesCollapsed; } } if(noOfEdgesCollapsed > 0) { //Fix up the indices for(int triCt = 0; triCt < m_pInputMesh->m_vecTriangleIndices.size(); triCt++) { uint32_t before = m_pInputMesh->m_vecTriangleIndices[triCt]; uint32_t after = vertexMapper[m_pInputMesh->m_vecTriangleIndices[triCt]]; if(before != after) { m_pInputMesh->m_vecTriangleIndices[triCt] = vertexMapper[m_pInputMesh->m_vecTriangleIndices[triCt]]; } } } return noOfEdgesCollapsed; } template bool MeshDecimator::attemptEdgeCollapse(uint32_t uSrc, uint32_t uDst) { //A vertex will be locked if it has already been involved in a collapse this pass. if(vertexLocked[uSrc] || vertexLocked[uDst]) { return false; } if(canCollapseEdge(uSrc, uDst)) { //Move v0 onto v1 vertexMapper[uSrc] = uDst; //vertexMapper[v1]; vertexLocked[uSrc] = true; vertexLocked[uDst] = true; //Increment the counter return true; } return false; } // Returns true if every bit which is set in 'a' is also set in 'b'. The reverse does not need to be true. template bool MeshDecimator::isSubset(std::bitset a, std::bitset b) { bool result = true; for(int ct = 0; ct < VF_NO_OF_FLAGS; ct++) { if(a.test(ct)) { if(b.test(ct) == false) { result = false; break; } } } return result; } //template bool MeshDecimator::canCollapseEdge(uint32_t uSrc, uint32_t uDst) { //For now, don't collapse vertices on material edges... if(m_vecInitialVertexMetadata[uSrc].isOnMaterialEdge || m_vecInitialVertexMetadata[uDst].isOnMaterialEdge) { return false; } // Vertices on the geometrical edge of surface meshes need special handling. // We check for this by whether any of the edge flags are set. if(m_vecInitialVertexMetadata[uSrc].vertexFlags.any() || m_vecInitialVertexMetadata[uDst].vertexFlags.any()) { // Assume we can't collapse until we prove otherwise... bool bCollapseGeometryEdgePair = false; // We can collapse normal vertices onto edge vertices, and edge vertices // onto corner vertices, but not vice-versa. Hence we check whether all // the edge flags in the source vertex are also set in the destination vertex. if(isSubset(m_vecInitialVertexMetadata[uSrc].vertexFlags, m_vecInitialVertexMetadata[uDst].vertexFlags)) { // In general adjacent regions surface meshes may collapse differently // and this can cause cracks. We solve this by only allowing the collapse // is the normals are exactly the same. We do not use the user provided // tolerence here (but do allow for floating point error). if(m_pInputMesh->m_vecVertices[uSrc].getNormal().dot(m_pInputMesh->m_vecVertices[uDst].getNormal()) > 0.999) { // Ok, this pair can collapse. bCollapseGeometryEdgePair = true; } } // Use the result. if(!bCollapseGeometryEdgePair) { return false; } } //Check the normals are within the threashold. if(m_pInputMesh->m_vecVertices[uSrc].getNormal().dot(m_pInputMesh->m_vecVertices[uDst].getNormal()) < fMinDotProductForCollapse) { return false; } //////////////////////////////////////////////////////////////////////////////// //The last test is whether we will flip any of the faces if(collapseChangesFaceNormals(uSrc,uDst, 0.9f)) { return false; } return true; } bool MeshDecimator::canCollapseEdge(uint32_t uSrc, uint32_t uDst) { bool bCanCollapse = true; if(m_vecInitialVertexMetadata[uSrc].isOnMaterialEdge) { bCanCollapse &= canCollapseMaterialEdge(uSrc, uDst); } if(m_vecInitialVertexMetadata[uSrc].vertexFlags.any()) { bCanCollapse &= canCollapseRegionEdge(uSrc, uDst); } if(bCanCollapse) //Only bother with this is the earlier tests passed. { bCanCollapse &= canCollapseNormalEdge(uSrc, uDst); } return bCanCollapse; } template bool MeshDecimator::canCollapseNormalEdge(uint32_t uSrc, uint32_t uDst) { return !collapseChangesFaceNormals(uSrc, uDst, 0.999f); } template bool MeshDecimator::canCollapseRegionEdge(uint32_t uSrc, uint32_t uDst) { if(isSubset(m_vecInitialVertexMetadata[uSrc].vertexFlags, m_vecInitialVertexMetadata[uDst].vertexFlags) == false) { return false; } if(m_vecInitialVertexMetadata[uSrc].normal.dot(m_vecInitialVertexMetadata[uDst].normal) < 0.999f) { return false; } return true; } template bool MeshDecimator::canCollapseMaterialEdge(uint32_t uSrc, uint32_t uDst) { return false; } template bool MeshDecimator::collapseChangesFaceNormals(uint32_t uSrc, uint32_t uDst, float fThreshold) { bool faceFlipped = false; list triangles = trianglesUsingVertex[uSrc]; for(list::iterator triIter = triangles.begin(); triIter != triangles.end(); triIter++) { uint32_t tri = *triIter; uint32_t v0Old = m_pInputMesh->m_vecTriangleIndices[tri * 3]; uint32_t v1Old = m_pInputMesh->m_vecTriangleIndices[tri * 3 + 1]; uint32_t v2Old = m_pInputMesh->m_vecTriangleIndices[tri * 3 + 2]; //Check if degenerate if((v0Old == v1Old) || (v1Old == v2Old) || (v2Old == v0Old)) { continue; } uint32_t v0New = v0Old; uint32_t v1New = v1Old; uint32_t v2New = v2Old; if(v0New == uSrc) v0New = uDst; if(v1New == uSrc) v1New = uDst; if(v2New == uSrc) v2New = uDst; //Check if degenerate if((v0New == v1New) || (v1New == v2New) || (v2New == v0New)) { continue; } Vector3DFloat v0OldPos = m_pInputMesh->m_vecVertices[vertexMapper[v0Old]].getPosition(); //Note: we need the vertex mapper here. These neighbouring vertices may have been moved. Vector3DFloat v1OldPos = m_pInputMesh->m_vecVertices[vertexMapper[v1Old]].getPosition(); Vector3DFloat v2OldPos = m_pInputMesh->m_vecVertices[vertexMapper[v2Old]].getPosition(); Vector3DFloat v0NewPos = m_pInputMesh->m_vecVertices[vertexMapper[v0New]].getPosition(); Vector3DFloat v1NewPos = m_pInputMesh->m_vecVertices[vertexMapper[v1New]].getPosition(); Vector3DFloat v2NewPos = m_pInputMesh->m_vecVertices[vertexMapper[v2New]].getPosition(); /*Vector3DFloat v0OldPos = m_vecVertices[v0Old].getPosition(); Vector3DFloat v1OldPos = m_vecVertices[v1Old].getPosition(); Vector3DFloat v2OldPos = m_vecVertices[v2Old].getPosition(); Vector3DFloat v0NewPos = m_vecVertices[v0New].getPosition(); Vector3DFloat v1NewPos = m_vecVertices[v1New].getPosition(); Vector3DFloat v2NewPos = m_vecVertices[v2New].getPosition();*/ Vector3DFloat OldNormal = (v1OldPos - v0OldPos).cross(v2OldPos - v1OldPos); Vector3DFloat NewNormal = (v1NewPos - v0NewPos).cross(v2NewPos - v1NewPos); OldNormal.normalise(); NewNormal.normalise(); // Note for after holiday - We are still getting faces flipping despite the following test. I tried changing // the 0.0 to 0.9 (which should still let coplanar faces merge) but oddly nothing then merged. Investigate this. float dotProduct = OldNormal.dot(NewNormal); //cout << dotProduct << endl; if(dotProduct < fThreshold) { //cout << " Face flipped!!" << endl; faceFlipped = true; /*vertexLocked[v0] = true; vertexLocked[v1] = true;*/ break; } } return faceFlipped; } }