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@95: #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@95: struct Key { nuclear@95: long tm; nuclear@95: Vector4 val; nuclear@95: }; nuclear@95: nuclear@95: static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx); nuclear@95: static Key read_key(XMLElement *xml_key); nuclear@95: 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@95: logmsg(LOG_ERROR, "%s: root node is not \n", __FUNCTION__); nuclear@51: delete [] buf; nuclear@51: return false; nuclear@51: } nuclear@51: nuclear@95: int anim_idx = -1; nuclear@95: XMLElement *elem = root->FirstChildElement(); nuclear@51: while(elem) { nuclear@51: const char *elem_name = elem->Name(); nuclear@51: nuclear@95: if(strcmp(elem_name, "track") != 0) { nuclear@95: logmsg(LOG_ERROR, "%s: only s allowed in \n", __FUNCTION__); nuclear@95: delete [] buf; nuclear@95: return false; nuclear@95: } nuclear@95: nuclear@95: if(!read_track(this, elem, &anim_idx)) { nuclear@95: delete [] buf; nuclear@95: return false; nuclear@51: } nuclear@51: elem = elem->NextSiblingElement(); nuclear@51: } nuclear@51: nuclear@95: if(anim_idx == -1) { nuclear@95: logmsg(LOG_INFO, "%s: WARNING animation affected 0 nodes\n", __FUNCTION__); nuclear@95: } nuclear@95: nuclear@51: delete [] buf; nuclear@51: return true; nuclear@51: } nuclear@19: nuclear@95: static bool read_track(Scene *scn, XMLElement *xml_track, int *anim_idx) nuclear@95: { nuclear@95: Node *node = 0; nuclear@95: int type = -1; nuclear@95: std::vector keys; nuclear@95: nuclear@95: XMLElement *elem = xml_track->FirstChildElement(); nuclear@95: while(elem) { nuclear@95: const char *elem_name = elem->Name(); nuclear@95: nuclear@95: if(strcmp(elem_name, "node") == 0) { nuclear@95: const char *name = elem->Attribute("string"); nuclear@95: if(!name || !(node = scn->get_node(name))) { nuclear@95: logmsg(LOG_ERROR, "%s: invalid track node: %s\n", __FUNCTION__, name); nuclear@95: return false; nuclear@95: } nuclear@95: nuclear@95: } else if(strcmp(elem_name, "attr") == 0) { nuclear@95: const char *str = elem->Attribute("string"); nuclear@95: if(str && strcmp(str, "position") == 0) { nuclear@95: type = XFormNode::POSITION_TRACK; nuclear@95: } else if(str && strcmp(str, "rotation") == 0) { nuclear@95: type = XFormNode::ROTATION_TRACK; nuclear@95: } else if(str && strcmp(str, "scaling") == 0) { nuclear@95: type = XFormNode::SCALING_TRACK; nuclear@95: } else { nuclear@95: logmsg(LOG_ERROR, "%s: invalid track attribute specifier: %s\n", __FUNCTION__, str); nuclear@95: return false; nuclear@95: } nuclear@95: nuclear@95: } else if(strcmp(elem_name, "key") == 0) { nuclear@95: Key key = read_key(elem); nuclear@95: if(key.tm == LONG_MIN) { nuclear@95: return false; // logging in read_key nuclear@95: } nuclear@95: keys.push_back(key); nuclear@95: nuclear@95: } else { nuclear@95: logmsg(LOG_ERROR, "%s: unexpected element <%s> in \n", __FUNCTION__, elem_name); nuclear@95: return false; nuclear@95: } nuclear@95: elem = elem->NextSiblingElement(); nuclear@95: } nuclear@95: nuclear@95: if(!node) { nuclear@95: logmsg(LOG_ERROR, "%s: invalid track, missing node reference\n", __FUNCTION__); nuclear@95: return false; nuclear@95: } nuclear@95: if(type == -1) { nuclear@95: logmsg(LOG_ERROR, "%s: invalid track, missing attribute specifier\n", __FUNCTION__); nuclear@95: return false; nuclear@95: } nuclear@95: nuclear@95: if(*anim_idx == -1) { nuclear@95: // this is the first node we encounter. add a new animation and activate it nuclear@95: XFormNode *root = node->get_root(); nuclear@95: *anim_idx = root->get_animation_count() + 1; nuclear@95: nuclear@95: char name[64]; nuclear@95: sprintf(name, "anim%03d\n", *anim_idx); nuclear@95: root->add_animation(name); nuclear@95: root->use_animation(*anim_idx); nuclear@95: } else { nuclear@95: // make sure this node hierarchy already has this animation, otherwise add it nuclear@95: XFormNode *root = node->get_root(); nuclear@95: if(root->get_active_animation_index() != *anim_idx) { nuclear@95: char name[64]; nuclear@95: sprintf(name, "anim%03d\n", *anim_idx); nuclear@95: root->add_animation(name); nuclear@95: root->use_animation(*anim_idx); nuclear@95: } nuclear@95: } nuclear@95: nuclear@96: for(size_t i=0; iset_position(Vector3(keys[i].val.x, keys[i].val.y, keys[i].val.z), keys[i].tm); nuclear@95: break; nuclear@95: case XFormNode::ROTATION_TRACK: nuclear@96: node->set_rotation(Quaternion(keys[i].val.w, keys[i].val.x, keys[i].val.y, keys[i].val.z), keys[i].tm); nuclear@95: break; nuclear@95: case XFormNode::SCALING_TRACK: nuclear@96: node->set_scaling(Vector3(keys[i].val.x, keys[i].val.y, keys[i].val.z), keys[i].tm); nuclear@95: } nuclear@95: } nuclear@95: return true; nuclear@95: } nuclear@95: nuclear@95: static Key read_key(XMLElement *xml_key) nuclear@95: { nuclear@95: Key key; nuclear@95: key.tm = LONG_MIN; // initialize to invalid time nuclear@95: nuclear@95: XMLElement *xml_time = xml_key->FirstChildElement("time"); nuclear@95: XMLElement *xml_value = xml_key->FirstChildElement("value"); nuclear@95: nuclear@95: if(!xml_time || !xml_value) { nuclear@95: logmsg(LOG_ERROR, "%s: invalid key, missing either