// http://voxels.blogspot.de/2014/05/quadric-mesh-simplification-with-source.html
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
//
// MIT License

// Changes:
// * Use Base::Vector3f as vec3f class
// * Move global variables to a class to make the algorithm usable for multi-threading
// * Comment out printf statements
// * Fix compiler warnings
// * Remove macros loop,i,j,k

#include <vector>
#include <Base/Vector3D.h>

#define _USE_MATH_DEFINES

typedef Base::Vector3f vec3f;

class SymmetricMatrix { 

public:

    // Constructor

    SymmetricMatrix(double c=0) { for (std::size_t i=0;i<10;++i ) m[i] = c; }

    SymmetricMatrix(double m11, double m12, double m13, double m14, 
                               double m22, double m23, double m24,
                                           double m33, double m34,
                                                       double m44) {
            m[0] = m11;  m[1] = m12;  m[2] = m13;  m[3] = m14; 
                         m[4] = m22;  m[5] = m23;  m[6] = m24; 
                                      m[7] = m33;  m[8] = m34;
                                                   m[9] = m44;
    }

    // Make plane

    SymmetricMatrix(double a,double b,double c,double d)
    {
        m[0] = a*a;  m[1] = a*b;  m[2] = a*c;  m[3] = a*d; 
                     m[4] = b*b;  m[5] = b*c;  m[6] = b*d; 
                                  m[7 ] =c*c;  m[8 ] = c*d;
                                               m[9 ] = d*d;
    }

    double operator[](int c) const { return m[c]; }

    // Determinant

    double det(int a11, int a12, int a13,
               int a21, int a22, int a23,
               int a31, int a32, int a33)
    {
        double det =  m[a11]*m[a22]*m[a33] + m[a13]*m[a21]*m[a32] + m[a12]*m[a23]*m[a31]
                    - m[a13]*m[a22]*m[a31] - m[a11]*m[a23]*m[a32] - m[a12]*m[a21]*m[a33];
        return det;
    }

    const SymmetricMatrix operator+(const SymmetricMatrix& n) const
    { 
        return SymmetricMatrix( m[0]+n[0],    m[1]+n[1],   m[2]+n[2],   m[3]+n[3],
                                              m[4]+n[4],   m[5]+n[5],   m[6]+n[6],
                                                           m[7]+n[7],   m[8]+n[8],
                                                                        m[9]+n[9]);
    }

    SymmetricMatrix& operator+=(const SymmetricMatrix& n)
    {
        m[0]+=n[0];   m[1]+=n[1];   m[2]+=n[2];   m[3]+=n[3]; 
        m[4]+=n[4];   m[5]+=n[5];   m[6]+=n[6];   m[7]+=n[7]; 
        m[8]+=n[8];   m[9]+=n[9];
        return *this; 
    }

    double m[10];
};
///////////////////////////////////////////

class Simplify
{
public:
    struct Triangle { int v[3];double err[4];int deleted,dirty;vec3f n; };
    struct Vertex { vec3f p;int tstart,tcount;SymmetricMatrix q;int border;};
    struct Ref { int tid,tvertex; }; 
    std::vector<Triangle> triangles;
    std::vector<Vertex> vertices;
    std::vector<Ref> refs;

    void simplify_mesh(int target_count, double tolerance, double aggressiveness=7);

private:
    // Helper functions

    double vertex_error(SymmetricMatrix q, double x, double y, double z);
    double calculate_error(int id_v1, int id_v2, vec3f &p_result);
    bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted);
    void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles);
    void update_mesh(int iteration);
    void compact_mesh();
};

