Skip to content

Commit d769a5a

Browse files
committed
QEM wip
1 parent 2e9441b commit d769a5a

File tree

5 files changed

+261
-31
lines changed

5 files changed

+261
-31
lines changed

Model-Modifier/Model-Modifier.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<ClCompile Include="src\scene\object\ObjectSelect.cpp" />
4444
<ClCompile Include="src\scene\surface\Surface_CatmullClark.cpp" />
4545
<ClCompile Include="src\scene\surface\Surface_DooSabin.cpp" />
46+
<ClCompile Include="src\scene\surface\Surface_GarlandHeckbert.cpp" />
4647
<ClCompile Include="src\scene\surface\Surface_Loop.cpp" />
4748
<ClCompile Include="src\scene\surface\Surface.cpp" />
4849
<ClCompile Include="src\scene\util\OrderVertices.cpp" />

Model-Modifier/src/main.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ int main()
135135
Object obj = objects.findObj(currObject);
136136
Material meshMat;
137137

138+
// object triangle count (QEM)
139+
int triCount = static_cast<int>(obj.m_TriFaceIndices.size());
140+
int desiredTriCount = triCount;
141+
138142
VertexBufferLayout layout;
139143
layout.Push<float>(3); // 3d coordinates
140144
layout.Push<float>(3); // normals
@@ -366,17 +370,17 @@ int main()
366370
obj = Lo.Loop();
367371
ModifyModel = true;
368372
}
369-
//if (ImGui::Button("Garland Heckbert Simplication Surface"))
370-
//{
371-
// Surface GH(obj);
372-
// obj = GH.QEM();
373-
// mesh.Rebuild(obj); // rebuild mesh based on object info
374-
// numFaces = static_cast<unsigned int>(mesh.m_Object.m_FaceIndices.size()); // update number of faces
375-
376-
// objectVA.Bind();
377-
// objectVB.AssignData(mesh.m_OutVertices, mesh.m_OutNumVert * sizeof(float), DRAW_MODE::STATIC);
378-
// objectIB.AssignData(mesh.m_OutIndices, mesh.m_OutNumIdx, DRAW_MODE::STATIC);
379-
//}
373+
if (ImGui::Button("Garland Heckbert Simplification Surface"))
374+
{
375+
obj.MakeTriangleMesh(); // Triangulate first
376+
Surface GH(obj);
377+
obj = GH.QEM(desiredTriCount);
378+
ModifyModel = true;
379+
}
380+
ImGui::Indent();
381+
ImGui::SliderInt("Desired count", &desiredTriCount, triCount/5, triCount);
382+
ImGui::Unindent();
383+
380384
ImGui::Unindent();
381385
}
382386

@@ -626,6 +630,7 @@ int main()
626630
currObject = nextObject;
627631

628632
obj = objects.findObj(currObject); // search for the object requested
633+
triCount = static_cast<int>(obj.m_TriFaceIndices.size()); desiredTriCount = triCount;
629634

