public void SaveToOBJ(string directory, string objPath)
        {
            var objFilename = Path.GetFileNameWithoutExtension(objPath);
            var objDirectory = Path.GetDirectoryName(objPath);

            if (!Directory.Exists(objDirectory))
                Directory.CreateDirectory(objDirectory);

            // Because we need to form triangles, we go back to the depth image
            var quadOffsets = new System.Drawing.Point[]
            {
                new System.Drawing.Point(0, 0),
                new System.Drawing.Point(1, 0),
                new System.Drawing.Point(0, 1),
                new System.Drawing.Point(1, 0),
                new System.Drawing.Point(1, 1),
                new System.Drawing.Point(0, 1),
            };

            var streamWriter = new CultureInvariantStreamWriter(objDirectory + "/" + objFilename + ".obj");
            var mtlFileWriter = new CultureInvariantStreamWriter(objDirectory + "/" + objFilename + ".mtl");
            streamWriter.WriteLine("mtllib " + objFilename + ".mtl");
            uint nextVertexIndex = 1;
            var depthImage = new FloatImage(Kinect2Calibration.depthImageWidth, Kinect2Calibration.depthImageHeight);

            foreach (var camera in cameras)
            {
                mtlFileWriter.WriteLine("newmtl camera" + camera.name);
                mtlFileWriter.WriteLine("Ka 1.000000 1.000000 1.000000");
                mtlFileWriter.WriteLine("Kd 1.000000 1.000000 1.000000");
                mtlFileWriter.WriteLine("Ks 0.000000 0.000000 0.000000");
                mtlFileWriter.WriteLine("Tr 1.000000");
                mtlFileWriter.WriteLine("illum 1");
                mtlFileWriter.WriteLine("Ns 0.000000");
                mtlFileWriter.WriteLine("map_Kd " + objFilename + "_" + camera.name + ".jpg");

                File.Copy(directory + "/camera" + camera.name + "/color.jpg", objDirectory + "/" + objFilename + "_" + camera.name + ".jpg", true);

                streamWriter.WriteLine("usemtl camera" + camera.name);

                // load depth image
                string cameraDirectory = directory + "/camera" + camera.name;
                depthImage.LoadFromFile(cameraDirectory + "/mean.bin");

                var calibration = camera.calibration;
                var depthFrameToCameraSpaceTable = calibration.ComputeDepthFrameToCameraSpaceTable();
                var vertices = new Vertex[Kinect2Calibration.depthImageWidth * Kinect2Calibration.depthImageHeight];
                var colorCamera = new Matrix(4, 1);
                var depthCamera = new Matrix(4, 1);
                var world = new Matrix(4, 1);

                for (int y = 0; y < Kinect2Calibration.depthImageHeight; y++)
                    for (int x = 0; x < Kinect2Calibration.depthImageWidth; x++)
                    {
                        // depth camera coords
                        var depth = depthImage[x, y] / 1000f; // m
                        // convert to depth camera space
                        var point = depthFrameToCameraSpaceTable[Kinect2Calibration.depthImageWidth * y + x];
                        depthCamera[0] = point.X * depth;
                        depthCamera[1] = point.Y * depth;
                        depthCamera[2] = depth;
                        depthCamera[3] = 1;

                        // world coordinates
                        world.Mult(camera.pose, depthCamera);
                        //world.Scale(1.0 / world[3]); not necessary for this transform

                        // convert to color camera space
                        colorCamera.Mult(calibration.depthToColorTransform, depthCamera);
                        colorCamera.Scale(1.0 / colorCamera[3]);

                        // project to color image
                        double colorU, colorV;
                        CameraMath.Project(calibration.colorCameraMatrix, calibration.colorLensDistortion, colorCamera[0], colorCamera[1], colorCamera[2], out colorU, out colorV);
                        colorU /= (double)Kinect2Calibration.colorImageWidth;
                        colorV /= (double)Kinect2Calibration.colorImageHeight;

                        var vertex = new Vertex();
                        vertex.x = (float)world[0];
                        vertex.y = (float)world[1];
                        vertex.z = (float)world[2];
                        vertex.u = (float)colorU;
                        vertex.v = (float)colorV;
                        vertices[Kinect2Calibration.depthImageWidth * y + x] = vertex;

                    }

                streamWriter.WriteLine("g camera" + camera.name);
                streamWriter.WriteLine("usemtl camera" + camera.name);

                // examine each triangle
                for (int y = 0; y < Kinect2Calibration.depthImageHeight - 1; y++)
                    for (int x = 0; x < Kinect2Calibration.depthImageWidth - 1; x++)
                    {
                        int offseti = 0;
                        for (int tri = 0; tri < 2; tri++)
                        {
                            // the indexes of the vertices of this triangle
                            var i0 = Kinect2Calibration.depthImageWidth * (y + quadOffsets[offseti].Y) + (x + quadOffsets[offseti].X);
                            var i1 = Kinect2Calibration.depthImageWidth * (y + quadOffsets[offseti + 1].Y) + (x + quadOffsets[offseti + 1].X);
                            var i2 = Kinect2Calibration.depthImageWidth * (y + quadOffsets[offseti + 2].Y) + (x + quadOffsets[offseti + 2].X);

                            // is triangle valid?
                            bool nonZero = (vertices[i0].z != 0) && (vertices[i1].z != 0) && (vertices[i2].z != 0);

                            bool jump01 = Vertex.DistanceSquared(vertices[i0], vertices[i1]) < 0.2 * 0.2;
                            bool jump02 = Vertex.DistanceSquared(vertices[i0], vertices[i2]) < 0.2 * 0.2;
                            bool jump12 = Vertex.DistanceSquared(vertices[i1], vertices[i2]) < 0.2 * 0.2;

                            bool valid = nonZero && jump01 && jump02 && jump12;
                            if (valid)
                            {
                                // only add the vertex if we haven't already
                                if (vertices[i0].index == 0)
                                {
                                    streamWriter.WriteLine(vertices[i0]);
                                    vertices[i0].index = nextVertexIndex++;
                                }
                                if (vertices[i1].index == 0)
                                {
                                    streamWriter.WriteLine(vertices[i1]);
                                    vertices[i1].index = nextVertexIndex++;
                                }
                                if (vertices[i2].index == 0)
                                {
                                    streamWriter.WriteLine(vertices[i2]);
                                    vertices[i2].index = nextVertexIndex++;
                                }
                                streamWriter.WriteLine("f {0}/{0} {1}/{1} {2}/{2}", vertices[i0].index, vertices[i1].index, vertices[i2].index);
                            }
                            offseti += 3;
                        }
                    }
            }
            streamWriter.Close();
            mtlFileWriter.Close();
        }
        public static void SaveToPly(string filename, Float3Image pts3D)
        {
            using (var file = new CultureInvariantStreamWriter(filename, false, Encoding.ASCII))
            {
                // Write Header
                file.WriteLine("ply");
                file.WriteLine("format ascii 1.0");
                file.WriteLine("comment VCGLIB generated");

                // Write Elements
                file.WriteLine("element vertex " + pts3D.Width * pts3D.Height);
                file.WriteLine("property float x\nproperty float y\nproperty float z");
                //file.WriteLine("element face 0");
                //file.WriteLine("property list uchar int vertex_indices");
                file.WriteLine("end_header");

                for (int r = 0; r < pts3D.Height; r++)
                {
                    for (int c = 0; c < pts3D.Width; c++)
                    {
                        Float3 xyz = pts3D[c, r];
                        if (xyz.z == float.NaN)
                        {
                            file.WriteLine("0 0 0");
                            continue;
                        }
                        file.WriteLine(xyz.x.ToString("0.000") + " " + xyz.y.ToString("0.000") + " " + xyz.z.ToString("0.000"));
                    }
                }
            }
        }