//
// Main simplification function 
//
// target_count   : target nr. of triangles
// tolerance      : tolerance for the quadratic errors
// aggressiveness : sharpness to increase the threshold.
//                  5..8 are good numbers
//                  more iterations yield higher quality
// If the passed tolerance is > 0 then this will be used to check
// the quadratic error metric of all triangles. If none of them is below
// the tolerance the algorithm will stop at this point. The number of the
// remaining triangles usually will be higher than \a target_count
//
void Simplify::simplify_mesh(int target_count, double tolerance, double aggressiveness)
{
    // init
    //printf("%s - start\n",__FUNCTION__);
    //int timeStart=timeGetTime();

    for (std::size_t i=0;i<triangles.size();++i)
        triangles[i].deleted=0;

    // main iteration loop 

    int deleted_triangles=0; 
    std::vector<int> deleted0,deleted1;
    int triangle_count=triangles.size();

    for (int iteration=0;iteration<100;++iteration)
    {
        // target number of triangles reached ? Then break
        //printf("iteration %d - triangles %d\n",iteration,triangle_count-deleted_triangles);
        if (triangle_count-deleted_triangles<=target_count)
            break;

        // update mesh once in a while
        if (iteration%5==0) 
        {
            update_mesh(iteration);
        }

        // clear dirty flag
        for (std::size_t i=0;i<triangles.size();++i)
            triangles[i].dirty=0;

        //
        // All triangles with edges below the threshold will be removed
        //
        // The following numbers works well for most models.
        // If it does not, try to adjust the 3 parameters
        //
        double threshold = 0.000000001*pow(double(iteration+3),aggressiveness);
        if (tolerance > 0.0)
        {
            bool canContinue = false;
            for (std::size_t i=0;i<triangles.size();++i)
            {
                Triangle &t=triangles[i];
                if (t.deleted)
                    continue;
                if (t.dirty)
                    continue;
                if (fabs(t.err[3])<tolerance)
                {
                    canContinue = true;
                    break;
                }
            }

            if (!canContinue)
                break;
        }

        // remove vertices & mark deleted triangles
        for (std::size_t i=0;i<triangles.size();++i)
        {
            Triangle &t=triangles[i];
            if (t.err[3]>threshold)
                continue;
            if (t.deleted)
                continue;
            if (t.dirty)
                continue;

            for (std::size_t j=0;j<3;++j)
            {
                if (t.err[j]<threshold)
                {
                    int i0=t.v[ j     ]; Vertex &v0 = vertices[i0]; 
                    int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];

                    // Border check
                    if (v0.border != v1.border)
                        continue;

                    // Compute vertex to collapse to
                    vec3f p;
                    calculate_error(i0,i1,p);

                    deleted0.resize(v0.tcount); // normals temporarily
                    deleted1.resize(v1.tcount); // normals temporarily

                    // don't remove if flipped
                    if (flipped(p,i0,i1,v0,v1,deleted0))
                        continue;
                    if (flipped(p,i1,i0,v1,v0,deleted1))
                        continue;

                    // not flipped, so remove edge
                    v0.p=p;
                    v0.q=v1.q+v0.q;
                    int tstart=refs.size();

                    update_triangles(i0,v0,deleted0,deleted_triangles);
                    update_triangles(i0,v1,deleted1,deleted_triangles);

                    int tcount=refs.size()-tstart;

                    if (tcount<=v0.tcount)
                    {
                        // save ram
                        if (tcount)
                            memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));
                    }
                    else
                    {
                        // append
                        v0.tstart=tstart;
                    }

                    v0.tcount=tcount;
                    break;
                }
            }

            // done?
            if (triangle_count-deleted_triangles<=target_count)
                break;
        }
    }

    // clean up mesh
    compact_mesh();

    // ready
    //int timeEnd=timeGetTime();
    //printf("%s - %d/%d %d%% removed in %d ms\n",__FUNCTION__,
    //    triangle_count-deleted_triangles,
    //    triangle_count,deleted_triangles*100/triangle_count,
    //    timeEnd-timeStart);

}

// Check if a triangle flips when this edge is removed

