Exemple #1
0
        /// <summary>
        ///     Exports a Model to the Collada format.
        ///     See: https://www.khronos.org/files/collada_spec_1_4.pdf for more information.
        /// </summary>
        /// <param name="model">The Model that will be exported</param>
        /// <param name="fileName">The output File Name</param>
        /// <param name="modelIndex">Index of the model to be exported</param>
        /// <param name="skeletalAnimationIndex">(Optional) Index of the skeletal animation</param>
        public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex, int skeletalAnimationIndex = -1)
        {
            RenderBase.OModel mdl = model.model[modelIndex];
            COLLADA           dae = new COLLADA();

            dae.asset.created  = DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ");
            dae.asset.modified = dae.asset.created;

            foreach (RenderBase.OTexture tex in model.texture)
            {
                daeImage img = new daeImage();
                img.id        = tex.name + "_id";
                img.name      = tex.name;
                img.init_from = "./" + tex.name + ".png";

                dae.library_images.Add(img);
            }

            foreach (RenderBase.OMaterial mat in mdl.material)
            {
                daeMaterial mtl = new daeMaterial();
                mtl.name = mat.name + "_mat";
                mtl.id   = mtl.name + "_id";
                mtl.instance_effect.url = "#eff_" + mat.name + "_id";

                dae.library_materials.Add(mtl);

                daeEffect eff = new daeEffect();
                eff.id   = "eff_" + mat.name + "_id";
                eff.name = "eff_" + mat.name;

                daeParam surface = new daeParam();
                surface.surface           = new daeParamSurfaceElement();
                surface.sid               = "img_surface";
                surface.surface.type      = "2D";
                surface.surface.init_from = mat.name0 + "_id";
                surface.surface.format    = "PNG";
                eff.profile_COMMON.newparam.Add(surface);

                daeParam sampler = new daeParam();
                sampler.sampler2D        = new daeParamSampler2DElement();
                sampler.sid              = "img_sampler";
                sampler.sampler2D.source = "img_surface";

                switch (mat.textureMapper[0].wrapU)
                {
                case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_s = "WRAP"; break;

                case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_s = "MIRROR"; break;

                case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_s = "CLAMP"; break;

                case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_s = "BORDER"; break;

                default: sampler.sampler2D.wrap_s = "NONE"; break;
                }

                switch (mat.textureMapper[0].wrapV)
                {
                case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_t = "WRAP"; break;

                case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_t = "MIRROR"; break;

                case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_t = "CLAMP"; break;

                case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_t = "BORDER"; break;

                default: sampler.sampler2D.wrap_t = "NONE"; break;
                }

                switch (mat.textureMapper[0].minFilter)
                {
                case RenderBase.OTextureMinFilter.linearMipmapLinear: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_LINEAR"; break;

                case RenderBase.OTextureMinFilter.linearMipmapNearest: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_NEAREST"; break;

                case RenderBase.OTextureMinFilter.nearestMipmapLinear: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_LINEAR"; break;

                case RenderBase.OTextureMinFilter.nearestMipmapNearest: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_NEAREST"; break;

                default: sampler.sampler2D.minfilter = "NONE"; break;
                }

                switch (mat.textureMapper[0].magFilter)
                {
                case RenderBase.OTextureMagFilter.linear: sampler.sampler2D.magfilter = "LINEAR"; break;

                case RenderBase.OTextureMagFilter.nearest: sampler.sampler2D.magfilter = "NEAREST"; break;

                default: sampler.sampler2D.magfilter = "NONE"; break;
                }

                sampler.sampler2D.mipfilter = sampler.sampler2D.magfilter;

                eff.profile_COMMON.newparam.Add(sampler);

                eff.profile_COMMON.technique.sid = "img_technique";
                eff.profile_COMMON.technique.phong.emission.set(Color.Black);
                eff.profile_COMMON.technique.phong.ambient.set(Color.Black);
                eff.profile_COMMON.technique.phong.specular.set(Color.White);
                eff.profile_COMMON.technique.phong.diffuse.texture.texture = "img_sampler";

                dae.library_effects.Add(eff);
            }

            string jointNames   = null;
            string invBindPoses = null;

            for (int index = 0; index < mdl.skeleton.Count; index++)
            {
                RenderBase.OMatrix transform = new RenderBase.OMatrix();
                transformSkeleton(mdl.skeleton, index, ref transform);

                jointNames += mdl.skeleton[index].name;
                daeMatrix mtx = new daeMatrix();
                mtx.set(transform.invert());
                invBindPoses += mtx.data;
                if (index < mdl.skeleton.Count - 1)
                {
                    jointNames   += " ";
                    invBindPoses += " ";
                }
            }

            int            meshIndex = 0;
            daeVisualScene vs        = new daeVisualScene();

            vs.name = "vs_" + mdl.name;
            vs.id   = vs.name + "_id";
            if (mdl.skeleton.Count > 0)
            {
                writeSkeleton(mdl.skeleton, 0, ref vs.node);
            }
            foreach (RenderBase.OMesh obj in mdl.mesh)
            {
                //Geometry
                daeGeometry geometry = new daeGeometry();

                string meshName = "mesh_" + meshIndex++ + "_" + obj.name;
                geometry.id   = meshName + "_id";
                geometry.name = meshName;

                MeshUtils.optimizedMesh mesh      = MeshUtils.optimizeMesh(obj);
                List <float>            positions = new List <float>();
                List <float>            normals   = new List <float>();
                List <float>            uv0       = new List <float>();
                List <float>            uv1       = new List <float>();
                List <float>            uv2       = new List <float>();
                List <float>            colors    = new List <float>();
                foreach (RenderBase.OVertex vtx in mesh.vertices)
                {
                    positions.Add(vtx.position.x);
                    positions.Add(vtx.position.y);
                    positions.Add(vtx.position.z);

                    if (mesh.hasNormal)
                    {
                        normals.Add(vtx.normal.x);
                        normals.Add(vtx.normal.y);
                        normals.Add(vtx.normal.z);
                    }

                    if (mesh.texUVCount > 0)
                    {
                        uv0.Add(vtx.texture0.x);
                        uv0.Add(vtx.texture0.y);
                    }

                    if (mesh.texUVCount > 1)
                    {
                        uv1.Add(vtx.texture1.x);
                        uv1.Add(vtx.texture1.y);
                    }

                    if (mesh.texUVCount > 2)
                    {
                        uv2.Add(vtx.texture2.x);
                        uv2.Add(vtx.texture2.y);
                    }

                    if (mesh.hasColor)
                    {
                        colors.Add(((vtx.diffuseColor >> 16) & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 8) & 0xff) / 255f);
                        colors.Add((vtx.diffuseColor & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 24) & 0xff) / 255f);
                    }
                }

                daeSource position = new daeSource();
                position.name           = meshName + "_position";
                position.id             = position.name + "_id";
                position.float_array    = new daeFloatArray();
                position.float_array.id = position.name + "_array_id";
                position.float_array.set(positions);
                position.technique_common.accessor.source = "#" + position.float_array.id;
                position.technique_common.accessor.count  = (uint)mesh.vertices.Count;
                position.technique_common.accessor.stride = 3;
                position.technique_common.accessor.addParam("X", "float");
                position.technique_common.accessor.addParam("Y", "float");
                position.technique_common.accessor.addParam("Z", "float");

                geometry.mesh.source.Add(position);

                daeSource normal = new daeSource();
                if (mesh.hasNormal)
                {
                    normal.name           = meshName + "_normal";
                    normal.id             = normal.name + "_id";
                    normal.float_array    = new daeFloatArray();
                    normal.float_array.id = normal.name + "_array_id";
                    normal.float_array.set(normals);
                    normal.technique_common.accessor.source = "#" + normal.float_array.id;
                    normal.technique_common.accessor.count  = (uint)mesh.vertices.Count;
                    normal.technique_common.accessor.stride = 3;
                    normal.technique_common.accessor.addParam("X", "float");
                    normal.technique_common.accessor.addParam("Y", "float");
                    normal.technique_common.accessor.addParam("Z", "float");

                    geometry.mesh.source.Add(normal);
                }

                daeSource[] texUV = new daeSource[3];
                for (int i = 0; i < mesh.texUVCount; i++)
                {
                    texUV[i] = new daeSource();

                    texUV[i].name           = meshName + "_uv" + i;
                    texUV[i].id             = texUV[i].name + "_id";
                    texUV[i].float_array    = new daeFloatArray();
                    texUV[i].float_array.id = texUV[i].name + "_array_id";
                    texUV[i].technique_common.accessor.source = "#" + texUV[i].float_array.id;
                    texUV[i].technique_common.accessor.count  = (uint)mesh.vertices.Count;
                    texUV[i].technique_common.accessor.stride = 2;
                    texUV[i].technique_common.accessor.addParam("S", "float");
                    texUV[i].technique_common.accessor.addParam("T", "float");

                    geometry.mesh.source.Add(texUV[i]);
                }

                daeSource color = new daeSource();
                if (mesh.hasColor)
                {
                    color.name           = meshName + "_color";
                    color.id             = color.name + "_id";
                    color.float_array    = new daeFloatArray();
                    color.float_array.id = color.name + "_array_id";
                    color.float_array.set(colors);
                    color.technique_common.accessor.source = "#" + color.float_array.id;
                    color.technique_common.accessor.count  = (uint)mesh.vertices.Count;
                    color.technique_common.accessor.stride = 4;
                    color.technique_common.accessor.addParam("R", "float");
                    color.technique_common.accessor.addParam("G", "float");
                    color.technique_common.accessor.addParam("B", "float");
                    color.technique_common.accessor.addParam("A", "float");

                    geometry.mesh.source.Add(color);
                }

                geometry.mesh.vertices.id = meshName + "_vertices_id";
                geometry.mesh.vertices.addInput("POSITION", "#" + position.id);


                geometry.mesh.triangles.material = mdl.material[obj.materialId].name;
                geometry.mesh.triangles.addInput("VERTEX", "#" + geometry.mesh.vertices.id);
                if (mesh.hasNormal)
                {
                    geometry.mesh.triangles.addInput("NORMAL", "#" + normal.id);
                }
                if (mesh.hasColor)
                {
                    geometry.mesh.triangles.addInput("COLOR", "#" + color.id);
                }
                if (mesh.texUVCount > 0)
                {
                    texUV[0].float_array.set(uv0);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[0].id);
                }
                if (mesh.texUVCount > 1)
                {
                    texUV[1].float_array.set(uv1);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[1].id, 0, 1);
                }
                if (mesh.texUVCount > 2)
                {
                    texUV[2].float_array.set(uv2);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[2].id, 0, 2);
                }
                geometry.mesh.triangles.set(mesh.indices);

                dae.library_geometries.Add(geometry);

                bool hasNode       = obj.vertices[0].node.Count > 0;
                bool hasWeight     = obj.vertices[0].weight.Count > 0;
                bool hasController = hasNode && hasWeight;

                //Controller
                daeController controller = new daeController();
                if (hasController)
                {
                    controller.id = meshName + "_ctrl_id";

                    controller.skin.source = "#" + geometry.id;
                    controller.skin.bind_shape_matrix.set(new RenderBase.OMatrix());

                    daeSource joints = new daeSource();
                    joints.id               = meshName + "_ctrl_joint_names_id";
                    joints.Name_array       = new daeNameArray();
                    joints.Name_array.id    = meshName + "_ctrl_joint_names_array_id";
                    joints.Name_array.count = (uint)mdl.skeleton.Count;
                    joints.Name_array.data  = jointNames;
                    joints.technique_common.accessor.source = "#" + joints.Name_array.id;
                    joints.technique_common.accessor.count  = joints.Name_array.count;
                    joints.technique_common.accessor.stride = 1;
                    joints.technique_common.accessor.addParam("JOINT", "Name");

                    controller.skin.src.Add(joints);

                    daeSource bindPoses = new daeSource();
                    bindPoses.id                = meshName + "_ctrl_inv_bind_poses_id";
                    bindPoses.float_array       = new daeFloatArray();
                    bindPoses.float_array.id    = meshName + "_ctrl_inv_bind_poses_array_id";
                    bindPoses.float_array.count = (uint)(mdl.skeleton.Count * 16);
                    bindPoses.float_array.data  = invBindPoses;
                    bindPoses.technique_common.accessor.source = "#" + bindPoses.float_array.id;
                    bindPoses.technique_common.accessor.count  = (uint)mdl.skeleton.Count;
                    bindPoses.technique_common.accessor.stride = 16;
                    bindPoses.technique_common.accessor.addParam("TRANSFORM", "float4x4");

                    controller.skin.src.Add(bindPoses);

                    daeSource weights = new daeSource();
                    weights.id             = meshName + "_ctrl_weights_id";
                    weights.float_array    = new daeFloatArray();
                    weights.float_array.id = meshName + "_ctrl_weights_array_id";
                    weights.technique_common.accessor.source = "#" + weights.float_array.id;
                    weights.technique_common.accessor.stride = 1;
                    weights.technique_common.accessor.addParam("WEIGHT", "float");

                    StringBuilder w      = new StringBuilder();
                    StringBuilder vcount = new StringBuilder();
                    StringBuilder v      = new StringBuilder();

                    float[] wLookBack      = new float[32];
                    uint    wLookBackIndex = 0;
                    int     buffLen        = 0;

                    int wIndex = 0;
                    int wCount = 0;
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        int count = Math.Min(vtx.node.Count, vtx.weight.Count);

                        vcount.Append(count + " ");
                        for (int n = 0; n < count; n++)
                        {
                            v.Append(vtx.node[n] + " ");
                            bool found = false;
                            uint bPos  = (wLookBackIndex - 1) & 0x1f;
                            for (int i = 0; i < buffLen; i++)
                            {
                                if (wLookBack[bPos] == vtx.weight[n])
                                {
                                    v.Append(wIndex - (i + 1) + " ");
                                    found = true;
                                    break;
                                }
                                bPos = (bPos - 1) & 0x1f;
                            }

                            if (!found)
                            {
                                v.Append(wIndex++ + " ");
                                w.Append(vtx.weight[n].ToString(CultureInfo.InvariantCulture) + " ");
                                wCount++;

                                wLookBack[wLookBackIndex] = vtx.weight[n];
                                wLookBackIndex            = (wLookBackIndex + 1) & 0x1f;
                                if (buffLen < wLookBack.Length)
                                {
                                    buffLen++;
                                }
                            }
                        }
                    }

                    weights.float_array.data  = w.ToString().TrimEnd();
                    weights.float_array.count = (uint)wCount;
                    weights.technique_common.accessor.count = (uint)wCount;

                    controller.skin.src.Add(weights);
                    controller.skin.vertex_weights.vcount = vcount.ToString().TrimEnd();
                    controller.skin.vertex_weights.v      = v.ToString().TrimEnd();
                    controller.skin.vertex_weights.count  = (uint)mesh.vertices.Count;
                    controller.skin.joints.addInput("JOINT", "#" + joints.id);
                    controller.skin.joints.addInput("INV_BIND_MATRIX", "#" + bindPoses.id);

                    controller.skin.vertex_weights.addInput("JOINT", "#" + joints.id);
                    controller.skin.vertex_weights.addInput("WEIGHT", "#" + weights.id, 1);

                    if (dae.library_controllers == null)
                    {
                        dae.library_controllers = new List <daeController>();
                    }
                    dae.library_controllers.Add(controller);
                }

                //Visual scene node
                daeNode node = new daeNode();
                node.name = "vsn_" + meshName;
                node.id   = node.name + "_id";
                node.matrix.set(new RenderBase.OMatrix());
                if (hasController)
                {
                    node.instance_controller          = new daeInstanceController();
                    node.instance_controller.url      = "#" + controller.id;
                    node.instance_controller.skeleton = "#" + mdl.skeleton[0].name + "_bone_id";
                    node.instance_controller.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name;
                    node.instance_controller.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }
                else
                {
                    node.instance_geometry     = new daeInstanceGeometry();
                    node.instance_geometry.url = "#" + geometry.id;
                    node.instance_geometry.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name;
                    node.instance_geometry.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }

                vs.node.Add(node);
            }
            dae.library_visual_scenes.Add(vs);

            daeInstaceVisualScene scene = new daeInstaceVisualScene();

            scene.url = "#" + vs.id;
            dae.scene.Add(scene);

            XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent   = true
            };

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

            ns.Add("", "http://www.collada.org/2005/11/COLLADASchema");
            XmlSerializer serializer = new XmlSerializer(typeof(COLLADA));
            XmlWriter     output     = XmlWriter.Create(new FileStream(fileName, FileMode.Create), settings);

            serializer.Serialize(output, dae, ns);
            output.Close();
        }
        /// <summary>
        ///     Exports a Model to the Collada format.
        ///     See: https://www.khronos.org/files/collada_spec_1_4.pdf for more information.
        /// </summary>
        /// <param name="model">The Model that will be exported</param>
        /// <param name="fileName">The output File Name</param>
        /// <param name="modelIndex">Index of the model to be exported</param>
        /// <param name="skeletalAnimationIndex">(Optional) Index of the skeletal animation</param>
        public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex, int skeletalAnimationIndex = -1)
        {
            RenderBase.OModel mdl = model.model[modelIndex];
            COLLADA dae = new COLLADA();
            COLLADA daeShiny = new COLLADA();

            dae.asset.created = DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ");
            dae.asset.modified = dae.asset.created;

            daeShiny.asset.created = DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ");
            daeShiny.asset.modified = dae.asset.created;

            bool hasShiny = false;

            // Attempt to detect packaged textures. See http://www.github.com/Quibilia/Ohana3DS-Transfigured for directions.
            try
            {
                string modelDir;
                string sDir;
                string[] texDirs;
                string[] texFiles;

                modelDir = Path.GetFileNameWithoutExtension(fileName).Remove(4);
                sDir = Path.GetDirectoryName(fileName);
                texDirs = Directory.GetDirectories(Path.Combine(sDir, "../../Textures/"));

                foreach (string texDir in texDirs)
                {
                    if (texDir.Contains(modelDir))
                    {
                        texFiles = Directory.GetFiles(texDir);

                        if (Directory.Exists(texDir.Replace("Textures", "Models/DAE")) == false && texDir.Contains("Xtra") == false)
                        {
                            Directory.CreateDirectory(texDir.Replace("Textures", "Models/DAE"));
                        }

                        bool hasIris = false;

                        foreach (string texFile in texFiles)
                        {
                            if (texFile.Contains("Iris1"))
                            {
                                hasIris = true;
                            }
                        }

                        foreach (string texFile in texFiles)
                        {
                            if (texFile.Contains("Xtra") == false)
                            {
                                Bitmap tempBMP = (Bitmap)Bitmap.FromFile(texFile);

                                if (texFile.Contains("Body") && texFile.Contains("Nor") == false)
                                {
                                    int w = tempBMP.Width / 2;

                                    Bitmap scaledBMP = new Bitmap(tempBMP, tempBMP.Width / 2, tempBMP.Height);
                                    Bitmap reverseBMP = new Bitmap(tempBMP, tempBMP.Width / 2, tempBMP.Height);
                                    reverseBMP.RotateFlip(RotateFlipType.RotateNoneFlipX);

                                    Bitmap finalBMP = new Bitmap(tempBMP.Width, tempBMP.Height);

                                    for (int x = 0; x < scaledBMP.Width; x++)
                                    {
                                        for (int y = 0; y < scaledBMP.Height; y++)
                                        {
                                            finalBMP.SetPixel(x, y, scaledBMP.GetPixel(x, y));
                                        }
                                    }

                                    for (int x = 0; x < reverseBMP.Width; x++)
                                    {
                                        for (int y = 0; y < reverseBMP.Height; y++)
                                        {
                                            finalBMP.SetPixel(x + reverseBMP.Width, y, reverseBMP.GetPixel(x, y));
                                        }
                                    }

                                    if (texFile.Contains("BodyA2") == false && texFile.Contains("BodyB2") == false && texFile.Contains("Body2") == false)
                                    {
                                        finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));

                                        model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                }
                                else if (texFile.Contains("Iris1"))
                                {
                                    Bitmap eyeBMP = (Bitmap)Bitmap.FromFile(texFile.Replace("Iris", "Eye"));

                                    foreach (string norFile in texFiles)
                                    {
                                        if (norFile.Contains("Xtra") == false && norFile.Contains("EyeNor"))
                                        {
                                            Bitmap thisEye = new Bitmap(eyeBMP.Width / 2, eyeBMP.Height / 4);
                                            Bitmap norBMP = (Bitmap)Bitmap.FromFile(norFile);
                                            Bitmap thisNor = new Bitmap(norBMP.Width / 4, norBMP.Height / 4);
                                            Bitmap finalBMP = new Bitmap(eyeBMP.Width * 2, eyeBMP.Height);

                                            int eye = 0;
                                            int half = 0;
                                            bool reflectEye = false;
                                            bool reflectIris = true;
                                            bool reflectNor = true;

                                            for (int eyeY = 0; eyeY < 4; eyeY++)
                                            {
                                                eye = 0;

                                                for (int eyeX = 0; eyeX < 2; eyeX++)
                                                {
                                                    reflectEye = !reflectEye;
                                                    reflectNor = !reflectNor;
                                                    reflectIris = !reflectIris;

                                                    for (half = 0; half < 2; half++)
                                                    {
                                                        for (int x = half * (eyeBMP.Width / 2); x < (half + 1) * (eyeBMP.Width / 2); x++)
                                                        {
                                                            for (int y = eyeY * (eyeBMP.Height / 4); y < (eyeY + 1) * (eyeBMP.Height / 4); y++)
                                                            {
                                                                thisEye.SetPixel(x - (half * (eyeBMP.Width / 2)), y - (eyeY * (eyeBMP.Height / 4)), eyeBMP.GetPixel(x, y));
                                                            }
                                                        }

                                                        for (int x = (half * 2) * (norBMP.Width / 4); x < ((half * 2) + 1) * (norBMP.Width / 4); x++)
                                                        {
                                                            for (int y = eyeY * (norBMP.Height / 4); y < (eyeY + 1) * (norBMP.Height / 4); y++)
                                                            {
                                                                thisNor.SetPixel(x - ((half * 2) * (norBMP.Width / 4)), y - (eyeY * (norBMP.Height / 4)), norBMP.GetPixel(x, y));
                                                            }
                                                        }

                                                        if (reflectEye == true)
                                                        {
                                                            thisEye.RotateFlip(RotateFlipType.RotateNoneFlipX);
                                                        }

                                                        reflectEye = !reflectEye;

                                                        if (reflectNor == true)
                                                        {
                                                            thisNor.RotateFlip(RotateFlipType.RotateNoneFlipX);
                                                        }

                                                        if (reflectIris == true)
                                                        {
                                                            tempBMP.RotateFlip(RotateFlipType.RotateNoneFlipX);
                                                            reflectIris = false;
                                                        }

                                                        // By now, tempBMP, thisEye, and thisNor should all be the same size.

                                                        for (int x = 0; x < tempBMP.Width; x++)
                                                        {
                                                            for (int y = 0; y < tempBMP.Height; y++)
                                                            {
                                                                if (tempBMP.GetPixel(x, y).A >= 0x7F && thisNor.GetPixel(x, y).A >= 0x7F)
                                                                {
                                                                    thisEye.SetPixel(x, y, tempBMP.GetPixel(x, y));
                                                                }
                                                            }
                                                        }

                                                        for (int x = eye * (norBMP.Width / 4); x < (eye + 1) * (norBMP.Width / 4); x++)
                                                        {
                                                            for (int y = eyeY * (norBMP.Height / 4); y < (eyeY + 1) * (norBMP.Height / 4); y++)
                                                            {
                                                                finalBMP.SetPixel(x, y, thisEye.GetPixel(x - (eye * (norBMP.Width / 4)), y - (eyeY * (norBMP.Height / 4))));
                                                            }
                                                        }

                                                        if (eye == 3)
                                                        {
                                                            eye--;
                                                        }
                                                        else if (eye == 1)
                                                        {
                                                            eye += 2;
                                                        }
                                                        else if (eye == 0)
                                                        {
                                                            eye++;
                                                        }
                                                        else if (eye == 2)
                                                        {
                                                            eye -= 2;
                                                        }
                                                    }
                                                }
                                            }

                                            finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                            model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                        }
                                    }
                                }
                                else if (texFile.Contains("Mouth1") && texFile.Contains("Nor") == false)
                                {
                                    bool hasNormals = false;

                                    foreach (string norFile in texFiles)
                                    {
                                        // If the mouth has a normal file, the UV coordinates expect it split across the image border.

                                        if (norFile.Contains("Mouth") && norFile.Contains("Nor"))
                                        {
                                            hasNormals = true;
                                        }
                                    }

                                    if (hasNormals == true)
                                    {
                                        Bitmap thisHalf = new Bitmap(tempBMP.Width / 2, tempBMP.Height / 4);
                                        Bitmap finalBMP = new Bitmap(tempBMP.Width * 2, tempBMP.Height);

                                        for (int halfY = 0; halfY < 4; halfY++)
                                        {
                                            for (int halfX = 0; halfX < 2; halfX++)
                                            {
                                                for (int x = halfX * (tempBMP.Width / 2); x < (halfX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = halfY * (tempBMP.Height / 4); y < (halfY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        thisHalf.SetPixel(x - (halfX * (tempBMP.Width / 2)), y - (halfY * (tempBMP.Height / 4)), tempBMP.GetPixel(x, y));
                                                    }
                                                }

                                                for (int x = halfX * (tempBMP.Width / 2); x < (halfX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = halfY * (tempBMP.Height / 4); y < (halfY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        finalBMP.SetPixel(x, y, thisHalf.GetPixel(x - (halfX * (tempBMP.Width / 2)), y - (halfY * (tempBMP.Height / 4))));
                                                    }
                                                }

                                                for (int x = halfX * (tempBMP.Width / 2); x < (halfX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = halfY * (tempBMP.Height / 4); y < (halfY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        finalBMP.SetPixel(finalBMP.Width - (x + 1), y, thisHalf.GetPixel(x - (halfX * (tempBMP.Width / 2)), y - (halfY * (tempBMP.Height / 4))));
                                                    }
                                                }
                                            }
                                        }

                                        finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                        model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                    else
                                    {
                                        // If the mouth doesn't have a normal file, the UV coordinates expect it as-is. Hooray!

                                        tempBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                        model.texture.Add(new RenderBase.OTexture(tempBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                }
                                else if (texFile.Contains("Eye1") && hasIris == false && texFile.Contains("AEye") == false && texFile.Contains("BEye") == false & texFile.Contains("CEye") == false)
                                {
                                    if (tempBMP.Width == tempBMP.Height)
                                    {
                                        tempBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                        model.texture.Add(new RenderBase.OTexture(tempBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                    else
                                    {
                                        Bitmap thisEye = new Bitmap(tempBMP.Width / 2, tempBMP.Height / 4);
                                        Bitmap finalBMP = new Bitmap(tempBMP.Width * 2, tempBMP.Height);

                                        for (int eyeY = 0; eyeY < 4; eyeY++)
                                        {
                                            for (int eyeX = 0; eyeX < 2; eyeX++)
                                            {
                                                for (int x = eyeX * (tempBMP.Width / 2); x < (eyeX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = eyeY * (tempBMP.Height / 4); y < (eyeY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        thisEye.SetPixel(x - (eyeX * (tempBMP.Width / 2)), y - (eyeY * (tempBMP.Height / 4)), tempBMP.GetPixel(x, y));
                                                    }
                                                }

                                                for (int x = eyeX * (tempBMP.Width / 2); x < (eyeX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = eyeY * (tempBMP.Height / 4); y < (eyeY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        finalBMP.SetPixel(x, y, thisEye.GetPixel(x - (eyeX * (tempBMP.Width / 2)), y - (eyeY * (tempBMP.Height / 4))));
                                                    }
                                                }

                                                for (int x = eyeX * (tempBMP.Width / 2); x < (eyeX + 1) * (tempBMP.Width / 2); x++)
                                                {
                                                    for (int y = eyeY * (tempBMP.Height / 4); y < (eyeY + 1) * (tempBMP.Height / 4); y++)
                                                    {
                                                        finalBMP.SetPixel(finalBMP.Width - (x + 1), y, thisEye.GetPixel(x - (eyeX * (tempBMP.Width / 2)), y - (eyeY * (tempBMP.Height / 4))));
                                                    }
                                                }
                                            }
                                        }

                                        finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                        model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                }
                                else if (texFile.Contains("AEye") || texFile.Contains("BEye") || texFile.Contains("CEye"))
                                {
                                    Bitmap finalBMP = new Bitmap(tempBMP, tempBMP.Width / 2, tempBMP.Height);

                                    finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                    model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                }
                                else if (texFile.Contains("Fire"))
                                {
                                    Bitmap finalBMP = new Bitmap(tempBMP);

                                    foreach (string bodyFile in texFiles)
                                    {
                                        if ((bodyFile.Contains("Body1") || bodyFile.Contains("BodyA1")) && bodyFile.Contains("Nor") == false)
                                        {
                                            Bitmap bodyBMP = (Bitmap)Bitmap.FromFile(bodyFile);

                                            byte r, g, b;

                                            // Most-Frequent Body Color
                                            Color MFBC = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00), LFBC = Color.FromArgb(0xFF, 0xFF, 0xFF, 0x00);
                                            int LF = 0, SF = 65536;

                                            List<Color> colors = new List<Color>();
                                            List<int> freqs = new List<int>();

                                            for (int x = 0; x < bodyBMP.Width; x++)
                                            {
                                                for (int y = 0; y < bodyBMP.Height; y++)
                                                {
                                                    r = bodyBMP.GetPixel(x, y).R;
                                                    g = bodyBMP.GetPixel(x, y).G;
                                                    b = bodyBMP.GetPixel(x, y).B;

                                                    if (bodyBMP.GetPixel(x, y).A != 0x00)
                                                    {
                                                        bool matchFound = false;

                                                        foreach (Color c in colors)
                                                        {
                                                            if (c.R == r && c.G == g && c.B == b)
                                                            {
                                                                matchFound = true;
                                                                int freq = freqs[colors.IndexOf(c)];
                                                                freq++;

                                                                freqs[colors.IndexOf(c)] = freq;
                                                            }
                                                        }

                                                        if (matchFound == false)
                                                        {
                                                            colors.Add(Color.FromArgb(r, g, b));
                                                            freqs.Add(1);
                                                        }
                                                    }
                                                }
                                            }

                                            int tolerance = 18;

                                            foreach (Color c in colors)
                                            {
                                                if (freqs[colors.IndexOf(c)] > LF)
                                                {
                                                    LF = freqs[colors.IndexOf(c)];
                                                    MFBC = c;
                                                }
                                                else if (freqs[colors.IndexOf(c)] + 150 < SF)
                                                {
                                                    if (c.R <= (c.G - tolerance) || c.R >= (c.G + tolerance))
                                                    {
                                                        if (c.B <= (c.G - tolerance) || c.B >= (c.G + tolerance))
                                                        {
                                                            SF = freqs[colors.IndexOf(c)];
                                                            LFBC = c;
                                                        }
                                                    }
                                                }
                                            }

                                            bool mfbcgray = false;
                                            bool lfbcgray = false;
                                            bool bothgray = false;
                                            bool neithergray = false;

                                            if (MFBC.R > (MFBC.G - tolerance) && MFBC.R < (MFBC.G + tolerance))
                                            {
                                                if (MFBC.B > (MFBC.G - tolerance) && MFBC.B < (MFBC.G + tolerance))
                                                {
                                                    mfbcgray = true;
                                                }
                                            }

                                            if (LFBC.R > (LFBC.G - tolerance) && LFBC.R < (LFBC.G + tolerance))
                                            {
                                                if (LFBC.B > (LFBC.G - tolerance) && LFBC.B < (LFBC.G + tolerance))
                                                {
                                                    lfbcgray = true;
                                                }
                                            }

                                            if (mfbcgray && lfbcgray)
                                            {
                                                bothgray = true;
                                            }

                                            if (!mfbcgray && !lfbcgray)
                                            {
                                                neithergray = true;
                                            }

                                            Color baseOperator;
                                            Color accentOperator;

                                            if (bothgray)
                                            {
                                                baseOperator = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                                                accentOperator = Color.FromArgb(0xFF, 0xFF, 0xFF, 0x00);
                                            }
                                            else if (neithergray)
                                            {
                                                baseOperator = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                                                accentOperator = Color.FromArgb(0xFF, 0xFF, 0xFF, 0x00);
                                            }
                                            else
                                            {
                                                if (mfbcgray)
                                                {
                                                    baseOperator = LFBC;
                                                    accentOperator = LFBC;
                                                }
                                                else if (lfbcgray)
                                                {
                                                    baseOperator = MFBC;
                                                    accentOperator = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
                                                }
                                                else
                                                {
                                                    // Just to satisfy C#...
                                                    baseOperator = MFBC;
                                                    accentOperator = LFBC;
                                                }
                                            }

                                            for (int x = 0; x < finalBMP.Width; x++)
                                            {
                                                for (int y = 0; y < finalBMP.Height; y++)
                                                {
                                                    Color tempColor = tempBMP.GetPixel(x, y);
                                                    Color finalColor;

                                                    if (tempColor.R < 0x10 && tempColor.G < 0x10 && tempColor.B < 0x10)
                                                    {
                                                        finalColor = Color.FromArgb(0x1F, baseOperator.R, baseOperator.G, baseOperator.B);
                                                    }
                                                    else
                                                    {
                                                        finalColor = Color.FromArgb(0x1F, tempColor.R & accentOperator.R, tempColor.G & accentOperator.G, tempColor.B & accentOperator.B);
                                                    }

                                                    finalBMP.SetPixel(x, y, finalColor);
                                                }
                                            }

                                            finalBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                            model.texture.Add(new RenderBase.OTexture(finalBMP, texFile.Replace("Textures", "Models/DAE")));
                                        }
                                    }
                                }
                                else
                                {
                                    if (texFile.Contains("Iris2") == false && texFile.Contains("Eye2") == false && texFile.Contains("Mouth2") == false)
                                    {
                                        tempBMP.Save(texFile.Replace("Textures", "Models/DAE"));
                                        model.texture.Add(new RenderBase.OTexture(tempBMP, texFile.Replace("Textures", "Models/DAE")));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch // No packaged textures detected...
            {
            }

            foreach (RenderBase.OTexture tex in model.texture)
            {
                daeImage img = new daeImage();

                string n = Path.GetFileNameWithoutExtension(tex.name);

                img.id = n;
                img.name = n;

                if (tex.name.Contains("Shiny"))
                {
                    img.id += "_shiny";
                    img.name += "_shiny";

                    hasShiny = true;
                }

                img.id += "_id";

                img.init_from = tex.name;

                if (img.id.Contains("_shiny"))
                {
                    daeShiny.library_images.Add(img);
                }
                else
                {
                    dae.library_images.Add(img);
                }
            }

            #region Normal

            int currentMat = 0;

            foreach (RenderBase.OMaterial mat in mdl.material)
            {
                if (mat.name0 == null)
                {
                    mat.name0 = mat.name;
                }

                mdl.material[currentMat].name = mat.name;
                mdl.material[currentMat].name0 = mat.name0;

                currentMat++;

                daeMaterial mtl = new daeMaterial();
                mtl.name = mat.name + "_mat";
                mtl.id = mtl.name + "_id";
                mtl.instance_effect.url = "#eff_" + mtl.id;

                dae.library_materials.Add(mtl);

                daeEffect eff = new daeEffect();
                eff.id = "eff_" + mtl.id;
                eff.name = "eff_" + mtl.name;

                daeParam surface = new daeParam();
                surface.surface = new daeParamSurfaceElement();
                surface.sid = "img_surface";
                surface.surface.type = "2D";
                surface.surface.init_from = mat.name0 + "_id";
                surface.surface.format = "PNG";
                eff.profile_COMMON.newparam.Add(surface);

                bool fire = false;

                for (int i = 0; i < dae.library_images.Count; i++)
                {
                    if (dae.library_images[i].init_from.Contains(mat.name) || dae.library_images[i].id.Contains(mat.name) || dae.library_images[i].init_from.Contains(mat.name0) || dae.library_images[i].id.Contains(mat.name0))
                    {
                        if (dae.library_images[i].init_from.Contains("Nor") == false)
                        {
                            if (mat.name.Contains("Fire"))
                            {
                                fire = true;

                                dae.library_images[i].name = dae.library_images[i].name.Replace("Mask", "1");
                                dae.library_images[i].id = dae.library_images[i].id.Replace("Mask", "1");
                                dae.library_images[i].init_from = dae.library_images[i].init_from.Replace("Mask", "1");

                                mat.name = mat.name.Replace("Mask", "1");
                                mat.name0 = mat.name0.Replace("Mask", "1");
                            }

                            surface.surface.init_from = dae.library_images[i].id;
                        }
                    }
                }

                daeParam sampler = new daeParam();
                sampler.sampler2D = new daeParamSampler2DElement();
                sampler.sid = "img_sampler";
                sampler.sampler2D.source = "img_surface";

                switch (mat.textureMapper[0].wrapU)
                {
                    case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_s = "WRAP"; break;
                    case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_s = "MIRROR"; break;
                    case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_s = "CLAMP"; break;
                    case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_s = "BORDER"; break;
                    default: sampler.sampler2D.wrap_s = "WRAP"; break;
                }

                switch (mat.textureMapper[0].wrapV)
                {
                    case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_t = "WRAP"; break;
                    case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_t = "MIRROR"; break;
                    case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_t = "CLAMP"; break;
                    case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_t = "BORDER"; break;
                    default: sampler.sampler2D.wrap_t = "WRAP"; break;
                }

                switch (mat.textureMapper[0].minFilter)
                {
                    case RenderBase.OTextureMinFilter.linearMipmapLinear: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_LINEAR"; break;
                    case RenderBase.OTextureMinFilter.linearMipmapNearest: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_NEAREST"; break;
                    case RenderBase.OTextureMinFilter.nearestMipmapLinear: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_LINEAR"; break;
                    case RenderBase.OTextureMinFilter.nearestMipmapNearest: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_NEAREST"; break;
                    default: sampler.sampler2D.minfilter = "NONE"; break;
                }

                switch (mat.textureMapper[0].magFilter)
                {
                    case RenderBase.OTextureMagFilter.linear: sampler.sampler2D.magfilter = "LINEAR"; break;
                    case RenderBase.OTextureMagFilter.nearest: sampler.sampler2D.magfilter = "NEAREST"; break;
                    default: sampler.sampler2D.magfilter = "NONE"; break;
                }

                sampler.sampler2D.mipfilter = sampler.sampler2D.magfilter;

                eff.profile_COMMON.newparam.Add(sampler);

                eff.profile_COMMON.technique.sid = "img_technique";

                eff.profile_COMMON.technique.phong.emission.set(Color.Black);
                eff.profile_COMMON.technique.phong.ambient.set(Color.Black);
                eff.profile_COMMON.technique.phong.specular.set(Color.White);

                eff.profile_COMMON.technique.phong.diffuse.texture.texture = "img_sampler";

                eff.profile_COMMON.technique.phong.transparency = new daePhongTransparent();

                if (fire)
                {
                    eff.profile_COMMON.technique.phong.transparency.value = "0.1";
                }
                else
                {
                    eff.profile_COMMON.technique.phong.transparency.value = "1.0";
                }

                dae.library_effects.Add(eff);
            }

            string jointNames = null;
            string invBindPoses = null;
            for (int index = 0; index < mdl.skeleton.Count; index++)
            {
                RenderBase.OMatrix transform = new RenderBase.OMatrix();
                transformSkeleton(mdl.skeleton, index, ref transform);

                jointNames += mdl.skeleton[index].name;
                daeMatrix mtx = new daeMatrix();
                mtx.set(transform.invert());
                invBindPoses += mtx.data;
                if (index < mdl.skeleton.Count - 1)
                {
                    jointNames += " ";
                    invBindPoses += " ";
                }
            }

            int meshIndex = 0;
            daeVisualScene vs = new daeVisualScene();
            vs.name = "vs_" + mdl.name;
            vs.id = vs.name + "_id";
            if (mdl.skeleton.Count > 0) writeSkeleton(mdl.skeleton, 0, ref vs.node);

            bool rightIris = false;

            foreach (RenderBase.OMesh obj in mdl.mesh)
            {
                float largestUV = 0.0f;

                //Geometry
                daeGeometry geometry = new daeGeometry();

                string meshName = "mesh_" + meshIndex++ + "_" + obj.name;
                geometry.id = meshName + "_id";
                geometry.name = meshName;

                MeshUtils.optimizedMesh mesh = MeshUtils.optimizeMesh(obj);
                List<float> positions = new List<float>();
                List<float> normals = new List<float>();
                List<float> uv0 = new List<float>();
                List<float> uv1 = new List<float>();
                List<float> uv2 = new List<float>();
                List<float> colors = new List<float>();

                if (obj.name.Contains("Iris"))
                {
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        if (mesh.texUVCount > 0)
                        {
                            if (vtx.texture0.x > largestUV)
                            {
                                largestUV = vtx.texture0.x;
                            }
                        }
                    }

                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        if (mesh.texUVCount > 0)
                        {
                            vtx.texture0.x = largestUV - vtx.texture0.x;

                            if (rightIris)
                            {
                                vtx.texture0.x = 1.25f - vtx.texture0.x;
                            }
                        }

                        if (mesh.texUVCount > 1)
                        {
                            vtx.texture1.x = largestUV - vtx.texture1.x;

                            if (rightIris)
                            {
                                vtx.texture1.x = 1.25f - vtx.texture1.x;
                            }
                        }

                        if (mesh.texUVCount > 2)
                        {
                            vtx.texture2.x = largestUV - vtx.texture2.x;

                            if (rightIris)
                            {
                                vtx.texture2.x = 1.25f - vtx.texture2.x;
                            }
                        }
                    }

                    rightIris = !rightIris;
                }
                else if (obj.name.Contains("Eye"))
                {
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        if (mesh.texUVCount > 0)
                        {
                            if (vtx.texture0.x > largestUV)
                            {
                                largestUV = vtx.texture0.x;
                            }
                        }
                    }
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        if (mesh.texUVCount > 0)
                        {
                            vtx.texture0.x = largestUV - vtx.texture0.x;
                        }

                        if (mesh.texUVCount > 1)
                        {
                            vtx.texture1.x = largestUV - vtx.texture1.x;
                        }

                        if (mesh.texUVCount > 2)
                        {
                            vtx.texture2.x = largestUV - vtx.texture2.x;
                        }
                    }
                }

                foreach (RenderBase.OVertex vtx in mesh.vertices)
                {
                    positions.Add(vtx.position.x);
                    positions.Add(vtx.position.y);
                    positions.Add(vtx.position.z);

                    if (mesh.hasNormal)
                    {
                        normals.Add(vtx.normal.x);
                        normals.Add(vtx.normal.y);
                        normals.Add(vtx.normal.z);
                    }

                    if (mesh.texUVCount > 0)
                    {
                        uv0.Add(vtx.texture0.x);
                        uv0.Add(vtx.texture0.y);
                    }

                    if (mesh.texUVCount > 1)
                    {
                        uv1.Add(vtx.texture1.x);
                        uv1.Add(vtx.texture1.y);
                    }

                    if (mesh.texUVCount > 2)
                    {
                        uv2.Add(vtx.texture2.x);
                        uv2.Add(vtx.texture2.y);
                    }

                    if (mesh.hasColor)
                    {
                        colors.Add(((vtx.diffuseColor >> 16) & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 8) & 0xff) / 255f);
                        colors.Add((vtx.diffuseColor & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 24) & 0xff) / 255f);
                    }
                }

                daeSource position = new daeSource();
                position.name = meshName + "_position";
                position.id = position.name + "_id";
                position.float_array = new daeFloatArray();
                position.float_array.id = position.name + "_array_id";
                position.float_array.set(positions);
                position.technique_common.accessor.source = "#" + position.float_array.id;
                position.technique_common.accessor.count = (uint)mesh.vertices.Count;
                position.technique_common.accessor.stride = 3;
                position.technique_common.accessor.addParam("X", "float");
                position.technique_common.accessor.addParam("Y", "float");
                position.technique_common.accessor.addParam("Z", "float");

                geometry.mesh.source.Add(position);

                daeSource normal = new daeSource();
                if (mesh.hasNormal)
                {
                    normal.name = meshName + "_normal";
                    normal.id = normal.name + "_id";
                    normal.float_array = new daeFloatArray();
                    normal.float_array.id = normal.name + "_array_id";
                    normal.float_array.set(normals);
                    normal.technique_common.accessor.source = "#" + normal.float_array.id;
                    normal.technique_common.accessor.count = (uint)mesh.vertices.Count;
                    normal.technique_common.accessor.stride = 3;
                    normal.technique_common.accessor.addParam("X", "float");
                    normal.technique_common.accessor.addParam("Y", "float");
                    normal.technique_common.accessor.addParam("Z", "float");

                    geometry.mesh.source.Add(normal);
                }

                daeSource[] texUV = new daeSource[3];
                for (int i = 0; i < mesh.texUVCount; i++)
                {
                    texUV[i] = new daeSource();

                    texUV[i].name = meshName + "_uv" + i;
                    texUV[i].id = texUV[i].name + "_id";
                    texUV[i].float_array = new daeFloatArray();
                    texUV[i].float_array.id = texUV[i].name + "_array_id";
                    texUV[i].technique_common.accessor.source = "#" + texUV[i].float_array.id;
                    texUV[i].technique_common.accessor.count = (uint)mesh.vertices.Count;
                    texUV[i].technique_common.accessor.stride = 2;
                    texUV[i].technique_common.accessor.addParam("S", "float");
                    texUV[i].technique_common.accessor.addParam("T", "float");

                    geometry.mesh.source.Add(texUV[i]);
                }

                daeSource color = new daeSource();
                if (mesh.hasColor)
                {
                    color.name = meshName + "_color";
                    color.id = color.name + "_id";
                    color.float_array = new daeFloatArray();
                    color.float_array.id = color.name + "_array_id";
                    color.float_array.set(colors);
                    color.technique_common.accessor.source = "#" + color.float_array.id;
                    color.technique_common.accessor.count = (uint)mesh.vertices.Count;
                    color.technique_common.accessor.stride = 4;
                    color.technique_common.accessor.addParam("R", "float");
                    color.technique_common.accessor.addParam("G", "float");
                    color.technique_common.accessor.addParam("B", "float");
                    color.technique_common.accessor.addParam("A", "float");

                    geometry.mesh.source.Add(color);
                }

                geometry.mesh.vertices.id = meshName + "_vertices_id";
                geometry.mesh.vertices.addInput("POSITION", "#" + position.id);

                geometry.mesh.triangles.material = mdl.material[obj.materialId].name + "_mat";
                geometry.mesh.triangles.addInput("VERTEX", "#" + geometry.mesh.vertices.id);
                if (mesh.hasNormal) geometry.mesh.triangles.addInput("NORMAL", "#" + normal.id);
                if (mesh.hasColor) geometry.mesh.triangles.addInput("COLOR", "#" + color.id);
                if (mesh.texUVCount > 0)
                {
                    texUV[0].float_array.set(uv0);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[0].id);
                }
                if (mesh.texUVCount > 1)
                {
                    texUV[1].float_array.set(uv1);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[1].id, 0, 1);
                }
                if (mesh.texUVCount > 2)
                {
                    texUV[2].float_array.set(uv2);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[2].id, 0, 2);
                }
                geometry.mesh.triangles.set(mesh.indices);

                dae.library_geometries.Add(geometry);

                bool hasNode = obj.vertices[0].node.Count > 0;
                bool hasWeight = obj.vertices[0].weight.Count > 0;
                bool hasController = hasNode && hasWeight;

                //Controller
                daeController controller = new daeController();
                if (hasController)
                {
                    controller.id = meshName + "_ctrl_id";

                    controller.skin.source = "#" + geometry.id;
                    controller.skin.bind_shape_matrix.set(new RenderBase.OMatrix());

                    daeSource joints = new daeSource();
                    joints.id = meshName + "_ctrl_joint_names_id";
                    joints.Name_array = new daeNameArray();
                    joints.Name_array.id = meshName + "_ctrl_joint_names_array_id";
                    joints.Name_array.count = (uint)mdl.skeleton.Count;
                    joints.Name_array.data = jointNames;
                    joints.technique_common.accessor.source = "#" + joints.Name_array.id;
                    joints.technique_common.accessor.count = joints.Name_array.count;
                    joints.technique_common.accessor.stride = 1;
                    joints.technique_common.accessor.addParam("JOINT", "Name");

                    controller.skin.src.Add(joints);

                    daeSource bindPoses = new daeSource();
                    bindPoses.id = meshName + "_ctrl_inv_bind_poses_id";
                    bindPoses.float_array = new daeFloatArray();
                    bindPoses.float_array.id = meshName + "_ctrl_inv_bind_poses_array_id";
                    bindPoses.float_array.count = (uint)(mdl.skeleton.Count * 16);
                    bindPoses.float_array.data = invBindPoses;
                    bindPoses.technique_common.accessor.source = "#" + bindPoses.float_array.id;
                    bindPoses.technique_common.accessor.count = (uint)mdl.skeleton.Count;
                    bindPoses.technique_common.accessor.stride = 16;
                    bindPoses.technique_common.accessor.addParam("TRANSFORM", "float4x4");

                    controller.skin.src.Add(bindPoses);

                    daeSource weights = new daeSource();
                    weights.id = meshName + "_ctrl_weights_id";
                    weights.float_array = new daeFloatArray();
                    weights.float_array.id = meshName + "_ctrl_weights_array_id";
                    weights.technique_common.accessor.source = "#" + weights.float_array.id;
                    weights.technique_common.accessor.stride = 1;
                    weights.technique_common.accessor.addParam("WEIGHT", "float");

                    StringBuilder w = new StringBuilder();
                    StringBuilder vcount = new StringBuilder();
                    StringBuilder v = new StringBuilder();

                    float[] wLookBack = new float[32];
                    uint wLookBackIndex = 0;
                    int buffLen = 0;

                    int wIndex = 0;
                    int wCount = 0;
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        int count = Math.Min(vtx.node.Count, vtx.weight.Count);

                        vcount.Append(count + " ");
                        for (int n = 0; n < count; n++)
                        {
                            v.Append(vtx.node[n] + " ");
                            bool found = false;
                            uint bPos = (wLookBackIndex - 1) & 0x1f;
                            for (int i = 0; i < buffLen; i++)
                            {
                                if (wLookBack[bPos] == vtx.weight[n])
                                {
                                    v.Append(wIndex - (i + 1) + " ");
                                    found = true;
                                    break;
                                }
                                bPos = (bPos - 1) & 0x1f;
                            }

                            if (!found)
                            {
                                v.Append(wIndex++ + " ");
                                w.Append(vtx.weight[n].ToString(CultureInfo.InvariantCulture) + " ");
                                wCount++;

                                wLookBack[wLookBackIndex] = vtx.weight[n];
                                wLookBackIndex = (wLookBackIndex + 1) & 0x1f;
                                if (buffLen < wLookBack.Length) buffLen++;
                            }
                        }
                    }

                    weights.float_array.data = w.ToString().TrimEnd();
                    weights.float_array.count = (uint)wCount;
                    weights.technique_common.accessor.count = (uint)wCount;

                    controller.skin.src.Add(weights);
                    controller.skin.vertex_weights.vcount = vcount.ToString().TrimEnd();
                    controller.skin.vertex_weights.v = v.ToString().TrimEnd();
                    controller.skin.vertex_weights.count = (uint)mesh.vertices.Count;
                    controller.skin.joints.addInput("JOINT", "#" + joints.id);
                    controller.skin.joints.addInput("INV_BIND_MATRIX", "#" + bindPoses.id);

                    controller.skin.vertex_weights.addInput("JOINT", "#" + joints.id);
                    controller.skin.vertex_weights.addInput("WEIGHT", "#" + weights.id, 1);

                    if (dae.library_controllers == null) dae.library_controllers = new List<daeController>();
                    dae.library_controllers.Add(controller);
                }

                //Visual scene node
                daeNode node = new daeNode();
                node.name = "vsn_" + meshName;
                node.id = node.name + "_id";
                node.matrix.set(new RenderBase.OMatrix());
                if (hasController)
                {
                    node.instance_controller = new daeInstanceController();
                    node.instance_controller.url = "#" + controller.id;
                    node.instance_controller.skeleton = "#" + mdl.skeleton[0].name + "_bone_id";
                    node.instance_controller.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name + "_mat";
                    node.instance_controller.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }
                else
                {
                    node.instance_geometry = new daeInstanceGeometry();
                    node.instance_geometry.url = "#" + geometry.id;
                    node.instance_geometry.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name + "_mat";
                    node.instance_geometry.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }

                vs.node.Add(node);
            }
            dae.library_visual_scenes.Add(vs);

            daeInstaceVisualScene scene = new daeInstaceVisualScene();
            scene.url = "#" + vs.id;
            dae.scene.Add(scene);

            XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent = true
            };

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "http://www.collada.org/2005/11/COLLADASchema");
            XmlSerializer serializer = new XmlSerializer(typeof(COLLADA));
            XmlWriter output = XmlWriter.Create(new FileStream(fileName.Replace(".bch", ".dae").Replace("BCH", "DAE"), FileMode.Create), settings);
            serializer.Serialize(output, dae, ns);
            output.Close();

            #endregion

            #region Shiny

            if (hasShiny)
            {
                currentMat = 0;

                foreach (RenderBase.OMaterial mat in mdl.material)
                {
                    if (mat.name0 == null)
                    {
                        mat.name0 = mat.name;
                    }

                    mat.name += "_shiny";
                    mat.name0 += "_shiny";

                    mdl.material[currentMat].name = mat.name;
                    mdl.material[currentMat].name0 = mat.name0;

                    currentMat++;

                    daeMaterial mtl = new daeMaterial();
                    mtl.name = mat.name + "_mat";
                    mtl.id = mtl.name + "_id";
                    mtl.instance_effect.url = "#eff_" + mtl.id;

                    daeShiny.library_materials.Add(mtl);

                    daeEffect eff = new daeEffect();
                    eff.id = "eff_" + mtl.id;
                    eff.name = "eff_" + mtl.name;

                    daeParam surface = new daeParam();
                    surface.surface = new daeParamSurfaceElement();
                    surface.sid = "img_surface";
                    surface.surface.type = "2D";
                    surface.surface.init_from = mat.name0 + "_id";
                    surface.surface.format = "PNG";
                    eff.profile_COMMON.newparam.Add(surface);

                    bool fire = false;

                    for (int i = 0; i < daeShiny.library_images.Count; i++)
                    {
                        if (daeShiny.library_images[i].init_from.Contains(mat.name) || daeShiny.library_images[i].id.Contains(mat.name) || daeShiny.library_images[i].init_from.Contains(mat.name0) || daeShiny.library_images[i].id.Contains(mat.name0))
                        {
                            if (daeShiny.library_images[i].init_from.Contains("Nor") == false)
                            {
                                if (mat.name.Contains("Fire"))
                                {
                                    fire = true;

                                    daeShiny.library_images[i].name = daeShiny.library_images[i].name.Replace("Mask", "1");
                                    daeShiny.library_images[i].id = daeShiny.library_images[i].id.Replace("Mask", "1");
                                    daeShiny.library_images[i].init_from = daeShiny.library_images[i].init_from.Replace("Mask", "1");

                                    mat.name = mat.name.Replace("Mask", "1");
                                    mat.name0 = mat.name0.Replace("Mask", "1");
                                }

                                surface.surface.init_from = daeShiny.library_images[i].id;
                            }
                        }
                    }

                    if (mat.name.Contains("Fire"))
                    {
                        fire = true;
                    }

                    daeParam sampler = new daeParam();
                    sampler.sampler2D = new daeParamSampler2DElement();
                    sampler.sid = "img_sampler";
                    sampler.sampler2D.source = "img_surface";

                    switch (mat.textureMapper[0].wrapU)
                    {
                        case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_s = "WRAP"; break;
                        case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_s = "MIRROR"; break;
                        case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_s = "CLAMP"; break;
                        case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_s = "BORDER"; break;
                        default: sampler.sampler2D.wrap_s = "WRAP"; break;
                    }

                    switch (mat.textureMapper[0].wrapV)
                    {
                        case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_t = "WRAP"; break;
                        case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_t = "MIRROR"; break;
                        case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_t = "CLAMP"; break;
                        case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_t = "BORDER"; break;
                        default: sampler.sampler2D.wrap_t = "WRAP"; break;
                    }

                    switch (mat.textureMapper[0].minFilter)
                    {
                        case RenderBase.OTextureMinFilter.linearMipmapLinear: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_LINEAR"; break;
                        case RenderBase.OTextureMinFilter.linearMipmapNearest: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_NEAREST"; break;
                        case RenderBase.OTextureMinFilter.nearestMipmapLinear: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_LINEAR"; break;
                        case RenderBase.OTextureMinFilter.nearestMipmapNearest: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_NEAREST"; break;
                        default: sampler.sampler2D.minfilter = "NONE"; break;
                    }

                    switch (mat.textureMapper[0].magFilter)
                    {
                        case RenderBase.OTextureMagFilter.linear: sampler.sampler2D.magfilter = "LINEAR"; break;
                        case RenderBase.OTextureMagFilter.nearest: sampler.sampler2D.magfilter = "NEAREST"; break;
                        default: sampler.sampler2D.magfilter = "NONE"; break;
                    }

                    sampler.sampler2D.mipfilter = sampler.sampler2D.magfilter;

                    eff.profile_COMMON.newparam.Add(sampler);

                    eff.profile_COMMON.technique.sid = "img_technique";

                    eff.profile_COMMON.technique.phong.emission.set(Color.Black);
                    eff.profile_COMMON.technique.phong.ambient.set(Color.Black);
                    eff.profile_COMMON.technique.phong.specular.set(Color.White);

                    eff.profile_COMMON.technique.phong.diffuse.texture.texture = "img_sampler";

                    eff.profile_COMMON.technique.phong.transparency = new daePhongTransparent();

                    if (fire)
                    {
                        eff.profile_COMMON.technique.phong.transparency.value = "0.1";
                    }
                    else
                    {
                        eff.profile_COMMON.technique.phong.transparency.value = "1.0";
                    }

                    daeShiny.library_effects.Add(eff);
                }

                jointNames = null;
                invBindPoses = null;
                for (int index = 0; index < mdl.skeleton.Count; index++)
                {
                    RenderBase.OMatrix transform = new RenderBase.OMatrix();
                    transformSkeleton(mdl.skeleton, index, ref transform);

                    jointNames += mdl.skeleton[index].name;
                    daeMatrix mtx = new daeMatrix();
                    mtx.set(transform.invert());
                    invBindPoses += mtx.data;
                    if (index < mdl.skeleton.Count - 1)
                    {
                        jointNames += " ";
                        invBindPoses += " ";
                    }
                }

                meshIndex = 0;
                vs = new daeVisualScene();
                vs.name = "vs_" + mdl.name;
                vs.id = vs.name + "_id";
                if (mdl.skeleton.Count > 0) writeSkeleton(mdl.skeleton, 0, ref vs.node);

                rightIris = false;

                foreach (RenderBase.OMesh obj in mdl.mesh)
                {
                    float largestUV = 0.0f;

                    //Geometry
                    daeGeometry geometry = new daeGeometry();

                    string meshName = "mesh_" + meshIndex++ + "_" + obj.name;
                    geometry.id = meshName + "_id";
                    geometry.name = meshName;

                    MeshUtils.optimizedMesh mesh = MeshUtils.optimizeMesh(obj);
                    List<float> positions = new List<float>();
                    List<float> normals = new List<float>();
                    List<float> uv0 = new List<float>();
                    List<float> uv1 = new List<float>();
                    List<float> uv2 = new List<float>();
                    List<float> colors = new List<float>();

                    if (obj.name.Contains("Iris"))
                    {
                        foreach (RenderBase.OVertex vtx in mesh.vertices)
                        {
                            if (mesh.texUVCount > 0)
                            {
                                if (vtx.texture0.x > largestUV)
                                {
                                    largestUV = vtx.texture0.x;
                                }
                            }
                        }

                        foreach (RenderBase.OVertex vtx in mesh.vertices)
                        {
                            if (mesh.texUVCount > 0)
                            {
                                vtx.texture0.x = largestUV - vtx.texture0.x;

                                if (rightIris)
                                {
                                    vtx.texture0.x = 1.25f - vtx.texture0.x;
                                }
                            }

                            if (mesh.texUVCount > 1)
                            {
                                vtx.texture1.x = largestUV - vtx.texture1.x;

                                if (rightIris)
                                {
                                    vtx.texture1.x = 1.25f - vtx.texture1.x;
                                }
                            }

                            if (mesh.texUVCount > 2)
                            {
                                vtx.texture2.x = largestUV - vtx.texture2.x;

                                if (rightIris)
                                {
                                    vtx.texture2.x = 1.25f - vtx.texture2.x;
                                }
                            }
                        }

                        rightIris = !rightIris;
                    }
                    else if (obj.name.Contains("Eye"))
                    {
                        foreach (RenderBase.OVertex vtx in mesh.vertices)
                        {
                            if (mesh.texUVCount > 0)
                            {
                                if (vtx.texture0.x > largestUV)
                                {
                                    largestUV = vtx.texture0.x;
                                }
                            }
                        }

                        foreach (RenderBase.OVertex vtx in mesh.vertices)
                        {
                            if (mesh.texUVCount > 0)
                            {
                                vtx.texture0.x = largestUV - vtx.texture0.x;
                            }

                            if (mesh.texUVCount > 1)
                            {
                                vtx.texture1.x = largestUV - vtx.texture1.x;
                            }

                            if (mesh.texUVCount > 2)
                            {
                                vtx.texture2.x = largestUV - vtx.texture2.x;
                            }
                        }
                    }

                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        positions.Add(vtx.position.x);
                        positions.Add(vtx.position.y);
                        positions.Add(vtx.position.z);

                        if (mesh.hasNormal)
                        {
                            normals.Add(vtx.normal.x);
                            normals.Add(vtx.normal.y);
                            normals.Add(vtx.normal.z);
                        }

                        if (mesh.texUVCount > 0)
                        {
                            uv0.Add(vtx.texture0.x);
                            uv0.Add(vtx.texture0.y);
                        }

                        if (mesh.texUVCount > 1)
                        {
                            uv1.Add(vtx.texture1.x);
                            uv1.Add(vtx.texture1.y);
                        }

                        if (mesh.texUVCount > 2)
                        {
                            uv2.Add(vtx.texture2.x);
                            uv2.Add(vtx.texture2.y);
                        }

                        if (mesh.hasColor)
                        {
                            colors.Add(((vtx.diffuseColor >> 16) & 0xff) / 255f);
                            colors.Add(((vtx.diffuseColor >> 8) & 0xff) / 255f);
                            colors.Add((vtx.diffuseColor & 0xff) / 255f);
                            colors.Add(((vtx.diffuseColor >> 24) & 0xff) / 255f);
                        }
                    }

                    daeSource position = new daeSource();
                    position.name = meshName + "_position";
                    position.id = position.name + "_id";
                    position.float_array = new daeFloatArray();
                    position.float_array.id = position.name + "_array_id";
                    position.float_array.set(positions);
                    position.technique_common.accessor.source = "#" + position.float_array.id;
                    position.technique_common.accessor.count = (uint)mesh.vertices.Count;
                    position.technique_common.accessor.stride = 3;
                    position.technique_common.accessor.addParam("X", "float");
                    position.technique_common.accessor.addParam("Y", "float");
                    position.technique_common.accessor.addParam("Z", "float");

                    geometry.mesh.source.Add(position);

                    daeSource normal = new daeSource();
                    if (mesh.hasNormal)
                    {
                        normal.name = meshName + "_normal";
                        normal.id = normal.name + "_id";
                        normal.float_array = new daeFloatArray();
                        normal.float_array.id = normal.name + "_array_id";
                        normal.float_array.set(normals);
                        normal.technique_common.accessor.source = "#" + normal.float_array.id;
                        normal.technique_common.accessor.count = (uint)mesh.vertices.Count;
                        normal.technique_common.accessor.stride = 3;
                        normal.technique_common.accessor.addParam("X", "float");
                        normal.technique_common.accessor.addParam("Y", "float");
                        normal.technique_common.accessor.addParam("Z", "float");

                        geometry.mesh.source.Add(normal);
                    }

                    daeSource[] texUV = new daeSource[3];
                    for (int i = 0; i < mesh.texUVCount; i++)
                    {
                        texUV[i] = new daeSource();

                        texUV[i].name = meshName + "_uv" + i;
                        texUV[i].id = texUV[i].name + "_id";
                        texUV[i].float_array = new daeFloatArray();
                        texUV[i].float_array.id = texUV[i].name + "_array_id";
                        texUV[i].technique_common.accessor.source = "#" + texUV[i].float_array.id;
                        texUV[i].technique_common.accessor.count = (uint)mesh.vertices.Count;
                        texUV[i].technique_common.accessor.stride = 2;
                        texUV[i].technique_common.accessor.addParam("S", "float");
                        texUV[i].technique_common.accessor.addParam("T", "float");

                        geometry.mesh.source.Add(texUV[i]);
                    }

                    daeSource color = new daeSource();
                    if (mesh.hasColor)
                    {
                        color.name = meshName + "_color";
                        color.id = color.name + "_id";
                        color.float_array = new daeFloatArray();
                        color.float_array.id = color.name + "_array_id";
                        color.float_array.set(colors);
                        color.technique_common.accessor.source = "#" + color.float_array.id;
                        color.technique_common.accessor.count = (uint)mesh.vertices.Count;
                        color.technique_common.accessor.stride = 4;
                        color.technique_common.accessor.addParam("R", "float");
                        color.technique_common.accessor.addParam("G", "float");
                        color.technique_common.accessor.addParam("B", "float");
                        color.technique_common.accessor.addParam("A", "float");

                        geometry.mesh.source.Add(color);
                    }

                    geometry.mesh.vertices.id = meshName + "_vertices_id";
                    geometry.mesh.vertices.addInput("POSITION", "#" + position.id);

                    geometry.mesh.triangles.material = mdl.material[obj.materialId].name + "_mat";
                    geometry.mesh.triangles.addInput("VERTEX", "#" + geometry.mesh.vertices.id);
                    if (mesh.hasNormal) geometry.mesh.triangles.addInput("NORMAL", "#" + normal.id);
                    if (mesh.hasColor) geometry.mesh.triangles.addInput("COLOR", "#" + color.id);
                    if (mesh.texUVCount > 0)
                    {
                        texUV[0].float_array.set(uv0);
                        geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[0].id);
                    }
                    if (mesh.texUVCount > 1)
                    {
                        texUV[1].float_array.set(uv1);
                        geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[1].id, 0, 1);
                    }
                    if (mesh.texUVCount > 2)
                    {
                        texUV[2].float_array.set(uv2);
                        geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[2].id, 0, 2);
                    }
                    geometry.mesh.triangles.set(mesh.indices);

                    daeShiny.library_geometries.Add(geometry);

                    bool hasNode = obj.vertices[0].node.Count > 0;
                    bool hasWeight = obj.vertices[0].weight.Count > 0;
                    bool hasController = hasNode && hasWeight;

                    //Controller
                    daeController controller = new daeController();
                    if (hasController)
                    {
                        controller.id = meshName + "_ctrl_id";

                        controller.skin.source = "#" + geometry.id;
                        controller.skin.bind_shape_matrix.set(new RenderBase.OMatrix());

                        daeSource joints = new daeSource();
                        joints.id = meshName + "_ctrl_joint_names_id";
                        joints.Name_array = new daeNameArray();
                        joints.Name_array.id = meshName + "_ctrl_joint_names_array_id";
                        joints.Name_array.count = (uint)mdl.skeleton.Count;
                        joints.Name_array.data = jointNames;
                        joints.technique_common.accessor.source = "#" + joints.Name_array.id;
                        joints.technique_common.accessor.count = joints.Name_array.count;
                        joints.technique_common.accessor.stride = 1;
                        joints.technique_common.accessor.addParam("JOINT", "Name");

                        controller.skin.src.Add(joints);

                        daeSource bindPoses = new daeSource();
                        bindPoses.id = meshName + "_ctrl_inv_bind_poses_id";
                        bindPoses.float_array = new daeFloatArray();
                        bindPoses.float_array.id = meshName + "_ctrl_inv_bind_poses_array_id";
                        bindPoses.float_array.count = (uint)(mdl.skeleton.Count * 16);
                        bindPoses.float_array.data = invBindPoses;
                        bindPoses.technique_common.accessor.source = "#" + bindPoses.float_array.id;
                        bindPoses.technique_common.accessor.count = (uint)mdl.skeleton.Count;
                        bindPoses.technique_common.accessor.stride = 16;
                        bindPoses.technique_common.accessor.addParam("TRANSFORM", "float4x4");

                        controller.skin.src.Add(bindPoses);

                        daeSource weights = new daeSource();
                        weights.id = meshName + "_ctrl_weights_id";
                        weights.float_array = new daeFloatArray();
                        weights.float_array.id = meshName + "_ctrl_weights_array_id";
                        weights.technique_common.accessor.source = "#" + weights.float_array.id;
                        weights.technique_common.accessor.stride = 1;
                        weights.technique_common.accessor.addParam("WEIGHT", "float");

                        StringBuilder w = new StringBuilder();
                        StringBuilder vcount = new StringBuilder();
                        StringBuilder v = new StringBuilder();

                        float[] wLookBack = new float[32];
                        uint wLookBackIndex = 0;
                        int buffLen = 0;

                        int wIndex = 0;
                        int wCount = 0;
                        foreach (RenderBase.OVertex vtx in mesh.vertices)
                        {
                            int count = Math.Min(vtx.node.Count, vtx.weight.Count);

                            vcount.Append(count + " ");
                            for (int n = 0; n < count; n++)
                            {
                                v.Append(vtx.node[n] + " ");
                                bool found = false;
                                uint bPos = (wLookBackIndex - 1) & 0x1f;
                                for (int i = 0; i < buffLen; i++)
                                {
                                    if (wLookBack[bPos] == vtx.weight[n])
                                    {
                                        v.Append(wIndex - (i + 1) + " ");
                                        found = true;
                                        break;
                                    }
                                    bPos = (bPos - 1) & 0x1f;
                                }

                                if (!found)
                                {
                                    v.Append(wIndex++ + " ");
                                    w.Append(vtx.weight[n].ToString(CultureInfo.InvariantCulture) + " ");
                                    wCount++;

                                    wLookBack[wLookBackIndex] = vtx.weight[n];
                                    wLookBackIndex = (wLookBackIndex + 1) & 0x1f;
                                    if (buffLen < wLookBack.Length) buffLen++;
                                }
                            }
                        }

                        weights.float_array.data = w.ToString().TrimEnd();
                        weights.float_array.count = (uint)wCount;
                        weights.technique_common.accessor.count = (uint)wCount;

                        controller.skin.src.Add(weights);
                        controller.skin.vertex_weights.vcount = vcount.ToString().TrimEnd();
                        controller.skin.vertex_weights.v = v.ToString().TrimEnd();
                        controller.skin.vertex_weights.count = (uint)mesh.vertices.Count;
                        controller.skin.joints.addInput("JOINT", "#" + joints.id);
                        controller.skin.joints.addInput("INV_BIND_MATRIX", "#" + bindPoses.id);

                        controller.skin.vertex_weights.addInput("JOINT", "#" + joints.id);
                        controller.skin.vertex_weights.addInput("WEIGHT", "#" + weights.id, 1);

                        if (daeShiny.library_controllers == null) daeShiny.library_controllers = new List<daeController>();
                        daeShiny.library_controllers.Add(controller);
                    }

                    //Visual scene node
                    daeNode node = new daeNode();
                    node.name = "vsn_" + meshName;
                    node.id = node.name + "_id";
                    node.matrix.set(new RenderBase.OMatrix());
                    if (hasController)
                    {
                        node.instance_controller = new daeInstanceController();
                        node.instance_controller.url = "#" + controller.id;
                        node.instance_controller.skeleton = "#" + mdl.skeleton[0].name + "_bone_id";
                        node.instance_controller.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name + "_mat";
                        node.instance_controller.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                    }
                    else
                    {
                        node.instance_geometry = new daeInstanceGeometry();
                        node.instance_geometry.url = "#" + geometry.id;
                        node.instance_geometry.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name + "_mat";
                        node.instance_geometry.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                    }

                    vs.node.Add(node);
                }
                daeShiny.library_visual_scenes.Add(vs);

                scene = new daeInstaceVisualScene();
                scene.url = "#" + vs.id;
                daeShiny.scene.Add(scene);

                settings = new XmlWriterSettings
                {
                    Encoding = Encoding.UTF8,
                    Indent = true
                };

                ns = new XmlSerializerNamespaces();
                ns.Add("", "http://www.collada.org/2005/11/COLLADASchema");
                serializer = new XmlSerializer(typeof(COLLADA));
                output = XmlWriter.Create(new FileStream(fileName.Replace(".bch", "_Shiny.dae").Replace("BCH", "DAE"), FileMode.Create), settings);
                serializer.Serialize(output, daeShiny, ns);
                output.Close();
            }
            #endregion
        }
        /// <summary>
        ///     Exports a Model to the Collada format.
        ///     See: https://www.khronos.org/files/collada_spec_1_4.pdf for more information.
        /// </summary>
        /// <param name="model">The Model that will be exported</param>
        /// <param name="fileName">The output File Name</param>
        /// <param name="modelIndex">Index of the model to be exported</param>
        /// <param name="skeletalAnimationIndex">(Optional) Index of the skeletal animation</param>
        public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex, int skeletalAnimationIndex = -1)
        {
            RenderBase.OModel mdl = model.model[modelIndex];
            COLLADA dae = new COLLADA();

            dae.asset.created = DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ");
            dae.asset.modified = dae.asset.created;

            foreach (RenderBase.OTexture tex in model.texture)
            {
                daeImage img = new daeImage();
                img.id = tex.name + "_id";
                img.name = tex.name;
                img.init_from = "./" + tex.name + ".png";

                dae.library_images.Add(img);
            }

            foreach (RenderBase.OMaterial mat in mdl.material)
            {
                daeMaterial mtl = new daeMaterial();
                mtl.name = mat.name + "_mat";
                mtl.id = mtl.name + "_id";
                mtl.instance_effect.url = "#eff_" + mat.name + "_id";

                dae.library_materials.Add(mtl);

                daeEffect eff = new daeEffect();
                eff.id = "eff_" + mat.name + "_id";
                eff.name = "eff_" + mat.name;

                daeParam surface = new daeParam();
                surface.surface = new daeParamSurfaceElement();
                surface.sid = "img_surface";
                surface.surface.type = "2D";
                surface.surface.init_from = mat.name0 + "_id";
                surface.surface.format = "PNG";
                eff.profile_COMMON.newparam.Add(surface);

                daeParam sampler = new daeParam();
                sampler.sampler2D = new daeParamSampler2DElement();
                sampler.sid = "img_sampler";
                sampler.sampler2D.source = "img_surface";

                switch (mat.textureMapper[0].wrapU)
                {
                    case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_s = "WRAP"; break;
                    case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_s = "MIRROR"; break;
                    case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_s = "CLAMP"; break;
                    case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_s = "BORDER"; break;
                    default: sampler.sampler2D.wrap_s = "NONE"; break;
                }

                switch (mat.textureMapper[0].wrapV)
                {
                    case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_t = "WRAP"; break;
                    case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_t = "MIRROR"; break;
                    case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_t = "CLAMP"; break;
                    case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_t = "BORDER"; break;
                    default: sampler.sampler2D.wrap_t = "NONE"; break;
                }

                switch (mat.textureMapper[0].minFilter)
                {
                    case RenderBase.OTextureMinFilter.linearMipmapLinear: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_LINEAR"; break;
                    case RenderBase.OTextureMinFilter.linearMipmapNearest: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_NEAREST"; break;
                    case RenderBase.OTextureMinFilter.nearestMipmapLinear: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_LINEAR"; break;
                    case RenderBase.OTextureMinFilter.nearestMipmapNearest: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_NEAREST"; break;
                    default: sampler.sampler2D.minfilter = "NONE"; break;
                }

                switch (mat.textureMapper[0].magFilter)
                {
                    case RenderBase.OTextureMagFilter.linear: sampler.sampler2D.magfilter = "LINEAR"; break;
                    case RenderBase.OTextureMagFilter.nearest: sampler.sampler2D.magfilter = "NEAREST"; break;
                    default: sampler.sampler2D.magfilter = "NONE"; break;
                }

                sampler.sampler2D.mipfilter = sampler.sampler2D.magfilter;

                eff.profile_COMMON.newparam.Add(sampler);

                eff.profile_COMMON.technique.sid = "img_technique";
                eff.profile_COMMON.technique.phong.emission.set(Color.Black);
                eff.profile_COMMON.technique.phong.ambient.set(Color.Black);
                eff.profile_COMMON.technique.phong.specular.set(Color.White);
                eff.profile_COMMON.technique.phong.diffuse.texture.texture = "img_sampler";

                dae.library_effects.Add(eff);
            }

            string jointNames = null;
            string invBindPoses = null;
            for (int index = 0; index < mdl.skeleton.Count; index++)
            {
                RenderBase.OMatrix transform = new RenderBase.OMatrix();
                transformSkeleton(mdl.skeleton, index, ref transform);

                jointNames += mdl.skeleton[index].name;
                daeMatrix mtx = new daeMatrix();
                mtx.set(transform.invert());
                invBindPoses += mtx.data;
                if (index < mdl.skeleton.Count - 1)
                {
                    jointNames += " ";
                    invBindPoses += " ";
                }
            }

            int meshIndex = 0;
            daeVisualScene vs = new daeVisualScene();
            vs.name = "vs_" + mdl.name;
            vs.id = vs.name + "_id";
            if (mdl.skeleton.Count > 0) writeSkeleton(mdl.skeleton, 0, ref vs.node);
            foreach (RenderBase.OMesh obj in mdl.mesh)
            {
                //Geometry
                daeGeometry geometry = new daeGeometry();

                string meshName = "mesh_" + meshIndex++ + "_" + obj.name;
                geometry.id = meshName + "_id";
                geometry.name = meshName;

                MeshUtils.optimizedMesh mesh = MeshUtils.optimizeMesh(obj);
                List<float> positions = new List<float>();
                List<float> normals = new List<float>();
                List<float> uv0 = new List<float>();
                List<float> uv1 = new List<float>();
                List<float> uv2 = new List<float>();
                List<float> colors = new List<float>();
                foreach (RenderBase.OVertex vtx in mesh.vertices)
                {
                    positions.Add(vtx.position.x);
                    positions.Add(vtx.position.y);
                    positions.Add(vtx.position.z);

                    if (mesh.hasNormal)
                    {
                        normals.Add(vtx.normal.x);
                        normals.Add(vtx.normal.y);
                        normals.Add(vtx.normal.z);
                    }

                    if (mesh.texUVCount > 0)
                    {
                        uv0.Add(vtx.texture0.x);
                        uv0.Add(vtx.texture0.y);
                    }

                    if (mesh.texUVCount > 1)
                    {
                        uv1.Add(vtx.texture1.x);
                        uv1.Add(vtx.texture1.y);
                    }

                    if (mesh.texUVCount > 2)
                    {
                        uv2.Add(vtx.texture2.x);
                        uv2.Add(vtx.texture2.y);
                    }

                    if (mesh.hasColor)
                    {
                        colors.Add(((vtx.diffuseColor >> 16) & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 8) & 0xff) / 255f);
                        colors.Add((vtx.diffuseColor & 0xff) / 255f);
                        colors.Add(((vtx.diffuseColor >> 24) & 0xff) / 255f);
                    }
                }

                daeSource position = new daeSource();
                position.name = meshName + "_position";
                position.id = position.name + "_id";
                position.float_array = new daeFloatArray();
                position.float_array.id = position.name + "_array_id";
                position.float_array.set(positions);
                position.technique_common.accessor.source = "#" + position.float_array.id;
                position.technique_common.accessor.count = (uint)mesh.vertices.Count;
                position.technique_common.accessor.stride = 3;
                position.technique_common.accessor.addParam("X", "float");
                position.technique_common.accessor.addParam("Y", "float");
                position.technique_common.accessor.addParam("Z", "float");

                geometry.mesh.source.Add(position);

                daeSource normal = new daeSource();
                if (mesh.hasNormal)
                {
                    normal.name = meshName + "_normal";
                    normal.id = normal.name + "_id";
                    normal.float_array = new daeFloatArray();
                    normal.float_array.id = normal.name + "_array_id";
                    normal.float_array.set(normals);
                    normal.technique_common.accessor.source = "#" + normal.float_array.id;
                    normal.technique_common.accessor.count = (uint)mesh.vertices.Count;
                    normal.technique_common.accessor.stride = 3;
                    normal.technique_common.accessor.addParam("X", "float");
                    normal.technique_common.accessor.addParam("Y", "float");
                    normal.technique_common.accessor.addParam("Z", "float");

                    geometry.mesh.source.Add(normal);
                }

                daeSource[] texUV = new daeSource[3];
                for (int i = 0; i < mesh.texUVCount; i++)
                {
                    texUV[i] = new daeSource();

                    texUV[i].name = meshName + "_uv" + i;
                    texUV[i].id = texUV[i].name + "_id";
                    texUV[i].float_array = new daeFloatArray();
                    texUV[i].float_array.id = texUV[i].name + "_array_id";
                    texUV[i].technique_common.accessor.source = "#" + texUV[i].float_array.id;
                    texUV[i].technique_common.accessor.count = (uint)mesh.vertices.Count;
                    texUV[i].technique_common.accessor.stride = 2;
                    texUV[i].technique_common.accessor.addParam("S", "float");
                    texUV[i].technique_common.accessor.addParam("T", "float");

                    geometry.mesh.source.Add(texUV[i]);
                }

                daeSource color = new daeSource();
                if (mesh.hasColor)
                {
                    color.name = meshName + "_color";
                    color.id = color.name + "_id";
                    color.float_array = new daeFloatArray();
                    color.float_array.id = color.name + "_array_id";
                    color.float_array.set(colors);
                    color.technique_common.accessor.source = "#" + color.float_array.id;
                    color.technique_common.accessor.count = (uint)mesh.vertices.Count;
                    color.technique_common.accessor.stride = 4;
                    color.technique_common.accessor.addParam("R", "float");
                    color.technique_common.accessor.addParam("G", "float");
                    color.technique_common.accessor.addParam("B", "float");
                    color.technique_common.accessor.addParam("A", "float");

                    geometry.mesh.source.Add(color);
                }

                geometry.mesh.vertices.id = meshName + "_vertices_id";
                geometry.mesh.vertices.addInput("POSITION", "#" + position.id);


                geometry.mesh.triangles.material = mdl.material[obj.materialId].name;
                geometry.mesh.triangles.addInput("VERTEX", "#" + geometry.mesh.vertices.id);
                if (mesh.hasNormal) geometry.mesh.triangles.addInput("NORMAL", "#" + normal.id);
                if (mesh.hasColor) geometry.mesh.triangles.addInput("COLOR", "#" + color.id);
                if (mesh.texUVCount > 0)
                {
                    texUV[0].float_array.set(uv0);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[0].id);
                }
                if (mesh.texUVCount > 1)
                {
                    texUV[1].float_array.set(uv1);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[1].id, 0, 1);
                }
                if (mesh.texUVCount > 2)
                {
                    texUV[2].float_array.set(uv2);
                    geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[2].id, 0, 2);
                }
                geometry.mesh.triangles.set(mesh.indices);

                dae.library_geometries.Add(geometry);

                bool hasNode = obj.vertices[0].node.Count > 0;
                bool hasWeight = obj.vertices[0].weight.Count > 0;
                bool hasController = hasNode && hasWeight;

                //Controller
                daeController controller = new daeController();
                if (hasController)
                {
                    controller.id = meshName + "_ctrl_id";

                    controller.skin.source = "#" + geometry.id;
                    controller.skin.bind_shape_matrix.set(new RenderBase.OMatrix());

                    daeSource joints = new daeSource();
                    joints.id = meshName + "_ctrl_joint_names_id";
                    joints.Name_array = new daeNameArray();
                    joints.Name_array.id = meshName + "_ctrl_joint_names_array_id";
                    joints.Name_array.count = (uint)mdl.skeleton.Count;
                    joints.Name_array.data = jointNames;
                    joints.technique_common.accessor.source = "#" + joints.Name_array.id;
                    joints.technique_common.accessor.count = joints.Name_array.count;
                    joints.technique_common.accessor.stride = 1;
                    joints.technique_common.accessor.addParam("JOINT", "Name");

                    controller.skin.src.Add(joints);

                    daeSource bindPoses = new daeSource();
                    bindPoses.id = meshName +"_ctrl_inv_bind_poses_id";
                    bindPoses.float_array = new daeFloatArray();
                    bindPoses.float_array.id = meshName + "_ctrl_inv_bind_poses_array_id";
                    bindPoses.float_array.count = (uint)(mdl.skeleton.Count * 16);
                    bindPoses.float_array.data = invBindPoses;
                    bindPoses.technique_common.accessor.source = "#" + bindPoses.float_array.id;
                    bindPoses.technique_common.accessor.count = (uint)mdl.skeleton.Count;
                    bindPoses.technique_common.accessor.stride = 16;
                    bindPoses.technique_common.accessor.addParam("TRANSFORM", "float4x4");

                    controller.skin.src.Add(bindPoses);

                    daeSource weights = new daeSource();
                    weights.id = meshName + "_ctrl_weights_id";
                    weights.float_array = new daeFloatArray();
                    weights.float_array.id = meshName + "_ctrl_weights_array_id";
                    weights.technique_common.accessor.source = "#" + weights.float_array.id;
                    weights.technique_common.accessor.stride = 1;
                    weights.technique_common.accessor.addParam("WEIGHT", "float");

                    StringBuilder w = new StringBuilder();
                    StringBuilder vcount = new StringBuilder();
                    StringBuilder v = new StringBuilder();

                    float[] wLookBack = new float[32];
                    uint wLookBackIndex = 0;
                    int buffLen = 0;

                    int wIndex = 0;
                    int wCount = 0;
                    foreach (RenderBase.OVertex vtx in mesh.vertices)
                    {
                        int count = Math.Min(vtx.node.Count, vtx.weight.Count);

                        vcount.Append(count + " ");
                        for (int n = 0; n < count; n++)
                        {
                            v.Append(vtx.node[n] + " ");
                            bool found = false;
                            uint bPos = (wLookBackIndex - 1) & 0x1f;
                            for (int i = 0; i < buffLen; i++)
                            {
                                if (wLookBack[bPos] == vtx.weight[n])
                                {
                                    v.Append(wIndex - (i + 1) + " ");
                                    found = true;
                                    break;
                                }
                                bPos = (bPos - 1) & 0x1f;
                            }

                            if (!found)
                            {
                                v.Append(wIndex++ + " ");
                                w.Append(vtx.weight[n].ToString(CultureInfo.InvariantCulture) + " ");
                                wCount++;

                                wLookBack[wLookBackIndex] = vtx.weight[n];
                                wLookBackIndex = (wLookBackIndex + 1) & 0x1f;
                                if (buffLen < wLookBack.Length) buffLen++;
                            }
                        }
                    }

                    weights.float_array.data = w.ToString().TrimEnd();
                    weights.float_array.count = (uint)wCount;
                    weights.technique_common.accessor.count = (uint)wCount;

                    controller.skin.src.Add(weights);
                    controller.skin.vertex_weights.vcount = vcount.ToString().TrimEnd();
                    controller.skin.vertex_weights.v = v.ToString().TrimEnd();
                    controller.skin.vertex_weights.count = (uint)mesh.vertices.Count;
                    controller.skin.joints.addInput("JOINT", "#" + joints.id);
                    controller.skin.joints.addInput("INV_BIND_MATRIX", "#" + bindPoses.id);

                    controller.skin.vertex_weights.addInput("JOINT", "#" + joints.id);
                    controller.skin.vertex_weights.addInput("WEIGHT", "#" + weights.id, 1);

                    if (dae.library_controllers == null) dae.library_controllers = new List<daeController>();
                    dae.library_controllers.Add(controller);
                }

                //Visual scene node
                daeNode node = new daeNode();
                node.name = "vsn_" + meshName;
                node.id = node.name + "_id";
                node.matrix.set(new RenderBase.OMatrix());
                if (hasController)
                {
                    node.instance_controller = new daeInstanceController();
                    node.instance_controller.url = "#" + controller.id;
                    node.instance_controller.skeleton = "#" + mdl.skeleton[0].name + "_bone_id";
                    node.instance_controller.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name;
                    node.instance_controller.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }
                else
                {
                    node.instance_geometry = new daeInstanceGeometry();
                    node.instance_geometry.url = "#" + geometry.id;
                    node.instance_geometry.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name;
                    node.instance_geometry.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id";
                }

                vs.node.Add(node);
            }
            dae.library_visual_scenes.Add(vs);

            daeInstaceVisualScene scene = new daeInstaceVisualScene();
            scene.url = "#" + vs.id;
            dae.scene.Add(scene);

            XmlWriterSettings settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent = true
            };

            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "http://www.collada.org/2005/11/COLLADASchema");
            XmlSerializer serializer = new XmlSerializer(typeof(COLLADA));
            XmlWriter output = XmlWriter.Create(new FileStream(fileName, FileMode.Create), settings);
            serializer.Serialize(output, dae, ns);
            output.Close();
        }