630635
ModifyModel = true;
631636
}
@@ -635,6 +640,7 @@ int main()
635640
{
636641
mesh.Rebuild(obj); // rebuild mesh based on object info
637642
numFaces = static_cast<unsigned int>(mesh.m_Object.m_FaceIndices.size()); // update number of faces
643+
triCount = static_cast<int>(obj.m_TriFaceIndices.size()); desiredTriCount = triCount;
638644

639645
objectVA.Bind();
640646
objectVB.AssignData(mesh.m_OutVertices, mesh.m_OutNumVert * sizeof(float), DRAW_MODE::STATIC);

Model-Modifier/src/scene/surface/Surface.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,3 @@ glm::vec3 Surface::ComputeFaceNormal(glm::vec3 pos0, glm::vec3 pos1, glm::vec3 p
137137
{
138138
return glm::normalize(glm::cross(pos1 - pos0, pos2 - pos0));
139139
}
140-
141-
// computer quadric matrix by summing all K_p matrices of a vertice v0
142-
glm::mat4 Surface::ComputeQuadric(VertexRecord v0)
143-
{
144-
glm::mat4 quadric{ 0.0f };
145-
// for each neighbouring face, compute K_p
146-
glm::vec3 position = v0.position;
147-
for (unsigned int faceIdx : v0.adjFacesIdx)
148-
{
149-
FaceRecord face = m_Faces[faceIdx];
150-
glm::vec3 faceNormal = ComputeFaceNormal(face);
151-
glm::vec4 plane{ faceNormal, -glm::dot(faceNormal, position) }; // plane equation ax+by+cz+d = 0
152-
153-
quadric += glm::outerProduct(plane, plane); // K_p
154-
}
155-
156-
return quadric;
157-
}

Model-Modifier/src/scene/surface/Surface.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <set>
44
#include <vector>
55
#include <unordered_map>
6+
#include <queue>
67

78
#include "../../external/glm/ext/vector_float3.hpp"
89
#include "../../external/glm/ext/vector_uint2.hpp"
@@ -37,6 +38,24 @@ struct FaceRecord
3738
std::vector<unsigned int> edgesIdx; // each face can have n edges
3839
};
3940

41+
// QEM
42+
struct ValidPair
43+
{
44+
unsigned int vertOne;
45+
unsigned int vertTwo;
46+
bool edge;
47+
float error;
48+
glm::vec3 newVert;
49+
};
50+
51+
struct CompareValidPairs
52+
{
53+
bool operator()(const ValidPair& a, const ValidPair& b) const
54+
{
55+
return a.error < b.error; // min heap
56+
}
57+
};
58+
4059
class Surface
4160
{
4261
public:
@@ -61,15 +80,16 @@ class Surface
6180
std::unordered_map<unsigned int, std::vector<glm::vec3>> pointsPerEdge
6281
);
6382
Object LoOutputOBJ(std::vector<glm::vec3> edgePoints);
64-
glm::mat4 ComputeQuadric(VertexRecord v0);
83+
glm::mat4 ComputeQuadric(VertexRecord v0);
84+
Object GHOutputOBJ();
6585

6686
// Modification algorithms
6787
Object Beehive();
6888
Object Snowflake();
6989
Object CatmullClark();
7090
Object DooSabin();
7191
Object Loop();
72-
Object QEM();
92+
Object QEM(unsigned int desiredCount);
7393

7494
public:
7595
std::vector<VertexRecord> m_Vertices;
@@ -80,4 +100,6 @@ class Surface
80100
glm::vec3 m_Max;
81101

82102
std::unordered_map<unsigned int, std::unordered_map<unsigned int, unsigned int>> m_EdgeIdxLookup;
103+
private:
104+
std::priority_queue<ValidPair, std::vector<ValidPair>, CompareValidPairs> m_QuadricErrorHeap;
83105
};
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#include "Surface.h"
2+
3+
4+
////////// helper //////////
5+
6+
// computer quadric matrix by summing all K_p matrices of a vertice v0
7+
glm::mat4 Surface::ComputeQuadric(VertexRecord v0)
8+
{
9+
glm::mat4 quadric{ 0.0f };
10+
// for each neighbouring face, compute K_p
11+
glm::vec3 position = v0.position;
12+
for (unsigned int faceIdx : v0.adjFacesIdx)
13+
{
14+
FaceRecord face = m_Faces[faceIdx];
15+
glm::vec3 faceNormal = ComputeFaceNormal(face);
16+
glm::vec4 plane{ faceNormal, -glm::dot(faceNormal, position) }; // plane equation ax+by+cz+d = 0
17+
18+
quadric += glm::outerProduct(plane, plane); // K_p
19+
}
20+
21+
return quadric;
22+
}
23+
24+
Object Surface::GHOutputOBJ()
25+
{
26+
// build new Object class
27+
std::vector<glm::vec3> VertexPos;
28+
std::unordered_map<float, std::unordered_map<float, std::unordered_map<float, unsigned int>>> VertLookup;
29+
std::vector<std::vector<unsigned int>> FaceIndices;
30+
std::unordered_map<unsigned int, unsigned int> NumberPolygons;
31+
32+
for (VertexRecord vert : m_Vertices)
33+
{
34+
glm::vec3 vertPos = vert.position;
35+
getVertIndex(vertPos, VertexPos, VertLookup);
36+
}
37+
38+
for (FaceRecord face : m_Faces)
39+
{
40+
std::vector<unsigned int> vertsIdx;
41+
for (unsigned int i = 0; i < 3; i++)
42+
{
43+
glm::vec3 vert = m_Vertices[face.verticesIdx[i]].position;
44+
vertsIdx.push_back(getVertIndex(vert, VertexPos, VertLookup));
45+
}
46+
FaceIndices.push_back(vertsIdx);
47+
NumberPolygons[3] += 1;
48+
}
49+
50+
// build object
51+
Object Obj;
52+
Obj.m_Min = m_Min; Obj.m_Max = m_Max;
53+
Obj.m_VertexPos = VertexPos; Obj.m_FaceIndices = FaceIndices;
54+
Obj.m_NumPolygons = NumberPolygons;
55+
Obj.TriangulateFaces();
56+
57+
return Obj;
58+
}
59+
60+
61+
////////// algorithms //////////
62+
63+
// Garland Heckbert simplification surface algorithm
64+
Object Surface::QEM(unsigned int desiredCount)
65+
{
66+
unsigned int numVertices = static_cast<unsigned int>(m_Vertices.size());
67+
// calculate quadric error for each vertex
68+
std::unordered_map<unsigned int, glm::mat4> quadricLookup;
69+
for (unsigned int i = 0; i < numVertices; i++)
70+
{
71+
quadricLookup.insert({ i, ComputeQuadric(m_Vertices[i]) });
72+
}
73+
74+
const float THRESHOLD = 0.05f;
75+
76+
// select all valid pairs
77+
std::vector<std::set<unsigned int>> vertexPairLookup; // maintain vertex pairs
78+
vertexPairLookup.resize(numVertices);
79+
80+
std::vector<ValidPair> validPairs;
81+
for (unsigned int firstV = 0; firstV < numVertices; firstV++)
82+
{
83+
for (unsigned int secondV = firstV + 1; secondV < numVertices; secondV++)
84+
{
85+
auto searchx = m_EdgeIdxLookup.find(firstV);
86+
if (searchx != m_EdgeIdxLookup.end())
87+
{
88+
// search if ending vertex in our lookup
89+
auto searchy = searchx->second.find(secondV);
90+
if (searchy != searchx->second.end())
91+
{
92+
// add the pair idx to the vertices
93+
vertexPairLookup[firstV].insert(static_cast<unsigned int>(validPairs.size()));
94+
vertexPairLookup[secondV].insert(static_cast<unsigned int>(validPairs.size()));
95+
// found edge, add to valid pairs
96+
ValidPair newPair{}; newPair.vertOne = firstV; newPair.vertTwo = secondV; newPair.edge = true;
97+
validPairs.push_back(newPair);
98+
}
99+
}
100+
// not found, check if the edges are close (distance smaller than threshold)
101+
glm::vec3 firstPos = m_Vertices[firstV].position;
102+
glm::vec3 secondPos = m_Vertices[secondV].position;
103+
if (glm::distance(firstPos, secondPos) < THRESHOLD)
104+
{
105+
// add the pair idx to the vertices
106+
vertexPairLookup[firstV].insert(static_cast<unsigned int>(validPairs.size()));
107+
vertexPairLookup[secondV].insert(static_cast<unsigned int>(validPairs.size()));
108+
// add to valid pairs
109+
ValidPair newPair{}; newPair.vertOne = firstV; newPair.vertTwo = secondV; newPair.edge = false;
110+
validPairs.push_back(newPair);
111+
}
112+
// else, do nothing, not a valid pair
113+
}
114+
}
115+
116+
// compute the new point and error associated for each valid pair
117+
118+
// each pair should contain a few pieces of information
119+
// 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
120+
for (ValidPair validPair : validPairs)
121+
{
122+
glm::mat4 firstQuad = quadricLookup[validPair.vertOne];
123+
glm::mat4 secondQuad = quadricLookup[validPair.vertTwo];
124+
glm::mat4 Quad = firstQuad + secondQuad;
125+
126+
glm::mat4 MatQ = {
127+
Quad[1][1], Quad[1][2], Quad[1][3], Quad[1][4],
128+
Quad[1][2], Quad[2][2], Quad[2][3], Quad[2][4],
129+
Quad[1][3], Quad[2][3], Quad[3][3], Quad[3][4],
130+
0, 0, 0, 1
131+
};
132+
if (glm::determinant(MatQ) != 0)
133+
{
134+
glm::vec4 newVPos = glm::inverse(MatQ) * glm::vec4{ 0, 0, 0, 1 };
135+
136+
// 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix
137+
validPair.error = (newVPos * Quad * newVPos)[0];
138+
139+
// change back to 3d coords from homogeneous coordinates
140+
validPair.newVert = glm::vec3(newVPos) / newVPos.w;
141+
}
142+
else
143+
{
144+
glm::vec4 end1 = { m_Vertices[validPair.vertOne].position, 1.0f };
145+
// 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix
146+
float end1Error = (end1 * Quad * end1)[0];
147+
148+
glm::vec4 end2 = { m_Vertices[validPair.vertTwo].position, 1.0f };
149+
// 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix
150+
float end2Error = (end2 * Quad * end2)[0];
151+
152+
glm::vec4 mid = (end1 + end2) / 2.0f;
153+
// 1x4 vector * 4x4 matrix * 4x1 vector yields a 1x1 matrix
154+
float midError = (mid * Quad * mid)[0];
155+
156+
float minError = std::min({ end1Error, end2Error, midError });
157+
validPair.error = minError;
158+
if (minError == end1Error)
159+
{
160+
validPair.newVert = end1;
161+
}
162+
if (minError == end2Error)
163+
{
164+
validPair.newVert = end2;
165+
}
166+
if (minError == midError)
167+
{
168+
validPair.newVert = mid;
169+
}
170+
}
171+
}
172+
173+
// create a min-heap to store all the valid pairs, ordered by error cost
174+
m_QuadricErrorHeap = std::priority_queue<ValidPair, std::vector<ValidPair>, CompareValidPairs>(validPairs.begin(), validPairs.end());
175+
176+
// iteratively remove the validpair with the lowest cost, until numFaces == desiredCount
177+
unsigned int numFaces = static_cast<unsigned int>(m_Faces.size());
178+
while (numFaces > desiredCount)
179+
{
180+
ValidPair leastCost = m_QuadricErrorHeap.top();
181+
m_QuadricErrorHeap.pop();
182+
183+
// contract the current pair
184+
// TODO: need to replace the pair with the new vertex, change all neighbours to use the new vertex
185+
// change m_Vertices[leastCost.vertOne] = newV?
186+
m_Vertices[leastCost.vertOne].position = leastCost.newVert;
187+
m_Vertices[leastCost.vertTwo].position = leastCost.newVert;
188+
189+
// if there are faces that use this current edge, remove it
190+
if (leastCost.edge)
191+
{
192+
// get edge index
193+
unsigned int edgeIdx = getEdgeIndex({ leastCost.vertOne, leastCost.vertTwo });
194+
195+
for (auto it = m_Faces.begin(); it != m_Faces.end();)
196+
{
197+
FaceRecord face = *it;
198+
if (std::find(face.edgesIdx.begin(), face.edgesIdx.end(), edgeIdx) != face.edgesIdx.end())
199+
it = m_Faces.erase(it); // Remove the element and update iterator
200+
else
201+
it++;
202+
}
203+
numFaces = static_cast<unsigned int>(m_Faces.size());
204+
}
205+
206+
207+
// update the cost of all valid pairs involving the current pair
208+
//for (ValidPair validPair : validPairs)
209+
//{
210+
// if ((validPair.vertOne == leastCost.vertOne) || (validPair.vertOne == leastCost.vertTwo))
211+
// // update cost
212+
// else if ((validPair.vertTwo == leastCost.vertOne) || (validPair.vertTwo == leastCost.vertTwo))
213+
// // update cost
214+
215+
//}
216+
}
217+
218+
return GHOutputOBJ();
219+
}

0 commit comments

Comments
 (0)