/// <summary>
        /// Simplifies the mesh to a desired quality.
        /// </summary>
        /// <param name="quality">The target quality (between 0 and 1).</param>
        public void SimplifyMesh(float quality)
        {
            quality = Mathf.Clamp01(quality);

            int deletedTris = 0;
            ResizableArray <bool> deleted0 = new ResizableArray <bool>(20);
            ResizableArray <bool> deleted1 = new ResizableArray <bool>(20);
            var triangles       = this.triangles.Data;
            int triangleCount   = this.triangles.Length;
            int startTrisCount  = triangleCount;
            var vertices        = this.vertices.Data;
            int targetTrisCount = Mathf.RoundToInt(triangleCount * quality);

            for (int iteration = 0; iteration < 100; iteration++)
            {
                if ((startTrisCount - deletedTris) <= targetTrisCount)
                {
                    break;
                }

                // Update mesh once in a while
                if ((iteration % 5) == 0)
                {
                    UpdateMesh(iteration);
                    triangles     = this.triangles.Data;
                    triangleCount = this.triangles.Length;
                    vertices      = this.vertices.Data;
                }

                // Clear dirty flag
                for (int i = 0; i < triangleCount; i++)
                {
                    triangles[i].dirty = false;
                }

                // 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 * Math.Pow(iteration + 3, agressiveness);

                if (verbose)
                {
                    Debug.LogFormat("iteration {0} - triangles {1} threshold {2}", iteration, (startTrisCount - deletedTris), threshold);
                }

                // Remove vertices & mark deleted triangles
                RemoveVertexPass(startTrisCount, targetTrisCount, threshold, deleted0, deleted1, ref deletedTris);
            }

            CompactMesh();

            if (verbose)
            {
                Debug.LogFormat("Finished simplification with triangle count {0}", this.triangles.Length);
            }
        }
        /// <summary>
        /// Simplifies the mesh without losing too much quality.
        /// </summary>
        public void SimplifyMeshLossless()
        {
            int deletedTris = 0;
            ResizableArray <bool> deleted0 = new ResizableArray <bool>(0);
            ResizableArray <bool> deleted1 = new ResizableArray <bool>(0);
            var triangles      = this.triangles.Data;
            int triangleCount  = this.triangles.Length;
            int startTrisCount = triangleCount;
            var vertices       = this.vertices.Data;

            for (int iteration = 0; iteration < 9999; iteration++)
            {
                // Update mesh constantly
                UpdateMesh(iteration);
                triangles     = this.triangles.Data;
                triangleCount = this.triangles.Length;
                vertices      = this.vertices.Data;

                // Clear dirty flag
                for (int i = 0; i < triangleCount; i++)
                {
                    triangles[i].dirty = false;
                }

                // 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 = DoubleEpsilon;

                if (verbose)
                {
                    Debug.LogFormat("Lossless iteration {0} - triangles {1}", iteration, triangleCount);
                }

                // Remove vertices & mark deleted triangles
                RemoveVertexPass(startTrisCount, 0, threshold, deleted0, deleted1, ref deletedTris);

                if (deletedTris <= 0)
                {
                    break;
                }

                deletedTris = 0;
            }

            CompactMesh();

            if (verbose)
            {
                Debug.LogFormat("Finished simplification with triangle count {0}", this.triangles.Length);
            }
        }
        /// <summary>
        /// Creates a new mesh simplifier.
        /// </summary>
        /// <param name="mesh">The original mesh to simplify.</param>
        public MeshSimplifier(Mesh mesh)
        {
            if (mesh == null)
            {
                throw new ArgumentNullException("mesh");
            }

            triangles = new ResizableArray <Triangle>(0);
            vertices  = new ResizableArray <Vertex>(0);
            refs      = new ResizableArray <Ref>(0);

            Initialize(mesh);
        }
        /// <summary>
        /// Check if a triangle flips when this edge is removed
        /// </summary>
        private bool Flipped(Vector3d p, int i0, int i1, ref Vertex v0, ResizableArray <bool> deleted)
        {
            int tcount    = v0.tcount;
            var refs      = this.refs.Data;
            var triangles = this.triangles.Data;
            var vertices  = this.vertices.Data;

            for (int k = 0; k < tcount; k++)
            {
                Ref      r = refs[v0.tstart + k];
                Triangle t = triangles[r.tid];
                if (t.deleted)
                {
                    continue;
                }

                int s   = r.tvertex;
                int id1 = t[(s + 1) % 3];
                int id2 = t[(s + 2) % 3];
                if (id1 == i1 || id2 == i1)
                {
                    deleted[k] = true;
                    continue;
                }

                Vector3d d1 = vertices[id1].p - p;
                d1.Normalize();
                Vector3d d2 = vertices[id2].p - p;
                d2.Normalize();
                double dot = Vector3d.Dot(ref d1, ref d2);
                if (System.Math.Abs(dot) > 0.999)
                {
                    return(true);
                }

                Vector3d n;
                Vector3d.Cross(ref d1, ref d2, out n);
                n.Normalize();
                deleted[k] = false;
                dot        = Vector3d.Dot(ref n, ref t.n);
                if (dot < 0.2)
                {
                    return(true);
                }
            }

            return(false);
        }
        private void InitializeVertexAttribute <T>(T[] attributeValues, ref ResizableArray <T> attributeArray)
        {
            if (attributeValues != null && attributeValues.Length == vertices.Length)
            {
                if (attributeArray == null)
                {
                    attributeArray = new ResizableArray <T>(attributeValues.Length);
                }
                else
                {
                    attributeArray.Resize(attributeValues.Length);
                }

                var arrayData = attributeArray.Data;
                Array.Copy(attributeValues, 0, arrayData, 0, attributeValues.Length);
            }
            else
            {
                attributeArray = null;
            }
        }
        /// <summary>
        /// Finally compact mesh before exiting.
        /// </summary>
        private void CompactMesh()
        {
            int dst         = 0;
            var vertices    = this.vertices.Data;
            int vertexCount = this.vertices.Length;

            for (int i = 0; i < vertexCount; i++)
            {
                vertices[i].tcount = 0;
            }

            var triangles     = this.triangles.Data;
            int triangleCount = this.triangles.Length;

            for (int i = 0; i < triangleCount; i++)
            {
                var triangle = triangles[i];
                if (!triangle.deleted)
                {
                    triangles[dst++]             = triangle;
                    vertices[triangle.v0].tcount = 1;
                    vertices[triangle.v1].tcount = 1;
                    vertices[triangle.v2].tcount = 1;
                }
            }
            this.triangles.Resize(dst);
            triangles     = this.triangles.Data;
            triangleCount = dst;

            var vertNormals     = (this.vertNormals != null ? this.vertNormals.Data : null);
            var vertTangents    = (this.vertTangents != null ? this.vertTangents.Data : null);
            var vertUV1         = (this.vertUV1 != null ? this.vertUV1.Data : null);
            var vertUV2         = (this.vertUV2 != null ? this.vertUV2.Data : null);
            var vertUV3         = (this.vertUV3 != null ? this.vertUV3.Data : null);
            var vertUV4         = (this.vertUV4 != null ? this.vertUV4.Data : null);
            var vertColors      = (this.vertColors != null ? this.vertColors.Data : null);
            var vertBoneWeights = (this.vertBoneWeights != null ? this.vertBoneWeights.Data : null);

            dst = 0;
            for (int i = 0; i < vertexCount; i++)
            {
                var vert = vertices[i];
                if (vert.tcount > 0)
                {
                    vert.tstart = dst;
                    vertices[i] = vert;

                    if (dst != i)
                    {
                        vertices[dst].p = vert.p;
                        if (vertNormals != null)
                        {
                            vertNormals[dst] = vertNormals[i];
                        }
                        if (vertTangents != null)
                        {
                            vertTangents[dst] = vertTangents[i];
                        }
                        if (vertUV1 != null)
                        {
                            vertUV1[dst] = vertUV1[i];
                        }
                        if (vertUV2 != null)
                        {
                            vertUV2[dst] = vertUV2[i];
                        }
                        if (vertUV3 != null)
                        {
                            vertUV3[dst] = vertUV3[i];
                        }
                        if (vertUV4 != null)
                        {
                            vertUV4[dst] = vertUV4[i];
                        }
                        if (vertColors != null)
                        {
                            vertColors[dst] = vertColors[i];
                        }
                        if (vertBoneWeights != null)
                        {
                            vertBoneWeights[dst] = vertBoneWeights[i];
                        }
                    }
                    ++dst;
                }
            }
            for (int i = 0; i < triangleCount; i++)
            {
                var triangle = triangles[i];
                triangle.v0  = vertices[triangle.v0].tstart;
                triangle.v1  = vertices[triangle.v1].tstart;
                triangle.v2  = vertices[triangle.v2].tstart;
                triangles[i] = triangle;
            }

            this.vertices.Resize(dst, true);
            if (vertNormals != null)
            {
                this.vertNormals.Resize(dst, true);
            }
            if (vertTangents != null)
            {
                this.vertTangents.Resize(dst, true);
            }
            if (vertUV1 != null)
            {
                this.vertUV1.Resize(dst, true);
            }
            if (vertUV2 != null)
            {
                this.vertUV2.Resize(dst, true);
            }
            if (vertUV3 != null)
            {
                this.vertUV3.Resize(dst, true);
            }
            if (vertUV4 != null)
            {
                this.vertUV4.Resize(dst, true);
            }
            if (vertColors != null)
            {
                this.vertColors.Resize(dst, true);
            }
            if (vertBoneWeights != null)
            {
                this.vertBoneWeights.Resize(dst, true);
            }
        }
        /// <summary>
        /// Compact triangles, compute edge error and build reference list.
        /// </summary>
        /// <param name="iteration">The iteration index.</param>
        private void UpdateMesh(int iteration)
        {
            var triangles = this.triangles.Data;
            var vertices  = this.vertices.Data;

            int triangleCount = this.triangles.Length;
            int vertexCount   = this.vertices.Length;

            if (iteration > 0) // compact triangles
            {
                int dst = 0;
                for (int i = 0; i < triangleCount; i++)
                {
                    var triangle = triangles[i];
                    if (!triangle.deleted)
                    {
                        if (dst != i)
                        {
                            triangles[dst] = triangle;
                        }
                        dst++;
                    }
                }
                this.triangles.Resize(dst);
                triangles     = this.triangles.Data;
                triangleCount = 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 (int i = 0; i < vertexCount; i++)
                {
                    vertices[i].q = new SymmetricMatrix();
                }

                Vector3d        n, p0, p1, p2, p10, p20, dummy;
                SymmetricMatrix sm;
                for (int i = 0; i < triangleCount; i++)
                {
                    var triangle = triangles[i];
                    var vert0    = vertices[triangle.v0];
                    var vert1    = vertices[triangle.v1];
                    var vert2    = vertices[triangle.v2];
                    p0  = vert0.p;
                    p1  = vert1.p;
                    p2  = vert2.p;
                    p10 = p1 - p0;
                    p20 = p2 - p0;
                    Vector3d.Cross(ref p10, ref p20, out n);
                    n.Normalize();
                    triangles[i].n = n;

                    sm       = new SymmetricMatrix(n.x, n.y, n.z, -Vector3d.Dot(ref n, ref p0));
                    vert0.q += sm;
                    vert1.q += sm;
                    vert2.q += sm;
                    vertices[triangle.v0] = vert0;
                    vertices[triangle.v1] = vert1;
                    vertices[triangle.v2] = vert2;
                }

                for (int i = 0; i < triangleCount; i++)
                {
                    // Calc Edge Error
                    var triangle = triangles[i];
                    //triangle.area = CalculateArea(triangle.v0, triangle.v1, triangle.v2);
                    triangle.err0 = CalculateError(triangle.v0, triangle.v1, out dummy);
                    triangle.err1 = CalculateError(triangle.v1, triangle.v2, out dummy);
                    triangle.err2 = CalculateError(triangle.v2, triangle.v0, out dummy);
                    triangle.err3 = MathHelper.Min(triangle.err0, triangle.err1, triangle.err2);
                    triangles[i]  = triangle;
                }
            }

            // Init Reference ID list
            for (int i = 0; i < vertexCount; i++)
            {
                var vertex = vertices[i];
                vertex.tstart = 0;
                vertex.tcount = 0;
                vertices[i]   = vertex;
            }

            for (int i = 0; i < triangleCount; i++)
            {
                var triangle = triangles[i];
                ++vertices[triangle.v0].tcount;
                ++vertices[triangle.v1].tcount;
                ++vertices[triangle.v2].tcount;
            }

            int tstart = 0;

            for (int i = 0; i < vertexCount; i++)
            {
                var vertex = vertices[i];
                vertex.tstart = tstart;
                tstart       += vertex.tcount;
                vertex.tcount = 0;
                vertices[i]   = vertex;
            }

            // Write References
            this.refs.Resize(tstart);
            var refs = this.refs.Data;

            for (int i = 0; i < triangleCount; i++)
            {
                var triangle = triangles[i];
                var vert0    = vertices[triangle.v0];
                var vert1    = vertices[triangle.v1];
                var vert2    = vertices[triangle.v2];

                refs[vert0.tstart + vert0.tcount].Set(i, 0);
                refs[vert1.tstart + vert1.tcount].Set(i, 1);
                refs[vert2.tstart + vert2.tcount].Set(i, 2);
                ++vert0.tcount;
                ++vert1.tcount;
                ++vert2.tcount;

                vertices[triangle.v0] = vert0;
                vertices[triangle.v1] = vert1;
                vertices[triangle.v2] = vert2;
            }

            // Identify boundary : vertices[].border=0,1
            if (iteration == 0)
            {
                List <int> vcount = new List <int>();
                List <int> vids   = new List <int>();
                for (int i = 0; i < vertexCount; i++)
                {
                    vertices[i].border = false;
                }

                int ofs;
                int id;
                for (int i = 0; i < vertexCount; i++)
                {
                    var vertex = vertices[i];
                    vcount.Clear();
                    vids.Clear();

                    int tcount = vertex.tcount;
                    for (int j = 0; j < tcount; j++)
                    {
                        int      k = refs[vertex.tstart + j].tid;
                        Triangle t = triangles[k];
                        for (k = 0; k < 3; k++)
                        {
                            ofs = 0;
                            id  = t[k];
                            while (ofs < vcount.Count)
                            {
                                if (vids[ofs] == id)
                                {
                                    break;
                                }

                                ++ofs;
                            }

                            if (ofs == vcount.Count)
                            {
                                vcount.Add(1);
                                vids.Add(id);
                            }
                            else
                            {
                                ++vcount[ofs];
                            }
                        }
                    }

                    int vcountCount = vcount.Count;
                    for (int j = 0; j < vcountCount; j++)
                    {
                        if (vcount[j] == 1)
                        {
                            id = vids[j];
                            vertices[id].border = true;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Remove vertices and mark deleted triangles
        /// </summary>
        private void RemoveVertexPass(int startTrisCount, int targetTrisCount, double threshold, ResizableArray <bool> deleted0, ResizableArray <bool> deleted1, ref int deletedTris)
        {
            var triangles     = this.triangles.Data;
            int triangleCount = this.triangles.Length;
            var vertices      = this.vertices.Data;

            Vertex   v0, v1;
            Vector3d p;

            for (int i = 0; i < triangleCount; i++)
            {
                var t = triangles[i];
                if (t.dirty || t.deleted || t.err3 > threshold)
                {
                    continue;
                }

                t.GetErrors(errArr);
                for (int j = 0; j < 3; j++)
                {
                    if (errArr[j] > threshold)
                    {
                        continue;
                    }

                    int i0 = t[j];
                    int i1 = t[(j + 1) % 3];
                    v0 = vertices[i0];
                    v1 = vertices[i1];

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

                    // If borders should be kept
                    if (keepBorders && (v0.border || v1.border))
                    {
                        continue;
                    }

                    // Compute vertex to collapse to
                    CalculateError(i0, i1, out p);
                    deleted0.Resize(v0.tcount); // normals temporarily
                    deleted1.Resize(v1.tcount); // normals temporarily

                    // Don't remove if flipped
                    if (Flipped(p, i0, i1, ref v0, deleted0))
                    {
                        continue;
                    }
                    if (Flipped(p, i1, i0, ref v1, deleted1))
                    {
                        continue;
                    }

                    // Not flipped, so remove edge
                    v0.p         = p;
                    v0.q        += v1.q;
                    vertices[i0] = v0;
                    MergeVertices(i0, i1);

                    int tstart = refs.Length;
                    UpdateTriangles(i0, ref v0, deleted0, ref deletedTris);
                    UpdateTriangles(i0, ref v1, deleted1, ref deletedTris);

                    int tcount = refs.Length - tstart;
                    if (tcount <= v0.tcount)
                    {
                        // save ram
                        if (tcount > 0)
                        {
                            var refsArr = refs.Data;
                            Array.Copy(refsArr, tstart, refsArr, v0.tstart, tcount);
                        }
                    }
                    else
                    {
                        // append
                        vertices[i0].tstart = tstart;
                    }

                    vertices[i0].tcount = tcount;
                    break;
                }

                // Check if we are already done
                if ((startTrisCount - deletedTris) <= targetTrisCount)
                {
                    break;
                }
            }
        }
 /// <summary>
 /// Creates a new mesh simplifier.
 /// </summary>
 public MeshSimplifier()
 {
     triangles = new ResizableArray <Triangle>(0);
     vertices  = new ResizableArray <Vertex>(0);
     refs      = new ResizableArray <Ref>(0);
 }