bool Simplify::flipped(vec3f p, int i0, int i1,
                       Vertex &v0,
                       Vertex &v1,
                       std::vector<int> &deleted)
{
    (void)i0; (void)v1;
    int bordercount=0;
    for (int k=0;k<v0.tcount;++k)
    {
        Triangle &t=triangles[refs[v0.tstart+k].tid]; 
        if (t.deleted)
            continue;

        int s=refs[v0.tstart+k].tvertex;
        int id1=t.v[(s+1)%3];
        int id2=t.v[(s+2)%3];

        if (id1==i1 || id2==i1) // delete ?
        {
            bordercount++;
            deleted[k]=1;
            continue;
        }
        vec3f d1 = vertices[id1].p-p; d1.Normalize();
        vec3f d2 = vertices[id2].p-p; d2.Normalize(); 
        if (fabs(d1.Dot(d2))>0.999)
            return true;
        vec3f n;
        n = d1.Cross(d2);
        n.Normalize();
        deleted[k]=0;
        if (n.Dot(t.n)<0.2)
            return true;
    }
    return false;
}

// Update triangle connections and edge error after a edge is collapsed

void Simplify::update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles)
{
    vec3f p;
    for (int k=0;k<v.tcount;++k)
    {
        Ref &r=refs[v.tstart+k];
        Triangle &t=triangles[r.tid]; 
        if (t.deleted)
            continue;
        if (deleted[k]) 
        {
            t.deleted=1;
            deleted_triangles++;
            continue;
        }
        t.v[r.tvertex]=i0;
        t.dirty=1;
        t.err[0]=calculate_error(t.v[0],t.v[1],p);
        t.err[1]=calculate_error(t.v[1],t.v[2],p);
        t.err[2]=calculate_error(t.v[2],t.v[0],p);
        t.err[3]=std::min(t.err[0],std::min(t.err[1],t.err[2]));
        refs.push_back(r);
    }
}

// compact triangles, compute edge error and build reference list

void Simplify::update_mesh(int iteration)
{
    if(iteration>0) // compact triangles
    {
        int dst=0;
        for (std::size_t i=0;i<triangles.size();++i)
        {
            if (!triangles[i].deleted)
            {
                triangles[dst++]=triangles[i];
            }
        }
        triangles.resize(dst);
    }
    //
    // Init Quadrics by Plane & Edge Errors
    //
    // required at the beginning ( iteration == 0 )
    // recomputing during the simplification is not required,
    // but mostly improves the result for closed meshes
    //
    if (iteration == 0)
    {
        for (std::size_t i=0;i<vertices.size();++i)
            vertices[i].q=SymmetricMatrix(0.0);

        for (std::size_t i=0;i<triangles.size();++i)
        {
            Triangle &t=triangles[i]; 
            vec3f n,p[3];
            for (std::size_t j=0;j<3;++j)
                p[j]=vertices[t.v[j]].p;
            n = (p[1]-p[0]).Cross(p[2]-p[0]);
            n.Normalize();
            t.n=n;
            for (std::size_t j=0;j<3;++j)
                vertices[t.v[j]].q = vertices[t.v[j]].q+SymmetricMatrix(n.x,n.y,n.z,-n.Dot(p[0]));
        }
        for (std::size_t i=0;i<triangles.size();++i)
        {
            // Calc Edge Error
            Triangle &t=triangles[i];vec3f p;
            for (std::size_t j=0;j<3;++j)
                t.err[j] = calculate_error(t.v[j],t.v[(j+1)%3],p);
            t.err[3]=std::min(t.err[0],std::min(t.err[1],t.err[2]));
        }
    }

    // Init Reference ID list
    for (std::size_t i=0;i<vertices.size();++i)
    {
        vertices[i].tstart=0;
        vertices[i].tcount=0;
    }
    for (std::size_t i=0;i<triangles.size();++i)
    {
        Triangle &t=triangles[i];
        for (std::size_t j=0;j<3;++j)
            vertices[t.v[j]].tcount++;
    }
    int tstart=0;
    for (std::size_t i=0;i<vertices.size();++i)
    {
        Vertex &v=vertices[i];
        v.tstart=tstart;
        tstart+=v.tcount;
        v.tcount=0;
    }

    // Write References
    refs.resize(triangles.size()*3);
    for (std::size_t i=0;i<triangles.size();++i)
    {
        Triangle &t=triangles[i];
        for (std::size_t j=0;j<3;++j)
        {
            Vertex &v=vertices[t.v[j]];
            refs[v.tstart+v.tcount].tid=i;
            refs[v.tstart+v.tcount].tvertex=j;
            v.tcount++;
        }
    }

    // Identify boundary : vertices[].border=0,1 
    if (iteration == 0)
    {
        std::vector<int> vcount,vids;

        for (std::size_t i=0;i<vertices.size();++i)
            vertices[i].border=0;

        for (std::size_t i=0;i<vertices.size();++i)
        {
            Vertex &v=vertices[i];
            vcount.clear();
            vids.clear();
            for (int j=0; j<v.tcount; ++j)
            {
                int k=refs[v.tstart+j].tid;
                Triangle &t=triangles[k];
                for (int k=0;k<3;++k)
                {
                    std::size_t ofs=0; int id=t.v[k];
                    while(ofs<vcount.size())
                    {
                        if (vids[ofs]==id)
                            break;
                        ofs++;
                    }
                    if(ofs==vcount.size())
                    {
                        vcount.push_back(1);
                        vids.push_back(id);
                    }
                    else
                    {
                        vcount[ofs]++;
                    }
                }
            }
            for (std::size_t j=0;j<vcount.size();++j) {
                if (vcount[j]==1)
                    vertices[vids[j]].border=1;
            }
        }
    }
}

