Update May 13th: Download the working version here
MIT license, well documented and working without errors.
The latest source is also included in the lowermost reply.
Summary I am currently on a new implementation of a Quadric based mesh simplification algorithm. Its quite short (130 lines for the main part) , but apparently there is a bug somewhere. I searched already a while but couldnt find it yet. If one of you can spot it, any help is appreciated.
In turn, everyone gets a great mesh simplification method for free once it works
Its able to reduce 90% of 650.000 triangles in about 1 sec. (Single threaded) by using less memory than most other methods.
Algorithm Different from the original method, it does not store the per-edge-error in an edges list, but per triangle. This avoids the edges list completely as the edge list is slow creating and updating. On the other hand, every error is computed twice - but that's not so serious.
Methods :
calculate_error calculates the error between two vertices and output the vertex an edge might be reduced to.
reduce_polygons main function to reduce the mesh
The Problem Here a screenshot of the bug: Some faces are flipped and look displaced..
Here the source code
struct Triangle{ int v[3];double err[3];bool deleted; };
struct Vertex{ vec3f p,n;int dst,dirty;Matrix q; };
std::vector<Triangle> triangles;
std::vector<Vertex> vertices;
double vertex_error(Matrix q, double x, double y, double z);
double calculate_error(int id_v1, int id_v2, vec3f &p_result);
void reduce_polygons()
{
// Init defaults
loopi(0,vertices.size())
{
vertices[i].dst=-1;
vertices[i].q=Matrix(0.0);
vertices[i].dirty=false;
vertices[i].n=vec3f(0,0,0);
}
loopi(0,triangles.size()) triangles[i].deleted=0;
// Init Quadric by Plane
loopi(0,triangles.size())
{
Triangle &t=triangles[i]; vec3f n,p[3];
loopj(0,3) p[j]=vertices[t.v[j]].p;
n.cross(p[1]-p[0],p[2]-p[0]);
n.normalize();
loopj(0,3) vertices[t.v[j]].q =
vertices[t.v[j]].q+Matrix(n.x,n.y,n.z,-n.dot(p[0]));
}
// Calc Edge Error
loopi(0,triangles.size())
{
Triangle &t=triangles[i];vec3f p;
loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
}
int deleted_triangles=0;
loopl(0,25) // iteration
{
// remove vertices & mark deleted triangles
loopi(0,triangles.size()) if(!triangles[i].deleted)
{
Triangle &t=triangles[i];
if(vertices[t.v[0]].dirty) continue;
if(vertices[t.v[1]].dirty) continue;
if(vertices[t.v[2]].dirty) continue;
loopj(0,3)
{
int i0=t.v[ j ]; Vertex &v0 = vertices[i0];
int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
bool test1=t.err[j] < 0.00000001*l*l*l*l*l;
bool test2=(v0.p-v1.p).length()<0.3*l;
// remove based on edgelength and quadric error
if(test1 && test2)
{
calculate_error(i0,i1,v0.p);
//v0.p=(v1.p+v0.p)*0.5;
v0.q=v1.q+v0.q;
v1.dst=i0;
v1.dirty=true;
v0.dirty=true;
t.deleted=1;
deleted_triangles++;
break;
}
}
}
// update connectivity
loopi(0,triangles.size()) if(!triangles[i].deleted)
{
Triangle &t=triangles[i];
loopj(0,3)
if(vertices[t.v[j]].dst>=0)
{
t.v[j]=vertices[t.v[j]].dst;
if(t.v[j]==t.v[(j+1)%3] || t.v[j]==t.v[(j+2)%3] )
{
// two equal points -> delete triangle
t.deleted=1;
deleted_triangles++;
break;
}
t.dirty=1;
}
if(!t.dirty)continue;
// update error
bool dirty=0;
loopj(0,3) dirty|=vertices[t.v[j]].dirty;
if(!dirty)continue;
// update error
vec3f p;
loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
}
// clear dirty flag
loopi(0,vertices.size()) vertices[i].dirty=0;
}
}
double vertex_error(Matrix q, double x, double y, double z)
{
return q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[5]*y*y
+ 2*q[6]*y*z + 2*q[7]*y + q[10]*z*z + 2*q[11]*z + q[15];
}
double calculate_error(int id_v1, int id_v2, vec3f &p_result)
{
Matrix q_bar = vertices[id_v1].q + vertices[id_v2].q;
Matrix q_delta ( q_bar[0], q_bar[1], q_bar[2], q_bar[3],
q_bar[4], q_bar[5], q_bar[6], q_bar[7],
q_bar[8], q_bar[9], q_bar[10], q_bar[11],
0, 0, 0, 1);
if ( double det = q_delta.det(0, 1, 2, 4, 5, 6, 8, 9, 10) )
{
p_result.x = -1/det*(q_delta.det(1, 2, 3, 5, 6, 7, 9, 10, 11));
p_result.y = 1/det*(q_delta.det(0, 2, 3, 4, 6, 7, 8, 10, 11));
p_result.z = -1/det*(q_delta.det(0, 1, 3, 4, 5, 7, 8, 9, 11));
}
else
{
vec3f p1=vertices[id_v1].p;
vec3f p2=vertices[id_v1].p;
vec3f p3=(p1+p2)/2;
double error1 = vertex_error(q_bar, p1.x,p1.y,p1.z);
double error2 = vertex_error(q_bar, p2.x,p2.y,p2.z);
double error3 = vertex_error(q_bar, p3.x,p3.y,p3.z);
double min_error = min(error1, min(error2, error3));
if (error1 == min_error) p_result=p1;
if (error2 == min_error) p_result=p2;
if (error3 == min_error) p_result=p3;
}
double min_error = vertex_error(q_bar, p_result.x, p_result.y, p_result.z);
return min_error;
}
You can download the full source+mesh data here DOWNLOAD