//----------------------------------------------------------------------------- // Product: OpenCTM tools // File: dae.cpp // Description: Implementation of the DAE (Collada) file format // importer/exporter. //----------------------------------------------------------------------------- // Copyright (c) 2009-2010 Marcus Geelnard // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include "dae.h" #if !defined(WIN32) && defined(_WIN32) #define WIN32 #endif #ifdef WIN32 #include #endif using namespace std; enum Axis { X,Y,Z,S,T }; class Source { public: Source() : stride(0), count(0), offset(0) { } Source(const Source& copy) : array(copy.array), stride(copy.stride), count(copy.count), offset(copy.offset), params(copy.params) { } vector array; size_t stride, count, offset; vector params; }; class Indexes { public: Indexes(size_t _vertIndex = 0, size_t _normalIndex = 0, size_t _texcoordIndex = 0) : vertIndex(_vertIndex), normalIndex(_normalIndex), texcoordIndex(_texcoordIndex) { } Indexes(const Indexes& copy) : vertIndex(copy.vertIndex), normalIndex(copy.normalIndex), texcoordIndex(copy.texcoordIndex) { } size_t vertIndex, normalIndex, texcoordIndex; }; enum Semantic { VERTEX, NORMAL, TEXCOORD, POSITIONS, UNKNOWN }; struct Input { string source; Semantic semantic; size_t offset; }; Semantic ToSemantic(const string& semantic) { if (semantic == "VERTEX") return VERTEX; else if (semantic == "NORMAL") return NORMAL; else if (semantic == "TEXCOORD") return TEXCOORD; else if (semantic == "POSITIONS") return POSITIONS; else return UNKNOWN; } void ReadIndexArray(TiXmlElement* p , vector& array) { istringstream strStream (p->GetText()); char val[100]; size_t value = 0; while (!strStream.eof()) { strStream >> val; value = atoi(val); array.push_back(value); } } void ReadInputs(TiXmlElement* rootElem,bool& hasVerts,bool& hasNormals,bool& hasTexcoords, string& vertSource,string& normalSource,string& texcoordSource, vector& inputs) { TiXmlHandle root(rootElem); for(TiXmlElement* inputElem = root.FirstChild( "input" ).ToElement();inputElem; inputElem = inputElem->NextSiblingElement()) { if(string(inputElem->Value()) != "input") continue; //TiXmlHandle input(inputElem); inputs.push_back(Input()); inputs.back().source = string(inputElem->Attribute("source")).substr(1); inputs.back().offset = atoi(inputElem->Attribute("offset")); inputs.back().semantic = ToSemantic(inputElem->Attribute("semantic")); switch(inputs.back().semantic) { case VERTEX: hasVerts = true; vertSource = inputs.back().source; break; case NORMAL: hasNormals = true; normalSource = inputs.back().source; break; case TEXCOORD: hasTexcoords = true; texcoordSource = inputs.back().source; break; default: break; } } } Source& GetSource(map& sources, map >& vertices,const string& source) { map::iterator srcIterator = sources.find(source); if (srcIterator != sources.end()) return srcIterator->second; map >::iterator vertIterator = vertices.find(source); if (vertIterator != vertices.end() ) { for (vector::iterator i = vertIterator->second.begin(); i != vertIterator->second.end() ; ++i) { srcIterator = sources.find(i->source); if (srcIterator != sources.end()) return srcIterator->second; } } else { throw string("Error"); } return srcIterator->second; } void InsertVertNormalTexcoord(vector& vertVector,vector& normalVector,vector& texcoordVector, bool hasVerts, bool hasNormals, bool hasTexcoords,const string& vertSource ,const string& normalSource ,const string& texcoordSource ,size_t vertIndex , size_t normalIndex , size_t texcoordIndex, map& sources,map >& vertices) { if (hasVerts) { Source& src = GetSource(sources, vertices , vertSource); float x = 0, y = 0, z = 0; if (src.stride >= 1) x = src.array[src.offset + vertIndex*src.stride]; if (src.stride >= 2) y = src.array[src.offset + vertIndex*src.stride + 1]; if (src.stride >= 3) z = src.array[src.offset + vertIndex*src.stride + 2]; vertVector.push_back(Vector3(x,y,z)); } if (hasNormals) { Source& src = GetSource(sources, vertices , normalSource); float x = 0, y = 0, z = 0; if (src.stride >= 1) x = src.array[src.offset + normalIndex*src.stride]; if (src.stride >= 2) y = src.array[src.offset + normalIndex*src.stride + 1]; if (src.stride >= 3) z = src.array[src.offset + normalIndex*src.stride + 2]; normalVector.push_back(Vector3(x,y,z) ); } if (hasTexcoords) { Source& src = GetSource(sources, vertices , texcoordSource); float s = 0, t = 0; if (src.stride >= 1) s = src.array[src.offset + texcoordIndex*src.stride]; if (src.stride >= 2) t = src.array[src.offset + texcoordIndex*src.stride + 1]; texcoordVector.push_back(Vector2(s,t)); } } /// Import a DAE file from a file. void Import_DAE(const char * aFileName, Mesh * aMesh) { // Start by ensuring that we use proper locale settings for the file format setlocale(LC_NUMERIC, "C"); // Clear the mesh aMesh->Clear(); // Load the XML document TiXmlDocument doc(aFileName); if (doc.LoadFile()) { TiXmlHandle hDoc(&doc); TiXmlElement* elem = hDoc.FirstChildElement().Element(); TiXmlHandle hRoot(elem); map sources; size_t indicesOffset = 0, vertexOffset = 0, texcoordOffset = 0, normalOffset = 0; TiXmlHandle geometry = hRoot.FirstChild( "library_geometries" ).FirstChild("geometry"); for(elem = geometry.ToElement(); elem; elem=elem->NextSiblingElement()) { TiXmlHandle geometry(elem); TiXmlElement* meshElem = geometry.FirstChild("mesh").ToElement(); if(meshElem) { TiXmlHandle mesh(meshElem); TiXmlElement* sourceElem; for(sourceElem = mesh.FirstChild("source").ToElement(); sourceElem; sourceElem = sourceElem->NextSiblingElement()) { if(string(sourceElem->Value()) != "source") continue; TiXmlHandle source(sourceElem); string id = source.ToElement()->Attribute("id"); TiXmlElement* arr = sourceElem->FirstChild("float_array")->ToElement(); string str = arr->GetText(); istringstream strStream (str); sources.insert(make_pair(id, Source())); TiXmlElement* techniqueElem = sourceElem->FirstChild("technique_common")->ToElement(); TiXmlElement* accessorElem = techniqueElem->FirstChild("accessor")->ToElement(); sources[id].stride = atoi(accessorElem->Attribute("stride")); sources[id].count = atoi(accessorElem->Attribute("count")); if (accessorElem->Attribute("offset")) sources[id].offset = atoi(accessorElem->Attribute("offset")); char val[100]; float value = 0; while(!strStream.eof()) { strStream >> val; value = float(atof(val)); sources[id].array.push_back(value); } } TiXmlElement* verticesElem = mesh.FirstChild("vertices").ToElement(); map > vertices; if (verticesElem) { string id = verticesElem->Attribute("id"); vertices.insert(make_pair(id, vector())); TiXmlElement* inputElem; for(inputElem = verticesElem->FirstChild("input")->ToElement(); inputElem; inputElem = inputElem->NextSiblingElement()) { if(string(inputElem->Value()) != "input") continue; vertices[id].push_back(Input()); vertices[id].back().source = string(inputElem->Attribute("source")).substr(1); vertices[id].back().semantic = ToSemantic(inputElem->Attribute("semantic")); } } TiXmlElement* trianglesElem = mesh.FirstChild("triangles").ToElement(); if(trianglesElem) { TiXmlHandle triangles(trianglesElem); vector inputs; bool hasVerts = false, hasNormals = false, hasTexcoords = false; string vertSource = "", normalSource = "", texcoordSource = ""; /* TiXmlElement* inputElem; for(inputElem = triangles.FirstChild( "input" ).ToElement(); inputElem; inputElem = inputElem->NextSiblingElement()) { if(string(inputElem->Value()) != "input") continue; //TiXmlHandle input(inputElem); inputs.push_back(Input()); inputs.back().source = string(inputElem->Attribute("source")).substr(1); inputs.back().offset = atoi(inputElem->Attribute("offset")); inputs.back().semantic = ToSemantic(inputElem->Attribute("semantic")); switch(inputs.back().semantic) { case VERTEX: hasVerts = true; vertSource = inputs.back().source; break; case NORMAL: hasNormals = true; normalSource = inputs.back().source; break; case TEXCOORD: hasTexcoords = true; texcoordSource = inputs.back().source; break; default: break; } } */ ReadInputs(trianglesElem, hasVerts, hasNormals, hasTexcoords, vertSource, normalSource, texcoordSource, inputs); vector pArray; TiXmlElement* p = triangles.FirstChild( "p" ).ToElement(); ReadIndexArray(p,pArray); vector indexVector; vector vertVector, normalVector; vector texcoordVector; map > > prevIndices; size_t index = 0; for (size_t i = 0; i < pArray.size() ; i += inputs.size()) { size_t vertIndex = 0, normalIndex = 0, texcoordIndex = 0; for (vector::const_iterator j = inputs.begin(); j != inputs.end(); ++j) { switch (j->semantic) { case VERTEX: vertIndex = pArray[i + j->offset]; break; case NORMAL: normalIndex = pArray[i + j->offset]; break; case TEXCOORD: texcoordIndex = pArray[i + j->offset]; break; default: break; } } map > >::iterator prevIt1 = prevIndices.find(vertIndex); if(prevIt1 != prevIndices.end()) { map >::iterator prevIt2 = prevIt1->second.find(normalIndex); if(prevIt2 != prevIt1->second.end()) { map< size_t, size_t >::iterator prevIt3 = prevIt2->second.find(texcoordIndex); if(prevIt3 != prevIt2->second.end()) { indexVector.push_back(prevIt3->second); } else { indexVector.push_back(index); prevIt2->second.insert(make_pair(texcoordIndex, index)); InsertVertNormalTexcoord(vertVector, normalVector, texcoordVector, hasVerts, hasNormals, hasTexcoords, vertSource, normalSource, texcoordSource, vertIndex, normalIndex, texcoordIndex, sources, vertices); ++index; } } else { indexVector.push_back(index); prevIt1->second.insert(make_pair(normalIndex, map< size_t, size_t >())); prevIt1->second[normalIndex].insert(make_pair(texcoordIndex, index)); InsertVertNormalTexcoord(vertVector, normalVector, texcoordVector, hasVerts, hasNormals, hasTexcoords, vertSource, normalSource, texcoordSource, vertIndex, normalIndex, texcoordIndex, sources, vertices); ++index; } } else { indexVector.push_back(index); prevIndices.insert(make_pair(vertIndex,map >())); prevIndices[vertIndex].insert(make_pair(normalIndex, map< size_t, size_t >())); prevIndices[vertIndex][normalIndex].insert(make_pair(texcoordIndex, index)); InsertVertNormalTexcoord(vertVector, normalVector, texcoordVector, hasVerts, hasNormals, hasTexcoords, vertSource, normalSource, texcoordSource, vertIndex, normalIndex, texcoordIndex, sources, vertices); ++index; } } TiXmlElement* polylistElem = mesh.FirstChild("polylist").ToElement(); if (polylistElem) { TiXmlHandle polylist(polylistElem); vector vcountArray, pArray; TiXmlElement* vcountElem = polylist.FirstChild("vcount").ToElement(); ReadIndexArray(vcountElem, vcountArray); TiXmlElement* pElem = polylist.FirstChild("p").ToElement(); ReadIndexArray(pElem, pArray); vector inputs; bool hasVerts = false, hasNormals = false, hasTexcoords = false; string vertSource = "", normalSource = "", texcoordSource = ""; ReadInputs(polylistElem, hasVerts, hasNormals, hasTexcoords, vertSource, normalSource, texcoordSource, inputs); size_t offset = 0; for (size_t i = 0; i < vcountArray.size(); ++i) { vector convexPolygon; for (size_t j = 0; j < vcountArray[i]; ++j) { convexPolygon.push_back(Indexes()); for (vector::const_iterator j = inputs.begin(); j != inputs.end(); ++j) { switch (j->semantic) { case VERTEX: convexPolygon.back().vertIndex = pArray[offset + j->offset]; break; case NORMAL: convexPolygon.back().normalIndex = pArray[offset + j->offset]; break; case TEXCOORD: convexPolygon.back().texcoordIndex = pArray[offset + j->offset]; break; default: break; } } } offset += vcountArray[i]; } } size_t indicesOff = indicesOffset, vertexOff = vertexOffset, normalOff = normalOffset, texcoordOff = texcoordOffset; indicesOffset += indexVector.size(); vertexOffset += vertVector.size(); normalOffset += normalVector.size(); texcoordOffset += texcoordVector.size(); aMesh->mIndices.resize(indicesOffset ); aMesh->mVertices.resize(vertexOffset ); aMesh->mNormals.resize(normalOffset ); aMesh->mTexCoords.resize(texcoordOffset ); for(size_t i = 0; i < indexVector.size(); ++i) aMesh->mIndices[indicesOff + i] = indexVector[i]; for(size_t i = 0; i < vertVector.size(); ++i) aMesh->mVertices[vertexOff + i] = vertVector[i]; for(size_t i = 0; i < normalVector.size(); ++i) aMesh->mNormals[normalOff + i] = normalVector[i]; for(size_t i = 0; i < texcoordVector.size(); ++i) aMesh->mTexCoords[texcoordOff + i] = texcoordVector[i]; } } } } else throw runtime_error("Could not open input file."); } /// Dump a float array to an XML text node. static void FloatArrayToXML(TiXmlElement * aNode, float * aArray, unsigned int aCount) { stringstream ss; for(unsigned int i = 0; i < aCount; ++ i) ss << aArray[i] << " "; aNode->LinkEndChild(new TiXmlText(ss.str().c_str())); } /// Generate an ISO 8601 format date string. static string MakeISO8601DateTime(void) { char buf[500]; #ifdef WIN32 SYSTEMTIME tm; GetSystemTime(&tm); sprintf(buf, "%i-%02i-%02iT%02i:%02i:%02i.%03iZ", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds); #else time_t t; time(&t); struct tm tm; localtime_r(&t, &tm); sprintf(buf, "%i-%02i-%02iT%02i:%02i:%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); #endif return string(buf); } /// Export a DAE file to a file. void Export_DAE(const char * aFileName, Mesh * aMesh, Options &aOptions) { // Start by ensuring that we use proper locale settings for the file format setlocale(LC_NUMERIC, "C"); // What should we export? bool exportTexCoords = aMesh->HasTexCoords() && !aOptions.mNoTexCoords; bool exportNormals = aMesh->HasNormals() && !aOptions.mNoNormals; TiXmlDocument xmlDoc; TiXmlElement * elem; string dateTime = MakeISO8601DateTime(); // Set XML declaration xmlDoc.LinkEndChild(new TiXmlDeclaration("1.0", "utf-8", "")); // Create root node TiXmlElement * root = new TiXmlElement("COLLADA"); xmlDoc.LinkEndChild(root); root->SetAttribute("xmlns", "http://www.collada.org/2005/11/COLLADASchema"); root->SetAttribute("version", "1.4.1"); // Create traceability nodes TiXmlElement * asset = new TiXmlElement("asset"); root->LinkEndChild(asset); TiXmlElement * contributor = new TiXmlElement("contributor"); asset->LinkEndChild(contributor); TiXmlElement * authoring_tool = new TiXmlElement("authoring_tool"); contributor->LinkEndChild(authoring_tool); authoring_tool->LinkEndChild(new TiXmlText("ctmconv")); TiXmlElement * comments = new TiXmlElement("comments"); contributor->LinkEndChild(comments); comments->LinkEndChild(new TiXmlText(aMesh->mComment.c_str())); elem = new TiXmlElement("created"); asset->LinkEndChild(elem); elem->LinkEndChild(new TiXmlText(dateTime.c_str())); elem = new TiXmlElement("modified"); asset->LinkEndChild(elem); elem->LinkEndChild(new TiXmlText(dateTime.c_str())); // Create the geometry nodes TiXmlElement * library_geometries = new TiXmlElement("library_geometries"); root->LinkEndChild(library_geometries); TiXmlElement * geometry = new TiXmlElement("geometry"); library_geometries->LinkEndChild(geometry); geometry->SetAttribute("id", "Mesh-1"); geometry->SetAttribute("name", "Mesh-1"); TiXmlElement * mesh = new TiXmlElement("mesh"); geometry->LinkEndChild(mesh); // Vertices (positions) TiXmlElement * source_position = new TiXmlElement("source"); mesh->LinkEndChild(source_position); source_position->SetAttribute("id", "Mesh-1-positions"); source_position->SetAttribute("name", "position"); TiXmlElement * positions_array = new TiXmlElement("float_array"); source_position->LinkEndChild(positions_array); positions_array->SetAttribute("id", "Mesh-1-positions-array"); positions_array->SetAttribute("count", int(aMesh->mVertices.size() * 3)); FloatArrayToXML(positions_array, &aMesh->mVertices[0].x, aMesh->mVertices.size() * 3); TiXmlElement * positions_technique = new TiXmlElement("technique_common"); source_position->LinkEndChild(positions_technique); TiXmlElement * positions_technique_accessor = new TiXmlElement("accessor"); positions_technique->LinkEndChild(positions_technique_accessor); positions_technique_accessor->SetAttribute("count", int(aMesh->mVertices.size())); positions_technique_accessor->SetAttribute("offset", 0); positions_technique_accessor->SetAttribute("source", "#Mesh-1-positions-array"); positions_technique_accessor->SetAttribute("stride", 3); elem = new TiXmlElement("param"); positions_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "X"); elem->SetAttribute("type", "float"); elem = new TiXmlElement("param"); positions_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "Y"); elem->SetAttribute("type", "float"); elem = new TiXmlElement("param"); positions_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "Z"); elem->SetAttribute("type", "float"); // Normals if(exportNormals) { TiXmlElement * source_normal = new TiXmlElement("source"); mesh->LinkEndChild(source_normal); source_normal->SetAttribute("id", "Mesh-1-normals"); source_normal->SetAttribute("name", "normal"); TiXmlElement * normals_array = new TiXmlElement("float_array"); source_normal->LinkEndChild(normals_array); normals_array->SetAttribute("id", "Mesh-1-normals-array"); normals_array->SetAttribute("count", int(aMesh->mVertices.size() * 3)); FloatArrayToXML(normals_array, &aMesh->mNormals[0].x, aMesh->mNormals.size() * 3); TiXmlElement * normals_technique = new TiXmlElement("technique_common"); source_normal->LinkEndChild(normals_technique); TiXmlElement * normals_technique_accessor = new TiXmlElement("accessor"); normals_technique->LinkEndChild(normals_technique_accessor); normals_technique_accessor->SetAttribute("count", int(aMesh->mVertices.size())); normals_technique_accessor->SetAttribute("offset", 0); normals_technique_accessor->SetAttribute("source", "#Mesh-1-normals-array"); normals_technique_accessor->SetAttribute("stride", 3); elem = new TiXmlElement("param"); normals_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "X"); elem->SetAttribute("type", "float"); elem = new TiXmlElement("param"); normals_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "Y"); elem->SetAttribute("type", "float"); elem = new TiXmlElement("param"); normals_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "Z"); elem->SetAttribute("type", "float"); } // UV map if(exportTexCoords) { TiXmlElement * source_map1 = new TiXmlElement("source"); mesh->LinkEndChild(source_map1); source_map1->SetAttribute("id", "Mesh-1-map1"); source_map1->SetAttribute("name", "map1"); TiXmlElement * map1_array = new TiXmlElement("float_array"); source_map1->LinkEndChild(map1_array); map1_array->SetAttribute("id", "Mesh-1-map1-array"); map1_array->SetAttribute("count", int(aMesh->mVertices.size() * 3)); FloatArrayToXML(map1_array, &aMesh->mTexCoords[0].u, aMesh->mTexCoords.size() * 2); TiXmlElement * map1_technique = new TiXmlElement("technique_common"); source_map1->LinkEndChild(map1_technique); TiXmlElement * map1_technique_accessor = new TiXmlElement("accessor"); map1_technique->LinkEndChild(map1_technique_accessor); map1_technique_accessor->SetAttribute("count", int(aMesh->mVertices.size())); map1_technique_accessor->SetAttribute("offset", 0); map1_technique_accessor->SetAttribute("source", "#Mesh-1-map1-array"); map1_technique_accessor->SetAttribute("stride", 2); elem = new TiXmlElement("param"); map1_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "S"); elem->SetAttribute("type", "float"); elem = new TiXmlElement("param"); map1_technique_accessor->LinkEndChild(elem); elem->SetAttribute("name", "T"); elem->SetAttribute("type", "float"); } // Vertices TiXmlElement * vertices = new TiXmlElement("vertices"); mesh->LinkEndChild(vertices); vertices->SetAttribute("id", "Mesh-1-vertices"); TiXmlElement * vertices_input = new TiXmlElement("input"); vertices->LinkEndChild(vertices_input); vertices_input->SetAttribute("semantic", "POSITION"); vertices_input->SetAttribute("source", "#Mesh-1-positions"); // Triangles TiXmlElement * triangles = new TiXmlElement("triangles"); mesh->LinkEndChild(triangles); triangles->SetAttribute("count", int(aMesh->mIndices.size() / 3)); int triangleInputCount = 0; elem = new TiXmlElement("input"); triangles->LinkEndChild(elem); elem->SetAttribute("offset", triangleInputCount); elem->SetAttribute("semantic", "VERTEX"); elem->SetAttribute("source", "#Mesh-1-vertices"); ++ triangleInputCount; if(exportNormals) { elem = new TiXmlElement("input"); triangles->LinkEndChild(elem); elem->SetAttribute("offset", triangleInputCount); elem->SetAttribute("semantic", "NORMAL"); elem->SetAttribute("source", "#Mesh-1-normals"); ++ triangleInputCount; } if(exportTexCoords) { elem = new TiXmlElement("input"); triangles->LinkEndChild(elem); elem->SetAttribute("offset", triangleInputCount); elem->SetAttribute("semantic", "TEXCOORD"); elem->SetAttribute("source", "#Mesh-1-map1"); elem->SetAttribute("set", 0); ++ triangleInputCount; } { elem = new TiXmlElement("p"); triangles->LinkEndChild(elem); stringstream ss; for(unsigned int i = 0; i < aMesh->mIndices.size(); ++ i) for(int j = 0; j < triangleInputCount; ++ j) ss << aMesh->mIndices[i] << " "; elem->LinkEndChild(new TiXmlText(ss.str().c_str())); } // Scene TiXmlElement * library_visual_scenes = new TiXmlElement("library_visual_scenes"); root->LinkEndChild(library_visual_scenes); TiXmlElement * visual_scene = new TiXmlElement("visual_scene"); library_visual_scenes->LinkEndChild(visual_scene); visual_scene->SetAttribute("id", "Scene-1"); visual_scene->SetAttribute("name", "Scene-1"); TiXmlElement * visual_scene_node = new TiXmlElement("node"); visual_scene->LinkEndChild(visual_scene_node); visual_scene_node->SetAttribute("id", "Object-1"); visual_scene_node->SetAttribute("name", "Object-1"); TiXmlElement * instance_geometry = new TiXmlElement("instance_geometry"); visual_scene_node->LinkEndChild(instance_geometry); instance_geometry->SetAttribute("url", "#Mesh-1"); TiXmlElement * scene = new TiXmlElement("scene"); root->LinkEndChild(scene); TiXmlElement * instance_visual_scene = new TiXmlElement("instance_visual_scene"); scene->LinkEndChild(instance_visual_scene); instance_visual_scene->SetAttribute("url", "#Scene-1"); // Save the XML document to a file xmlDoc.SaveFile(aFileName); if(xmlDoc.Error()) throw runtime_error(string(xmlDoc.ErrorDesc())); }