nuclear@54: /* nuclear@54: goat3d - 3D scene, character, and animation file format library. nuclear@54: Copyright (C) 2013-2014 John Tsiombikas nuclear@54: nuclear@54: This program is free software: you can redistribute it and/or modify nuclear@54: it under the terms of the GNU Lesser General Public License as published by nuclear@54: the Free Software Foundation, either version 3 of the License, or nuclear@54: (at your option) any later version. nuclear@54: nuclear@54: This program is distributed in the hope that it will be useful, nuclear@54: but WITHOUT ANY WARRANTY; without even the implied warranty of nuclear@54: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nuclear@54: GNU Lesser General Public License for more details. nuclear@54: nuclear@54: You should have received a copy of the GNU Lesser General Public License nuclear@54: along with this program. If not, see . nuclear@54: */ nuclear@19: #include nuclear@75: #include nuclear@75: #include nuclear@19: #include "goat3d.h" nuclear@19: #include "goat3d_impl.h" nuclear@19: #include "tinyxml2.h" nuclear@19: #include "log.h" nuclear@19: nuclear@47: using namespace g3dimpl; nuclear@19: using namespace tinyxml2; nuclear@19: nuclear@19: static Material *read_material(Scene *scn, XMLElement *xml_mtl); nuclear@19: static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr); nuclear@19: static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh); nuclear@75: static Node *read_node(Scene *scn, XMLElement *xml_node, std::map &linkmap); nuclear@75: static std::string get_name(XMLElement *node, int idx, const char *def_prefix); nuclear@19: nuclear@19: bool Scene::loadxml(goat3d_io *io) nuclear@19: { nuclear@19: long bytes = io->seek(0, SEEK_END, io->cls); nuclear@19: io->seek(0, SEEK_SET, io->cls); nuclear@19: nuclear@19: char *buf = new char[bytes]; nuclear@19: if(io->read(buf, bytes, io->cls) < bytes) { nuclear@19: logmsg(LOG_ERROR, "failed to read XML scene file\n"); nuclear@45: delete [] buf; nuclear@19: return false; nuclear@19: } nuclear@19: nuclear@19: XMLDocument xml; nuclear@19: XMLError err = xml.Parse(buf, bytes); nuclear@19: if(err) { nuclear@19: logmsg(LOG_ERROR, "failed to parse XML scene file: %s\n%s\n", xml.GetErrorStr1(), nuclear@19: xml.GetErrorStr2()); nuclear@45: delete [] buf; nuclear@19: return false; nuclear@19: } nuclear@19: nuclear@19: XMLElement *root = xml.RootElement(); nuclear@19: if(strcmp(root->Name(), "scene") != 0) { nuclear@19: logmsg(LOG_ERROR, "invalid XML file, root node is not \n"); nuclear@45: delete [] buf; nuclear@19: return false; nuclear@19: } nuclear@19: nuclear@19: XMLElement *elem; nuclear@19: nuclear@19: // get all materials nuclear@19: elem = root->FirstChildElement("mtl"); nuclear@19: while(elem) { nuclear@19: Material *mtl = read_material(this, elem); nuclear@19: if(mtl) { nuclear@19: add_material(mtl); nuclear@19: } nuclear@19: elem = elem->NextSiblingElement("mtl"); nuclear@19: } nuclear@19: nuclear@19: // get all meshes nuclear@19: elem = root->FirstChildElement("mesh"); nuclear@19: while(elem) { nuclear@19: Mesh *mesh = read_mesh(this, elem); nuclear@19: if(mesh) { nuclear@19: add_mesh(mesh); nuclear@19: } nuclear@19: elem = elem->NextSiblingElement("mesh"); nuclear@19: } nuclear@19: nuclear@75: // get all nodes nuclear@75: std::map linkmap; nuclear@75: nuclear@75: elem = root->FirstChildElement("node"); nuclear@75: while(elem) { nuclear@75: Node *node = read_node(this, elem, linkmap); nuclear@75: if(node) { nuclear@75: add_node(node); nuclear@75: } nuclear@75: elem = elem->NextSiblingElement("node"); nuclear@75: } nuclear@75: nuclear@75: // link up all the nodes in the hierarchy nuclear@75: for(size_t i=0; iadd_child(nodes[i]); nuclear@75: } nuclear@75: } nuclear@75: } nuclear@75: nuclear@45: delete [] buf; nuclear@45: return true; nuclear@19: } nuclear@19: nuclear@51: bool Scene::load_anim_xml(goat3d_io *io) nuclear@51: { nuclear@51: long bytes = io->seek(0, SEEK_END, io->cls); nuclear@51: io->seek(0, SEEK_SET, io->cls); nuclear@51: nuclear@51: char *buf = new char[bytes]; nuclear@51: if(io->read(buf, bytes, io->cls) < bytes) { nuclear@51: logmsg(LOG_ERROR, "failed to read XML animation file\n"); nuclear@51: delete [] buf; nuclear@51: return false; nuclear@51: } nuclear@51: nuclear@51: XMLDocument xml; nuclear@51: XMLError err = xml.Parse(buf, bytes); nuclear@51: if(err) { nuclear@51: logmsg(LOG_ERROR, "failed to parse XML animation file: %s\n%s\n", xml.GetErrorStr1(), nuclear@51: xml.GetErrorStr2()); nuclear@51: delete [] buf; nuclear@51: return false; nuclear@51: } nuclear@51: nuclear@51: XMLElement *root = xml.RootElement(); nuclear@51: if(strcmp(root->Name(), "anim") != 0) { nuclear@51: logmsg(LOG_ERROR, "invalid XML file, root node is not \n"); nuclear@51: delete [] buf; nuclear@51: return false; nuclear@51: } nuclear@51: nuclear@51: XMLElement *elem; nuclear@51: nuclear@51: elem = root->FirstChildElement(); nuclear@51: while(elem) { nuclear@51: const char *elem_name = elem->Name(); nuclear@51: nuclear@51: if(strcmp(elem_name, "name") == 0) { nuclear@51: } else if(strcmp(elem_name, "attr") == 0) { nuclear@51: } nuclear@51: elem = elem->NextSiblingElement(); nuclear@51: } nuclear@51: nuclear@51: delete [] buf; nuclear@51: return true; nuclear@51: } nuclear@19: nuclear@19: static Material *read_material(Scene *scn, XMLElement *xml_mtl) nuclear@19: { nuclear@19: Material *mtl = new Material; nuclear@75: mtl->name = get_name(xml_mtl, scn->get_material_count(), "material"); nuclear@19: nuclear@19: // get all the material attributes in turn nuclear@19: XMLElement *elem = xml_mtl->FirstChildElement("attr"); nuclear@19: while(elem) { nuclear@19: MaterialAttrib attr; nuclear@19: const char *name = read_material_attrib(&attr, elem); nuclear@19: if(name) { nuclear@19: (*mtl)[name] = attr; nuclear@19: } nuclear@19: nuclear@19: elem = elem->NextSiblingElement("attr"); nuclear@19: } nuclear@19: nuclear@19: return mtl; nuclear@19: } nuclear@19: nuclear@19: static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr) nuclear@19: { nuclear@19: const char *name; nuclear@19: nuclear@19: XMLElement *elem; nuclear@19: if((elem = xml_attr->FirstChildElement("name"))) { nuclear@19: if(!(name = elem->Attribute("string"))) { nuclear@19: return 0; nuclear@19: } nuclear@19: } nuclear@19: nuclear@19: if((elem = xml_attr->FirstChildElement("val"))) { nuclear@19: if(elem->QueryFloatAttribute("float", &attr->value.x) != XML_NO_ERROR) { nuclear@19: // try a float3 nuclear@19: const char *valstr = elem->Attribute("float3"); nuclear@19: if(!valstr || sscanf(valstr, "%f %f %f", &attr->value.x, &attr->value.y, nuclear@19: &attr->value.z) != 3) { nuclear@19: // try a float4 nuclear@19: valstr = elem->Attribute("float4"); nuclear@19: if(!valstr || sscanf(valstr, "%f %f %f %f", &attr->value.x, &attr->value.y, nuclear@19: &attr->value.z, &attr->value.w) != 4) { nuclear@19: // no valid val attribute found nuclear@19: return 0; nuclear@19: } nuclear@19: } nuclear@19: } nuclear@19: } nuclear@19: nuclear@19: if((elem = xml_attr->FirstChildElement("map"))) { nuclear@19: const char *tex = elem->Attribute("string"); nuclear@19: if(tex) { nuclear@19: attr->map = std::string(tex); nuclear@19: } nuclear@19: } nuclear@19: nuclear@19: return name; nuclear@19: } nuclear@19: nuclear@19: static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh) nuclear@19: { nuclear@19: Mesh *mesh = new Mesh; nuclear@75: mesh->name = get_name(xml_mesh, scn->get_mesh_count(), "mesh"); nuclear@19: nuclear@19: XMLElement *elem; nuclear@19: if((elem = xml_mesh->FirstChildElement("material"))) { nuclear@19: int idx; nuclear@19: if(elem->QueryIntAttribute("int", &idx) == XML_NO_ERROR) { nuclear@19: mesh->material = scn->get_material(idx); nuclear@19: } else { nuclear@19: // try string nuclear@19: const char *mtlstr = elem->Attribute("string"); nuclear@19: if(mtlstr) { nuclear@19: mesh->material = scn->get_material(mtlstr); nuclear@19: } nuclear@19: } nuclear@19: } nuclear@19: nuclear@19: /* reading mesh data from XML is not supported, only MESH_FILE can be used to nuclear@19: * specify an external mesh file to be loaded nuclear@19: */ nuclear@19: nuclear@19: if((elem = xml_mesh->FirstChildElement("file"))) { nuclear@19: const char *fname = elem->Attribute("string"); nuclear@19: if(fname) { nuclear@74: char *path = (char*)fname; nuclear@74: if(scn->goat->search_path) { nuclear@74: path = (char*)alloca(strlen(fname) + strlen(scn->goat->search_path) + 2); nuclear@74: sprintf(path, "%s/%s", scn->goat->search_path, fname); nuclear@74: } nuclear@74: if(!mesh->load(path)) { nuclear@19: delete mesh; nuclear@19: return 0; nuclear@19: } nuclear@19: } nuclear@19: } nuclear@19: nuclear@19: return mesh; nuclear@19: } nuclear@19: nuclear@75: static Node *read_node(Scene *scn, XMLElement *xml_node, std::map &linkmap) nuclear@75: { nuclear@75: Node *node = new Node; nuclear@75: node->set_name(get_name(xml_node, scn->get_node_count(), "node").c_str()); nuclear@75: nuclear@75: XMLElement *elem; nuclear@75: if((elem = xml_node->FirstChildElement("parent"))) { nuclear@75: const char *pname = elem->Attribute("string"); nuclear@75: if(pname) { nuclear@75: linkmap[node] = pname; nuclear@75: } nuclear@75: } nuclear@75: nuclear@75: if((elem = xml_node->FirstChildElement("mesh"))) { nuclear@75: Mesh *mesh = scn->get_mesh(elem->Attribute("string")); nuclear@75: if(mesh) { nuclear@75: node->set_object(mesh); nuclear@75: } nuclear@75: } else if((elem = xml_node->FirstChildElement("light"))) { nuclear@75: Light *lt = scn->get_light(elem->Attribute("string")); nuclear@75: if(lt) { nuclear@75: node->set_object(lt); nuclear@75: } nuclear@75: } else if((elem = xml_node->FirstChildElement("camera"))) { nuclear@75: Camera *cam = scn->get_camera(elem->Attribute("string")); nuclear@75: if(cam) { nuclear@75: node->set_object(cam); nuclear@75: } nuclear@75: } nuclear@75: nuclear@75: float vec[4]; nuclear@75: if((elem = xml_node->FirstChildElement("pos"))) { nuclear@75: const char *val = elem->Attribute("float3"); nuclear@75: if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) { nuclear@75: node->set_position(Vector3(val[0], val[1], val[2])); nuclear@75: } else { nuclear@75: logmsg(LOG_ERROR, "node %s: invalid position tag\n", node->get_name()); nuclear@75: } nuclear@75: } nuclear@75: if((elem = xml_node->FirstChildElement("rot"))) { nuclear@75: const char *val = elem->Attribute("float4"); nuclear@75: if(val && sscanf(val, "%f %f %f %f", vec, vec + 1, vec + 2, vec + 3) == 4) { nuclear@75: node->set_rotation(Quaternion(vec[3], Vector3(vec[0], vec[1], vec[2]))); nuclear@75: } else { nuclear@75: logmsg(LOG_ERROR, "node %s: invalid rotation tag\n", node->get_name()); nuclear@75: } nuclear@75: } nuclear@75: if((elem = xml_node->FirstChildElement("scale"))) { nuclear@75: const char *val = elem->Attribute("float3"); nuclear@75: if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) { nuclear@75: node->set_scaling(Vector3(vec[0], vec[1], vec[2])); nuclear@75: } else { nuclear@75: logmsg(LOG_ERROR, "node %s: invalid scaling tag\n", node->get_name()); nuclear@75: } nuclear@75: } nuclear@75: if((elem = xml_node->FirstChildElement("pivot"))) { nuclear@75: const char *val = elem->Attribute("float3"); nuclear@75: if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) { nuclear@75: node->set_pivot(Vector3(vec[0], vec[1], vec[2])); nuclear@75: } else { nuclear@75: logmsg(LOG_ERROR, "node %s: invalid pivot tag\n", node->get_name()); nuclear@75: } nuclear@75: } nuclear@75: nuclear@75: return node; nuclear@75: } nuclear@75: nuclear@75: static std::string get_name(XMLElement *node, int idx, const char *def_prefix) nuclear@19: { nuclear@19: char buf[64]; nuclear@19: const char *name = 0; nuclear@19: nuclear@19: XMLElement *elem; nuclear@19: if((elem = node->FirstChildElement("name"))) { nuclear@19: name = elem->Attribute("string"); nuclear@19: } nuclear@19: nuclear@19: if(!name) { nuclear@75: sprintf(buf, "%s%04d", def_prefix, idx); nuclear@19: name = buf; nuclear@19: } nuclear@19: nuclear@19: return std::string(name); nuclear@19: }