// Finally compact mesh before exiting

void Simplify::compact_mesh()
{
    int dst=0;
    for (std::size_t i=0;i<vertices.size();++i)
    {
        vertices[i].tcount=0;
    }
    for (std::size_t i=0;i<triangles.size();++i)
    {
        if (!triangles[i].deleted)
        {
            Triangle &t=triangles[i];
            triangles[dst++]=t;
            for (std::size_t j=0;j<3;++j)
                vertices[t.v[j]].tcount=1;
        }
    }

    triangles.resize(dst);
    dst=0;
    for (std::size_t i=0;i<vertices.size();++i)
    {
        if (vertices[i].tcount)
        {
            vertices[i].tstart=dst;
            vertices[dst].p=vertices[i].p;
            dst++;
        }
    }
    for (std::size_t i=0;i<triangles.size();++i)
    {
        Triangle &t=triangles[i];
        for (std::size_t j=0;j<3;++j)
            t.v[j]=vertices[t.v[j]].tstart;
    }
    vertices.resize(dst);
}

// Error between vertex and Quadric

double Simplify::vertex_error(SymmetricMatrix 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[4]*y*y
         + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];
}

// Error for one edge

double Simplify::calculate_error(int id_v1, int id_v2, vec3f &p_result)
{
    // compute interpolated vertex 

    SymmetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
    bool   border = vertices[id_v1].border & vertices[id_v2].border;
    double error=0;
    double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);

    if (det != 0 && !border)
    {
        // q_delta is invertible
        p_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8));    // vx = A41/det(q_delta) 
        p_result.y =  1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8));    // vy = A42/det(q_delta) 
        p_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5,  8));    // vz = A43/det(q_delta) 
        error = vertex_error(q, p_result.x, p_result.y, p_result.z);
    }
    else
    {
        // det = 0 -> try to find best result
        vec3f p1=vertices[id_v1].p;
        vec3f p2=vertices[id_v2].p;
        vec3f p3=(p1+p2)/2;
        double error1 = vertex_error(q, p1.x,p1.y,p1.z);
        double error2 = vertex_error(q, p2.x,p2.y,p2.z);
        double error3 = vertex_error(q, p3.x,p3.y,p3.z);
        error = std::min(error1, std::min(error2, error3));
        if (error1 == error)
            p_result=p1;
        if (error2 == error)
            p_result=p2;
        if (error3 == error)
            p_result=p3;
    }
    return error;
}

///////////////////////////////////////////
