/**
         * Convert the normals back to cartesian coordinates.
         */
        private float[] restoreNormals(int[] intNormals, float[] vertices, int[] indices, float normalPrecision)
        {
            // Calculate smooth normals (nominal normals)
            float[] smoothNormals = CommonAlgorithm.calcSmoothNormals(vertices, indices);
            float[] normals       = new float[vertices.Length];

            int vc = vertices.Length / Mesh.CTM_POSITION_ELEMENT_COUNT;
            int ne = Mesh.CTM_NORMAL_ELEMENT_COUNT;

            for (int i = 0; i < vc; ++i)
            {
                // Get the normal magnitude from the first of the three normal elements
                float magn = intNormals[i * ne] * normalPrecision;

                // Get phi and theta (spherical coordinates, relative to the smooth normal).
                double thetaScale, theta;
                int    intPhi = intNormals[i * ne + 1];
                double phi    = intPhi * (0.5 * Math.PI) * normalPrecision;
                if (intPhi == 0)
                {
                    thetaScale = 0.0f;
                }
                else if (intPhi <= 4)
                {
                    thetaScale = Math.PI / 2.0f;
                }
                else
                {
                    thetaScale = (2.0f * Math.PI) / intPhi;
                }
                theta = intNormals[i * ne + 2] * thetaScale - Math.PI;

                // Convert the normal from the angular representation (phi, theta) back to
                // cartesian coordinates
                double[] n2 = new double[3];
                n2[0] = Math.Sin(phi) * Math.Cos(theta);
                n2[1] = Math.Sin(phi) * Math.Sin(theta);
                n2[2] = Math.Cos(phi);
                float[]  basisAxes = CommonAlgorithm.makeNormalCoordSys(smoothNormals, i * ne);
                double[] n         = new double[3];
                for (int j = 0; j < 3; ++j)
                {
                    n[j] = basisAxes[j] * n2[0]
                           + basisAxes[3 + j] * n2[1]
                           + basisAxes[6 + j] * n2[2];
                }

                // Apply normal magnitude, and output to the normals array
                for (int j = 0; j < 3; ++j)
                {
                    normals[i * ne + j] = (float)(n[j] * magn);
                }
            }

            return(normals);
        }
        private float[] readVertices(CtmInputStream input, Grid grid, int vcount, float precision)
        {
            checkTag(input.readLittleInt(), VERT);
            int[] intVertices = input.readPackedInts(vcount, Mesh.CTM_POSITION_ELEMENT_COUNT, false);

            checkTag(input.readLittleInt(), GIDX);
            int[] gridIndices = input.readPackedInts(vcount, 1, false);
            for (int i = 1; i < vcount; i++)
            {
                gridIndices[i] += gridIndices[i - 1];
            }

            return(CommonAlgorithm.restoreVertices(intVertices, gridIndices, grid, precision));
        }
        /**
         * Calculate various forms of derivatives in order to reduce data entropy.
         */
        private int[] makeVertexDeltas(float[] vertices, SortableVertex[] sortVertices, Grid grid)
        {
            int vc = sortVertices.Length;

            // Vertex scaling factor
            float scale = 1.0f / vertexPrecision;

            float prevGridIndex = 0x7fffffff;
            int   prevDeltaX    = 0;

            int[] intVertices = new int[vc * Mesh.CTM_POSITION_ELEMENT_COUNT];
            for (int i = 0; i < vc; ++i)
            {
                // Get grid box origin
                int     gridIdx    = sortVertices [i].gridIndex;
                float[] gridOrigin = CommonAlgorithm.gridIdxToPoint(grid, gridIdx);

                // Get old vertex coordinate index (before vertex sorting)
                int oldIdx = sortVertices [i].originalIndex;

                // Store delta to the grid box origin in the integer vertex array. For the
                // X axis (which is sorted) we also do the delta to the previous coordinate
                // in the box.
                int deltaX = (int)Math.Floor(scale * (vertices [oldIdx * 3] - gridOrigin [0]) + 0.5f);
                if (gridIdx == prevGridIndex)
                {
                    intVertices [i * 3] = deltaX - prevDeltaX;
                }
                else
                {
                    intVertices [i * 3] = deltaX;
                }

                intVertices [i * 3 + 1] = (int)Math.Floor(scale * (vertices [oldIdx * 3 + 1] - gridOrigin [1]) + 0.5f);
                intVertices [i * 3 + 2] = (int)Math.Floor(scale * (vertices [oldIdx * 3 + 2] - gridOrigin [2]) + 0.5f);

                prevGridIndex = gridIdx;
                prevDeltaX    = deltaX;
            }

            return(intVertices);
        }
        /**
         * Convert the normals to a new coordinate system: magnitude, phi, theta
         * (relative to predicted smooth normals).
         */
        private int[] makeNormalDeltas(float[] vertices, float[] normals, int[] indices, SortableVertex[] sortVertices)
        {
            // Calculate smooth normals (Note: aVertices and aIndices use the sorted
            // index space, so smoothNormals will too)
            float[] smoothNormals = CommonAlgorithm.calcSmoothNormals(vertices, indices);

            // Normal scaling factor
            float scale = 1.0f / normalPrecision;

            int vc = vertices.Length / Mesh.CTM_POSITION_ELEMENT_COUNT;

            int[] intNormals = new int[vc * Mesh.CTM_NORMAL_ELEMENT_COUNT];
            for (int i = 0; i < vc; ++i)
            {
                // Get old normal index (before vertex sorting)
                int oldIdx = sortVertices [i].originalIndex;

                // Calculate normal magnitude (should always be 1.0 for unit length normals)
                float magn = (float)Math.Sqrt(normals [oldIdx * 3] * normals [oldIdx * 3]
                                              + normals [oldIdx * 3 + 1] * normals [oldIdx * 3 + 1]
                                              + normals [oldIdx * 3 + 2] * normals [oldIdx * 3 + 2]);
                if (magn < 1e-10f)
                {
                    magn = 1.0f;
                }

                // Invert magnitude if the normal is negative compared to the predicted
                // smooth normal
                if ((smoothNormals [i * 3] * normals [oldIdx * 3]
                     + smoothNormals [i * 3 + 1] * normals [oldIdx * 3 + 1]
                     + smoothNormals [i * 3 + 2] * normals [oldIdx * 3 + 2]) < 0.0f)
                {
                    magn = -magn;
                }

                // Store the magnitude in the first element of the three normal elements
                intNormals [i * 3] = (int)Math.Floor(scale * magn + 0.5f);

                // Normalize the normal (1 / magn) - and flip it if magn < 0
                magn = 1.0f / magn;
                float[] n = new float[3];
                for (int j = 0; j < 3; ++j)
                {
                    n [j] = normals [oldIdx * 3 + j] * magn;
                }

                // Convert the normal to angular representation (phi, theta) in a coordinate
                // system where the nominal (smooth) normal is the Z-axis
                float[] basisAxes = CommonAlgorithm.makeNormalCoordSys(smoothNormals, i * 3);
                float[] n2        = new float[3];
                for (int j = 0; j < 3; ++j)
                {
                    n2 [j] = basisAxes [j * 3] * n [0]
                             + basisAxes [j * 3 + 1] * n [1]
                             + basisAxes [j * 3 + 2] * n [2];
                }
                double phi, theta, thetaScale;
                if (n2 [2] >= 1.0f)
                {
                    phi = 0.0f;
                }
                else
                {
                    phi = Math.Acos(n2 [2]);
                }
                theta = Math.Atan2(n2 [1], n2 [0]);

                // Round phi and theta (spherical coordinates) to integers. Note: We let the
                // theta resolution vary with the x/y circumference (roughly phi).
                int intPhi = (int)Math.Floor(phi * (scale / (0.5 * Math.PI)) + 0.5);
                if (intPhi == 0)
                {
                    thetaScale = 0.0;
                }
                else if (intPhi <= 4)
                {
                    thetaScale = 2.0 / Math.PI;
                }
                else
                {
                    thetaScale = intPhi / (2.0 * Math.PI);
                }
                intNormals [i * 3 + 1] = intPhi;
                intNormals [i * 3 + 2] = (int)Math.Floor((theta + Math.PI) * thetaScale + 0.5f);
            }
            return(intNormals);
        }
        public override void encode(Mesh m, CtmOutputStream output)
        {
            Grid grid = setupGrid(m.vertices);

            SortableVertex[] sorted  = sortVertices(grid, m.vertices);
            int[]            vdeltas = makeVertexDeltas(m.vertices, sorted, grid);

            int[] gridIndicies = new int[m.getVertexCount()];
            gridIndicies [0] = sorted [0].gridIndex;
            for (int i = 1; i < m.getVertexCount(); ++i)
            {
                gridIndicies [i] = sorted [i].gridIndex - sorted [i - 1].gridIndex;
            }

            output.writeLittleInt(MG2Decoder.MG2_HEADER_TAG);

            output.writeLittleFloat(vertexPrecision);
            output.writeLittleFloat(normalPrecision);

            grid.writeToStream(output);

            output.writeLittleInt(MeshDecoder.VERT);
            output.writePackedInts(vdeltas, m.getVertexCount(), Mesh.CTM_POSITION_ELEMENT_COUNT, false);

            output.writeLittleInt(MG2Decoder.GIDX);
            output.writePackedInts(gridIndicies, m.getVertexCount(), 1, false);

            output.writeLittleInt(MeshDecoder.INDX);
            int[] indices = reIndexIndices(sorted, m.indices);
            rearrangeTriangles(indices);

            //write indicies
            {
                int[] deltas = new int[indices.Length];
                Array.Copy(indices, deltas, indices.Length);
                makeIndexDeltas(deltas);
                output.writePackedInts(deltas, m.getTriangleCount(), 3, false);
            }

            if (m.hasNormals())
            {
                for (int i = 1; i < m.getVertexCount(); i++)
                {
                    gridIndicies [i] += gridIndicies [i - 1];
                }
                float[] restoredv = CommonAlgorithm.restoreVertices(vdeltas, gridIndicies, grid, vertexPrecision);

                output.writeLittleInt(MeshDecoder.NORM);
                int[] intNormals = makeNormalDeltas(restoredv, m.normals, indices, sorted);
                output.writePackedInts(intNormals, m.getVertexCount(), Mesh.CTM_NORMAL_ELEMENT_COUNT, false);
            }

            foreach (AttributeData ad in m.texcoordinates)
            {
                output.writeLittleInt(MeshDecoder.TEXC);
                output.writeString(ad.name);
                output.writeString(ad.materialName);
                output.writeLittleFloat(ad.precision);
                int[] deltas = makeUVCoordDeltas(ad, sorted);
                output.writePackedInts(deltas, m.getVertexCount(), Mesh.CTM_UV_ELEMENT_COUNT, true);
            }

            foreach (AttributeData ad in m.attributs)
            {
                output.writeLittleInt(MeshDecoder.ATTR);
                output.writeString(ad.name);
                output.writeLittleFloat(ad.precision);
                int[] deltas = makeAttribDeltas(ad, sorted);
                output.writePackedInts(deltas, m.getVertexCount(), Mesh.CTM_ATTR_ELEMENT_COUNT, true);
            }
        }