private XmlVertexData GetXmlVertexData(VertexData vertexData)
        {
            XmlVertexData xmlVertexData = new XmlVertexData(vertexData.vertexCount);
            xmlVertexData.positionData = null;
            xmlVertexData.normalData = null;
            xmlVertexData.diffuseData = null;
            xmlVertexData.specularData = null;

            // Normals are transformed by the transpose of the inverse of the spatial transform.
            // Exclude the scale though, since I don't want to mess that up.
            float scale = GetScale(exportTransform);
            Matrix4 tmpTransform = ScaleMatrix(exportTransform, 1 / scale);
            Matrix4 invTrans = tmpTransform.Inverse().Transpose();
            // I'm going to write all the texture buffers to one vertex
            // buffer, so I can leave textureOffset at zero.
            int textureOffset = 0;
            for (short bindIdx = 0; bindIdx < vertexData.vertexDeclaration.ElementCount; ++bindIdx) {
                VertexElement element = vertexData.vertexDeclaration.GetElement(bindIdx);
                HardwareVertexBuffer vBuffer = vertexData.vertexBufferBinding.GetBuffer(element.Source);
                int vertexOffset = element.Offset;
                int vertexStride = vBuffer.VertexSize;
                switch (element.Semantic) {
                    case VertexElementSemantic.Position:
                        xmlVertexData.positionData = new float[xmlVertexData.vertexCount, 3];
                        ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, xmlVertexData.positionData, exportTransform);
                        break;
                    case VertexElementSemantic.Normal:
                        xmlVertexData.normalData = new float[xmlVertexData.vertexCount, 3];
                        ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, xmlVertexData.normalData, invTrans);
                        break;
                    case VertexElementSemantic.Diffuse:
                        xmlVertexData.diffuseData = new uint[xmlVertexData.vertexCount];
                        ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, xmlVertexData.diffuseData);
                        break;
                    case VertexElementSemantic.Specular:
                        xmlVertexData.specularData = new uint[xmlVertexData.vertexCount];
                        ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, xmlVertexData.specularData);
                        break;
                    case VertexElementSemantic.TexCoords: {
                            int dim = VertexElement.GetTypeSize(element.Type) /
                                      VertexElement.GetTypeSize(VertexElementType.Float1);
                            float[,] data = new float[xmlVertexData.vertexCount, dim];
                            ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, data);
                            // pad out the list
                            while (textureOffset + element.Index >= xmlVertexData.multiTexData.Count)
                                xmlVertexData.multiTexData.Add(null);
                            // set this element
                            xmlVertexData.multiTexData[textureOffset + element.Index] = data;
                            textureOffset++;
                        }
                        break;
                    case VertexElementSemantic.Tangent:
                    case VertexElementSemantic.Binormal: {
                            int dim = VertexElement.GetTypeSize(element.Type) /
                                      VertexElement.GetTypeSize(VertexElementType.Float1);
                            float[,] data = new float[xmlVertexData.vertexCount, dim];
                            ReadBuffer(vBuffer, vertexOffset, vertexStride, xmlVertexData.vertexCount, element.Size, data);
                            // pad out the list
                            while (textureOffset + element.Index >= xmlVertexData.multiTexData.Count)
                                xmlVertexData.multiTexData.Add(null);
                            // set this element
                            xmlVertexData.multiTexData[textureOffset + element.Index] = data;
                            textureOffset++;
                        }
                        break;
                    default:
                        log.WarnFormat("Unknown vertex buffer semantic: {0}", element.Semantic);
                        break;
                }
            }
            return xmlVertexData;
        }
        protected XmlElement WriteVertex(XmlVertexData xmlVertData, int vertIndex)
        {
            XmlElement node = document.CreateElement("vertex");

            if (xmlVertData.positionData != null) {
                XmlElement childNode = document.CreateElement("position");
                WriteVector(childNode, ref xmlVertData.positionData, vertIndex);
                node.AppendChild(childNode);
            }

            if (xmlVertData.normalData != null) {
                XmlElement childNode = document.CreateElement("normal");
                WriteVector(childNode, ref xmlVertData.normalData, vertIndex);
                node.AppendChild(childNode);
            }

            if (xmlVertData.diffuseData != null) {
                XmlElement childNode = document.CreateElement("colour_diffuse");
                WriteColour(childNode, xmlVertData.diffuseData[vertIndex]);
                node.AppendChild(childNode);
            }

            if (xmlVertData.specularData != null) {
                XmlElement childNode = document.CreateElement("colour_specular");
                WriteColour(childNode, xmlVertData.specularData[vertIndex]);
                node.AppendChild(childNode);
            }

            if (xmlVertData.multiTexData != null) {
                for (int texIndex = 0; texIndex < xmlVertData.multiTexData.Count; ++texIndex) {
                    XmlElement childNode = document.CreateElement("texcoord");
                    WriteTexCoord(childNode, xmlVertData.multiTexData[texIndex], vertIndex);
                    node.AppendChild(childNode);
                }
            }

            return node;
        }
        protected XmlElement WriteVertexBuffer(XmlVertexData xmlVertData)
        {
            XmlElement node = document.CreateElement("vertexbuffer");
            XmlAttribute attr;

            if (xmlVertData.positionData != null) {
                attr = document.CreateAttribute("positions");
                attr.Value = "true";
                node.Attributes.Append(attr);
            }

            if (xmlVertData.normalData != null) {
                attr = document.CreateAttribute("normals");
                attr.Value = "true";
                node.Attributes.Append(attr);
            }

            if (xmlVertData.diffuseData != null) {
                attr = document.CreateAttribute("colours_diffuse");
                attr.Value = "true";
                node.Attributes.Append(attr);
            }

            if (xmlVertData.specularData != null) {
                attr = document.CreateAttribute("colours_specular");
                attr.Value = "true";
                node.Attributes.Append(attr);
            }

            if (xmlVertData.multiTexData != null) {
                attr = document.CreateAttribute("texture_coords");
                attr.Value = xmlVertData.multiTexData.Count.ToString();
                node.Attributes.Append(attr);

                for (int i = 0; i < xmlVertData.multiTexData.Count; ++i) {
                    attr = document.CreateAttribute("texture_coord_dimensions_" + i);
                    attr.Value = xmlVertData.multiTexData[i].GetLength(1).ToString();
                    node.Attributes.Append(attr);
                }
            }

            // now write the vertex entries;
            for (int i = 0; i < xmlVertData.vertexCount; ++i) {
                XmlElement childNode = WriteVertex(xmlVertData, i);
                node.AppendChild(childNode);
            }

            return node;
        }
        protected XmlNode WriteMesh()
        {
            XmlElement node = document.CreateElement("mesh");

            XmlNode childNode;

            // Build the dictionary of vertex data for the submeshes.
            for (int i = 0; i < mesh.SubMeshCount; ++i) {
                SubMesh subMesh = mesh.GetSubMesh(i);
                if (subMesh.vertexData == null)
                    continue;
                XmlVertexData vertexData = GetXmlVertexData(subMesh.vertexData);
                xmlVertexDataDict[subMesh] = vertexData;
            }

            // TODO: Write the shared xml vertex data
            if (mesh.SharedVertexData != null)
                sharedXmlVertexData = GetXmlVertexData(mesh.SharedVertexData);

            // Write the submesh components
            childNode = WriteSubmeshes();
            node.AppendChild(childNode);

            // Next write skeletonlink
            if (mesh.HasSkeleton) {
                childNode = WriteSkeletonLink();
                node.AppendChild(childNode);
            }

            if (mesh.SharedVertexData != null) {
                childNode = WriteGeometry(mesh);
                node.AppendChild(childNode);
            }

            if (mesh.BoneAssignmentList.Count > 0) {
                childNode = WriteBoneAssignments(mesh);
                node.AppendChild(childNode);
            }
            // TODO:
            // childNode = WriteLevelOfDetail();
            // node.AppendChild(childNode);

            // Next write submesh names
            childNode = WriteSubmeshNames();
            node.AppendChild(childNode);

            // Finally write the bounds info
            childNode = WriteBoundsInfo();
            node.AppendChild(childNode);

            return node;
        }
        protected XmlElement WriteGeometry(XmlVertexData xmlVertData, XmlElement node)
        {
            // Break up the vertex data into the portions used for geometry, and the
            // portions used for color and textures.

            XmlElement childNode;

            if (xmlVertData.positionData != null ||
                xmlVertData.normalData != null)
            {
                XmlVertexData xmlGeomVertData = new XmlVertexData(xmlVertData.vertexCount);
                xmlGeomVertData.positionData = xmlVertData.positionData;
                xmlGeomVertData.normalData = xmlVertData.normalData;
                xmlGeomVertData.diffuseData = null;
                xmlGeomVertData.specularData = null;
                xmlGeomVertData.multiTexData = null;

                childNode = WriteVertexBuffer(xmlGeomVertData);
                node.AppendChild(childNode);
            }

            if (xmlVertData.diffuseData != null ||
                xmlVertData.specularData != null ||
                xmlVertData.multiTexData.Count > 0)
            {
                XmlVertexData xmlMatVertData = new XmlVertexData(xmlVertData.vertexCount);
                xmlMatVertData.positionData = null;
                xmlMatVertData.normalData = null;
                xmlMatVertData.diffuseData = xmlVertData.diffuseData;
                xmlMatVertData.specularData = xmlVertData.specularData;
                xmlMatVertData.multiTexData = xmlVertData.multiTexData;

                childNode = WriteVertexBuffer(xmlMatVertData);
                node.AppendChild(childNode);
            }

            return node;
        }
        protected void ReadVertexBuffer(XmlNode node, VertexData vertexData, XmlVertexData xmlVertexData)
        {
            bool positions = false;
            bool normals = false;
            bool colours_diffuse = false;
            bool colours_specular = false;
            int texture_coords = 0;

            foreach (XmlAttribute attr in node.Attributes) {
                switch (attr.Name) {
                    case "positions":
                        if (attr.Value == "true") {
                            positions = true;
                            xmlVertexData.positionData = new float[xmlVertexData.vertexCount, 3];
                        }
                        break;
                    case "normals":
                        if (attr.Value == "true") {
                            normals = true;
                            xmlVertexData.normalData = new float[xmlVertexData.vertexCount, 3];
                        }
                        break;
                    case "colours_diffuse":
                        if (attr.Value == "true") {
                            colours_diffuse = true;
                            xmlVertexData.diffuseData = new uint[xmlVertexData.vertexCount];
                        }
                        break;
                    case "colours_specular":
                        if (attr.Value == "true") {
                            colours_specular = true;
                            xmlVertexData.specularData = new uint[xmlVertexData.vertexCount];
                        }
                        break;
                    case "texture_coords":
                        texture_coords = int.Parse(attr.Value);
                        break;
                    case "texture_coord_dimensions_0":
                    case "texture_coord_dimensions_1":
                    case "texture_coord_dimensions_2":
                    case "texture_coord_dimensions_3":
                    case "texture_coord_dimensions_4":
                    case "texture_coord_dimensions_5":
                    case "texture_coord_dimensions_6":
                    case "texture_coord_dimensions_7":
                        break;
                    default:
                        DebugMessage(node, attr);
                        break;
                }
            }

            for (int i = 0; i < texture_coords; ++i) {
                string key = string.Format("texture_coord_dimensions_{0}", i);
                XmlNode attrNode = node.Attributes.GetNamedItem(key);
                if (attrNode != null)
                    xmlVertexData.AddTexture(int.Parse(attrNode.Value));
                else
                    xmlVertexData.AddTexture(2);
            }

            int vertexIndex = 0;
            foreach (XmlNode childNode in node.ChildNodes) {
                switch (childNode.Name) {
                    case "vertex":
                        ReadVertex(childNode, xmlVertexData, vertexIndex++);
                        break;
                    default:
                        DebugMessage(childNode);
                        break;
                }
            }

            if (positions)
                AllocateBuffer(vertexData, VertexElementType.Float3,
                               VertexElementSemantic.Position, xmlVertexData.bindIdx++,
                               0, xmlVertexData.positionData);
            if (normals)
                AllocateBuffer(vertexData, VertexElementType.Float3,
                               VertexElementSemantic.Normal, xmlVertexData.bindIdx++,
                               0, xmlVertexData.normalData);
            if (colours_diffuse)
                AllocateBuffer(vertexData, VertexElementType.Color,
                               VertexElementSemantic.Diffuse, xmlVertexData.bindIdx++,
                               0, xmlVertexData.diffuseData);
            if (colours_specular)
                AllocateBuffer(vertexData, VertexElementType.Color,
                               VertexElementSemantic.Specular, xmlVertexData.bindIdx++,
                               0, xmlVertexData.specularData);
            for (int i = 0; i < texture_coords; ++i) {
                int dim = xmlVertexData.GetTextureData(i).GetLength(1);
                AllocateBuffer(vertexData,
                               VertexElement.MultiplyTypeCount(VertexElementType.Float1, dim),
                               VertexElementSemantic.TexCoords, xmlVertexData.bindIdx++,
                               i, xmlVertexData.GetTextureData(i));
            }

            // We have read the textures for this vertex buffer node.
            xmlVertexData.textureOffset += texture_coords;
        }
 protected void ReadVertex(XmlNode node, XmlVertexData vertexData, int vertexIndex)
 {
     int textureIndex = 0;
     foreach (XmlNode childNode in node.ChildNodes) {
         switch (childNode.Name) {
             case "position":
                 ReadVector(childNode, vertexData.positionData, vertexIndex);
                 break;
             case "normal":
                 ReadVector(childNode, vertexData.normalData, vertexIndex);
                 break;
             case "colour_diffuse":
                 ReadColour(childNode, vertexData.diffuseData, vertexIndex);
                 break;
             case "colour_specular":
                 ReadColour(childNode, vertexData.specularData, vertexIndex);
                 break;
             case "texcoord":
                 ReadTexCoord(childNode, vertexData.GetTextureData(textureIndex), vertexIndex);
                 textureIndex++;
                 break;
             default:
                 DebugMessage(childNode);
                 break;
         }
     }
 }
        protected void ReadGeometry(XmlNode node, SubMesh subMesh)
        {
            if (subMesh.useSharedVertices)
                throw new Exception("I don't support shared vertices");

            VertexData vertexData = new VertexData();
            subMesh.vertexData = vertexData;

            vertexData.vertexStart = 0;
            vertexData.vertexCount = int.Parse(node.Attributes["vertexcount"].Value);

            XmlVertexData xmlVertexData = new XmlVertexData(vertexData.vertexCount);

            // Read in the various vertex buffers for this geometry, and
            // consolidate them into one vertex buffer.
            foreach (XmlNode childNode in node.ChildNodes) {
                switch (childNode.Name) {
                    case "vertexbuffer":
                        ReadVertexBuffer(childNode, subMesh.vertexData, xmlVertexData);
                        break;
                    default:
                        DebugMessage(childNode);
                        break;
                }
            }

            xmlVertexDataDict[subMesh] = xmlVertexData;
        }