diff --git a/Model-Modifier/Model-Modifier.vcxproj b/Model-Modifier/Model-Modifier.vcxproj index 99817b4..33865e0 100644 --- a/Model-Modifier/Model-Modifier.vcxproj +++ b/Model-Modifier/Model-Modifier.vcxproj @@ -43,6 +43,7 @@ + diff --git a/Model-Modifier/src/main.cpp b/Model-Modifier/src/main.cpp index 36e9778..3e2a8e1 100644 --- a/Model-Modifier/src/main.cpp +++ b/Model-Modifier/src/main.cpp @@ -135,6 +135,10 @@ int main() Object obj = objects.findObj(currObject); Material meshMat; + // object triangle count (QEM) + int triCount = static_cast(obj.m_TriFaceIndices.size()); + int desiredTriCount = triCount; + VertexBufferLayout layout; layout.Push(3); // 3d coordinates layout.Push(3); // normals @@ -366,17 +370,17 @@ int main() obj = Lo.Loop(); ModifyModel = true; } - //if (ImGui::Button("Garland Heckbert Simplication Surface")) - //{ - // Surface GH(obj); - // obj = GH.QEM(); - // mesh.Rebuild(obj); // rebuild mesh based on object info - // numFaces = static_cast(mesh.m_Object.m_FaceIndices.size()); // update number of faces - - // objectVA.Bind(); - // objectVB.AssignData(mesh.m_OutVertices, mesh.m_OutNumVert * sizeof(float), DRAW_MODE::STATIC); - // objectIB.AssignData(mesh.m_OutIndices, mesh.m_OutNumIdx, DRAW_MODE::STATIC); - //} + if (ImGui::Button("Garland Heckbert Simplification Surface")) + { + obj.MakeTriangleMesh(); // Triangulate first + Surface GH(obj); + obj = GH.QEM(desiredTriCount); + ModifyModel = true; + } + ImGui::Indent(); + ImGui::SliderInt("Desired count", &desiredTriCount, triCount/5, triCount); + ImGui::Unindent(); + ImGui::Unindent(); } @@ -626,6 +630,7 @@ int main() currObject = nextObject; obj = objects.findObj(currObject); // search for the object requested + triCount = static_cast(obj.m_TriFaceIndices.size()); desiredTriCount = triCount; ModifyModel = true; } @@ -635,6 +640,7 @@ int main() { mesh.Rebuild(obj); // rebuild mesh based on object info numFaces = static_cast(mesh.m_Object.m_FaceIndices.size()); // update number of faces + triCount = static_cast(obj.m_TriFaceIndices.size()); desiredTriCount = triCount; objectVA.Bind(); objectVB.AssignData(mesh.m_OutVertices, mesh.m_OutNumVert * sizeof(float), DRAW_MODE::STATIC); diff --git a/Model-Modifier/src/scene/surface/Surface.cpp b/Model-Modifier/src/scene/surface/Surface.cpp index b9dec95..22cd2e8 100644 --- a/Model-Modifier/src/scene/surface/Surface.cpp +++ b/Model-Modifier/src/scene/surface/Surface.cpp @@ -137,21 +137,3 @@ glm::vec3 Surface::ComputeFaceNormal(glm::vec3 pos0, glm::vec3 pos1, glm::vec3 p { return glm::normalize(glm::cross(pos1 - pos0, pos2 - pos0)); } - -// computer quadric matrix by summing all K_p matrices of a vertice v0 -glm::mat4 Surface::ComputeQuadric(VertexRecord v0) -{ - glm::mat4 quadric{ 0.0f }; - // for each neighbouring face, compute K_p - glm::vec3 position = v0.position; - for (unsigned int faceIdx : v0.adjFacesIdx) - { - FaceRecord face = m_Faces[faceIdx]; - glm::vec3 faceNormal = ComputeFaceNormal(face); - glm::vec4 plane{ faceNormal, -glm::dot(faceNormal, position) }; // plane equation ax+by+cz+d = 0 - - quadric += glm::outerProduct(plane, plane); // K_p - } - - return quadric; -} diff --git a/Model-Modifier/src/scene/surface/Surface.h b/Model-Modifier/src/scene/surface/Surface.h index 7f4baff..4803289 100644 --- a/Model-Modifier/src/scene/surface/Surface.h +++ b/Model-Modifier/src/scene/surface/Surface.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "../../external/glm/ext/vector_float3.hpp" #include "../../external/glm/ext/vector_uint2.hpp" @@ -37,6 +38,24 @@ struct FaceRecord std::vector edgesIdx; // each face can have n edges }; +// QEM +struct ValidPair +{ + unsigned int vertOne; + unsigned int vertTwo; + bool edge; + float error; + glm::vec3 newVert; +}; + +struct CompareValidPairs +{ + bool operator()(const ValidPair& a, const ValidPair& b) const + { + return a.error < b.error; // min heap + } +}; + class Surface { public: @@ -61,7 +80,8 @@ class Surface std::unordered_map> pointsPerEdge ); Object LoOutputOBJ(std::vector edgePoints); - glm::mat4 ComputeQuadric(VertexRecord v0); + glm::mat4 ComputeQuadric(VertexRecord v0); + Object GHOutputOBJ(); // Modification algorithms Object Beehive(); @@ -69,7 +89,7 @@ class Surface Object CatmullClark(); Object DooSabin(); Object Loop(); - Object QEM(); + Object QEM(unsigned int desiredCount); public: std::vector m_Vertices; @@ -80,4 +100,6 @@ class Surface glm::vec3 m_Max; std::unordered_map> m_EdgeIdxLookup; +private: + std::priority_queue, CompareValidPairs> m_QuadricErrorHeap; }; diff --git a/Model-Modifier/src/scene/surface/Surface_GarlandHeckbert.cpp b/Model-Modifier/src/scene/surface/Surface_GarlandHeckbert.cpp new file mode 100644 index 0000000..ee64782 --- /dev/null +++ b/Model-Modifier/src/scene/surface/Surface_GarlandHeckbert.cpp @@ -0,0 +1,219 @@ +#include "Surface.h" + + +////////// helper ////////// + +// computer quadric matrix by summing all K_p matrices of a vertice v0 +glm::mat4 Surface::ComputeQuadric(VertexRecord v0) +{ + glm::mat4 quadric{ 0.0f }; + // for each neighbouring face, compute K_p + glm::vec3 position = v0.position; + for (unsigned int faceIdx : v0.adjFacesIdx) + { + FaceRecord face = m_Faces[faceIdx]; + glm::vec3 faceNormal = ComputeFaceNormal(face); + glm::vec4 plane{ faceNormal, -glm::dot(faceNormal, position) }; // plane equation ax+by+cz+d = 0 + + quadric += glm::outerProduct(plane, plane); // K_p + } + + return quadric; +} + +Object Surface::GHOutputOBJ() +{ + // build new Object class + std::vector VertexPos; + std::unordered_map>> VertLookup; + std::vector> FaceIndices; + std::unordered_map NumberPolygons; + + for (VertexRecord vert : m_Vertices) + { + glm::vec3 vertPos = vert.position; + getVertIndex(vertPos, VertexPos, VertLookup); + } + + for (FaceRecord face : m_Faces) + { + std::vector vertsIdx; + for (unsigned int i = 0; i < 3; i++) + { + glm::vec3 vert = m_Vertices[face.verticesIdx[i]].position; + vertsIdx.push_back(getVertIndex(vert, VertexPos, VertLookup)); + } + FaceIndices.push_back(vertsIdx); + NumberPolygons[3] += 1; + } + + // build object + Object Obj; + Obj.m_Min = m_Min; Obj.m_Max = m_Max; + Obj.m_VertexPos = VertexPos; Obj.m_FaceIndices = FaceIndices; + Obj.m_NumPolygons = NumberPolygons; + Obj.TriangulateFaces(); + + return Obj; +} + + +////////// algorithms ////////// + +// Garland Heckbert simplification surface algorithm +Object Surface::QEM(unsigned int desiredCount) +{ + unsigned int numVertices = static_cast(m_Vertices.size()); + // calculate quadric error for each vertex + std::unordered_map quadricLookup; + for (unsigned int i = 0; i < numVertices; i++) + { + quadricLookup.insert({ i, ComputeQuadric(m_Vertices[i]) }); + } + + const float THRESHOLD = 0.05f; + + // select all valid pairs + std::vector> vertexPairLookup; // maintain vertex pairs + vertexPairLookup.resize(numVertices); + + std::vector validPairs; + for (unsigned int firstV = 0; firstV < numVertices; firstV++) + { + for (unsigned int secondV = firstV + 1; secondV < numVertices; secondV++) + { + auto searchx = m_EdgeIdxLookup.find(firstV); + if (searchx != m_EdgeIdxLookup.end()) + { + // search if ending vertex in our lookup + auto searchy = searchx->second.find(secondV); + if (searchy != searchx->second.end()) + { + // add the pair idx to the vertices + vertexPairLookup[firstV].insert(static_cast(validPairs.size())); + vertexPairLookup[secondV].insert(static_cast(validPairs.size())); + // found edge, add to valid pairs + ValidPair newPair{}; newPair.vertOne = firstV; newPair.vertTwo = secondV; newPair.edge = true; + validPairs.push_back(newPair); + } + } + // not found, check if the edges are close (distance smaller than threshold) + glm::vec3 firstPos = m_Vertices[firstV].position; + glm::vec3 secondPos = m_Vertices[secondV].position; + if (glm::distance(firstPos, secondPos) < THRESHOLD) + { + // add the pair idx to the vertices + vertexPairLookup[firstV].insert(static_cast(validPairs.size())); + vertexPairLookup[secondV].insert(static_cast(validPairs.size())); + // add to valid pairs + ValidPair newPair{}; newPair.vertOne = firstV; newPair.vertTwo = secondV; newPair.edge = false; + validPairs.push_back(newPair); + } + // else, do nothing, not a valid pair + } + } + + // compute the new point and error associated for each valid pair + + // each pair should contain a few pieces of information + // vertex 1, vertex 2, the new vertex position, the error after contraction, the quadric matrices for both 1 and 2, and the new vertex after contraction + for (ValidPair validPair : validPairs) + { + glm::mat4 firstQuad = quadricLookup[validPair.vertOne]; + glm::mat4 secondQuad = quadricLookup[validPair.vertTwo]; + glm::mat4 Quad = firstQuad + secondQuad; + + glm::mat4 MatQ = { + Quad[1][1], Quad[1][2], Quad[1][3], Quad[1][4], + Quad[1][2], Quad[2][2], Quad[2][3], Quad[2][4], + Quad[1][3], Quad[2][3], Quad[3][3], Quad[3][4], + 0, 0, 0, 1 + }; + if (glm::determinant(MatQ) != 0) + { + glm::vec4 newVPos = glm::inverse(MatQ) * glm::vec4{ 0, 0, 0, 1 }; + + // 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix + validPair.error = (newVPos * Quad * newVPos)[0]; + + // change back to 3d coords from homogeneous coordinates + validPair.newVert = glm::vec3(newVPos) / newVPos.w; + } + else + { + glm::vec4 end1 = { m_Vertices[validPair.vertOne].position, 1.0f }; + // 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix + float end1Error = (end1 * Quad * end1)[0]; + + glm::vec4 end2 = { m_Vertices[validPair.vertTwo].position, 1.0f }; + // 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix + float end2Error = (end2 * Quad * end2)[0]; + + glm::vec4 mid = (end1 + end2) / 2.0f; + // 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix + float midError = (mid * Quad * mid)[0]; + + float minError = std::min({ end1Error, end2Error, midError }); + validPair.error = minError; + if (minError == end1Error) + { + validPair.newVert = end1; + } + if (minError == end2Error) + { + validPair.newVert = end2; + } + if (minError == midError) + { + validPair.newVert = mid; + } + } + } + + // create a min-heap to store all the valid pairs, ordered by error cost + m_QuadricErrorHeap = std::priority_queue, CompareValidPairs>(validPairs.begin(), validPairs.end()); + + // iteratively remove the validpair with the lowest cost, until numFaces == desiredCount + unsigned int numFaces = static_cast(m_Faces.size()); + while (numFaces > desiredCount) + { + ValidPair leastCost = m_QuadricErrorHeap.top(); + m_QuadricErrorHeap.pop(); + + // contract the current pair + // TODO: need to replace the pair with the new vertex, change all neighbours to use the new vertex + // change m_Vertices[leastCost.vertOne] = newV? + m_Vertices[leastCost.vertOne].position = leastCost.newVert; + m_Vertices[leastCost.vertTwo].position = leastCost.newVert; + + // if there are faces that use this current edge, remove it + if (leastCost.edge) + { + // get edge index + unsigned int edgeIdx = getEdgeIndex({ leastCost.vertOne, leastCost.vertTwo }); + + for (auto it = m_Faces.begin(); it != m_Faces.end();) + { + FaceRecord face = *it; + if (std::find(face.edgesIdx.begin(), face.edgesIdx.end(), edgeIdx) != face.edgesIdx.end()) + it = m_Faces.erase(it); // Remove the element and update iterator + else + it++; + } + numFaces = static_cast(m_Faces.size()); + } + + + // update the cost of all valid pairs involving the current pair + //for (ValidPair validPair : validPairs) + //{ + // if ((validPair.vertOne == leastCost.vertOne) || (validPair.vertOne == leastCost.vertTwo)) + // // update cost + // else if ((validPair.vertTwo == leastCost.vertOne) || (validPair.vertTwo == leastCost.vertTwo)) + // // update cost + + //} + } + + return GHOutputOBJ(); +}