rev |
line source |
nuclear@54
|
1 /*
|
nuclear@54
|
2 goat3d - 3D scene, character, and animation file format library.
|
nuclear@54
|
3 Copyright (C) 2013-2014 John Tsiombikas <nuclear@member.fsf.org>
|
nuclear@54
|
4
|
nuclear@54
|
5 This program is free software: you can redistribute it and/or modify
|
nuclear@54
|
6 it under the terms of the GNU Lesser General Public License as published by
|
nuclear@54
|
7 the Free Software Foundation, either version 3 of the License, or
|
nuclear@54
|
8 (at your option) any later version.
|
nuclear@54
|
9
|
nuclear@54
|
10 This program is distributed in the hope that it will be useful,
|
nuclear@54
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nuclear@54
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nuclear@54
|
13 GNU Lesser General Public License for more details.
|
nuclear@54
|
14
|
nuclear@54
|
15 You should have received a copy of the GNU Lesser General Public License
|
nuclear@54
|
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
nuclear@54
|
17 */
|
nuclear@19
|
18 #include <stdio.h>
|
nuclear@75
|
19 #include <map>
|
nuclear@75
|
20 #include <string>
|
nuclear@19
|
21 #include "goat3d.h"
|
nuclear@19
|
22 #include "goat3d_impl.h"
|
nuclear@19
|
23 #include "tinyxml2.h"
|
nuclear@19
|
24 #include "log.h"
|
nuclear@19
|
25
|
nuclear@47
|
26 using namespace g3dimpl;
|
nuclear@19
|
27 using namespace tinyxml2;
|
nuclear@19
|
28
|
nuclear@19
|
29 static Material *read_material(Scene *scn, XMLElement *xml_mtl);
|
nuclear@19
|
30 static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr);
|
nuclear@19
|
31 static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh);
|
nuclear@75
|
32 static Node *read_node(Scene *scn, XMLElement *xml_node, std::map<Node*, std::string> &linkmap);
|
nuclear@75
|
33 static std::string get_name(XMLElement *node, int idx, const char *def_prefix);
|
nuclear@19
|
34
|
nuclear@19
|
35 bool Scene::loadxml(goat3d_io *io)
|
nuclear@19
|
36 {
|
nuclear@19
|
37 long bytes = io->seek(0, SEEK_END, io->cls);
|
nuclear@19
|
38 io->seek(0, SEEK_SET, io->cls);
|
nuclear@19
|
39
|
nuclear@19
|
40 char *buf = new char[bytes];
|
nuclear@19
|
41 if(io->read(buf, bytes, io->cls) < bytes) {
|
nuclear@19
|
42 logmsg(LOG_ERROR, "failed to read XML scene file\n");
|
nuclear@45
|
43 delete [] buf;
|
nuclear@19
|
44 return false;
|
nuclear@19
|
45 }
|
nuclear@19
|
46
|
nuclear@19
|
47 XMLDocument xml;
|
nuclear@19
|
48 XMLError err = xml.Parse(buf, bytes);
|
nuclear@19
|
49 if(err) {
|
nuclear@19
|
50 logmsg(LOG_ERROR, "failed to parse XML scene file: %s\n%s\n", xml.GetErrorStr1(),
|
nuclear@19
|
51 xml.GetErrorStr2());
|
nuclear@45
|
52 delete [] buf;
|
nuclear@19
|
53 return false;
|
nuclear@19
|
54 }
|
nuclear@19
|
55
|
nuclear@19
|
56 XMLElement *root = xml.RootElement();
|
nuclear@19
|
57 if(strcmp(root->Name(), "scene") != 0) {
|
nuclear@19
|
58 logmsg(LOG_ERROR, "invalid XML file, root node is not <scene>\n");
|
nuclear@45
|
59 delete [] buf;
|
nuclear@19
|
60 return false;
|
nuclear@19
|
61 }
|
nuclear@19
|
62
|
nuclear@19
|
63 XMLElement *elem;
|
nuclear@19
|
64
|
nuclear@19
|
65 // get all materials
|
nuclear@19
|
66 elem = root->FirstChildElement("mtl");
|
nuclear@19
|
67 while(elem) {
|
nuclear@19
|
68 Material *mtl = read_material(this, elem);
|
nuclear@19
|
69 if(mtl) {
|
nuclear@19
|
70 add_material(mtl);
|
nuclear@19
|
71 }
|
nuclear@19
|
72 elem = elem->NextSiblingElement("mtl");
|
nuclear@19
|
73 }
|
nuclear@19
|
74
|
nuclear@19
|
75 // get all meshes
|
nuclear@19
|
76 elem = root->FirstChildElement("mesh");
|
nuclear@19
|
77 while(elem) {
|
nuclear@19
|
78 Mesh *mesh = read_mesh(this, elem);
|
nuclear@19
|
79 if(mesh) {
|
nuclear@19
|
80 add_mesh(mesh);
|
nuclear@19
|
81 }
|
nuclear@19
|
82 elem = elem->NextSiblingElement("mesh");
|
nuclear@19
|
83 }
|
nuclear@19
|
84
|
nuclear@75
|
85 // get all nodes
|
nuclear@75
|
86 std::map<Node*, std::string> linkmap;
|
nuclear@75
|
87
|
nuclear@75
|
88 elem = root->FirstChildElement("node");
|
nuclear@75
|
89 while(elem) {
|
nuclear@75
|
90 Node *node = read_node(this, elem, linkmap);
|
nuclear@75
|
91 if(node) {
|
nuclear@75
|
92 add_node(node);
|
nuclear@75
|
93 }
|
nuclear@75
|
94 elem = elem->NextSiblingElement("node");
|
nuclear@75
|
95 }
|
nuclear@75
|
96
|
nuclear@75
|
97 // link up all the nodes in the hierarchy
|
nuclear@75
|
98 for(size_t i=0; i<nodes.size(); i++) {
|
nuclear@75
|
99 std::string parent_name = linkmap[nodes[i]];
|
nuclear@75
|
100 if(!parent_name.empty()) {
|
nuclear@75
|
101 Node *parent = get_node(parent_name.c_str());
|
nuclear@75
|
102 if(parent) {
|
nuclear@75
|
103 parent->add_child(nodes[i]);
|
nuclear@75
|
104 }
|
nuclear@75
|
105 }
|
nuclear@75
|
106 }
|
nuclear@75
|
107
|
nuclear@45
|
108 delete [] buf;
|
nuclear@45
|
109 return true;
|
nuclear@19
|
110 }
|
nuclear@19
|
111
|
nuclear@51
|
112 bool Scene::load_anim_xml(goat3d_io *io)
|
nuclear@51
|
113 {
|
nuclear@51
|
114 long bytes = io->seek(0, SEEK_END, io->cls);
|
nuclear@51
|
115 io->seek(0, SEEK_SET, io->cls);
|
nuclear@51
|
116
|
nuclear@51
|
117 char *buf = new char[bytes];
|
nuclear@51
|
118 if(io->read(buf, bytes, io->cls) < bytes) {
|
nuclear@51
|
119 logmsg(LOG_ERROR, "failed to read XML animation file\n");
|
nuclear@51
|
120 delete [] buf;
|
nuclear@51
|
121 return false;
|
nuclear@51
|
122 }
|
nuclear@51
|
123
|
nuclear@51
|
124 XMLDocument xml;
|
nuclear@51
|
125 XMLError err = xml.Parse(buf, bytes);
|
nuclear@51
|
126 if(err) {
|
nuclear@51
|
127 logmsg(LOG_ERROR, "failed to parse XML animation file: %s\n%s\n", xml.GetErrorStr1(),
|
nuclear@51
|
128 xml.GetErrorStr2());
|
nuclear@51
|
129 delete [] buf;
|
nuclear@51
|
130 return false;
|
nuclear@51
|
131 }
|
nuclear@51
|
132
|
nuclear@51
|
133 XMLElement *root = xml.RootElement();
|
nuclear@51
|
134 if(strcmp(root->Name(), "anim") != 0) {
|
nuclear@51
|
135 logmsg(LOG_ERROR, "invalid XML file, root node is not <anim>\n");
|
nuclear@51
|
136 delete [] buf;
|
nuclear@51
|
137 return false;
|
nuclear@51
|
138 }
|
nuclear@51
|
139
|
nuclear@51
|
140 XMLElement *elem;
|
nuclear@51
|
141
|
nuclear@51
|
142 elem = root->FirstChildElement();
|
nuclear@51
|
143 while(elem) {
|
nuclear@51
|
144 const char *elem_name = elem->Name();
|
nuclear@51
|
145
|
nuclear@51
|
146 if(strcmp(elem_name, "name") == 0) {
|
nuclear@51
|
147 } else if(strcmp(elem_name, "attr") == 0) {
|
nuclear@51
|
148 }
|
nuclear@51
|
149 elem = elem->NextSiblingElement();
|
nuclear@51
|
150 }
|
nuclear@51
|
151
|
nuclear@51
|
152 delete [] buf;
|
nuclear@51
|
153 return true;
|
nuclear@51
|
154 }
|
nuclear@19
|
155
|
nuclear@19
|
156 static Material *read_material(Scene *scn, XMLElement *xml_mtl)
|
nuclear@19
|
157 {
|
nuclear@19
|
158 Material *mtl = new Material;
|
nuclear@75
|
159 mtl->name = get_name(xml_mtl, scn->get_material_count(), "material");
|
nuclear@19
|
160
|
nuclear@19
|
161 // get all the material attributes in turn
|
nuclear@19
|
162 XMLElement *elem = xml_mtl->FirstChildElement("attr");
|
nuclear@19
|
163 while(elem) {
|
nuclear@19
|
164 MaterialAttrib attr;
|
nuclear@19
|
165 const char *name = read_material_attrib(&attr, elem);
|
nuclear@19
|
166 if(name) {
|
nuclear@19
|
167 (*mtl)[name] = attr;
|
nuclear@19
|
168 }
|
nuclear@19
|
169
|
nuclear@19
|
170 elem = elem->NextSiblingElement("attr");
|
nuclear@19
|
171 }
|
nuclear@19
|
172
|
nuclear@19
|
173 return mtl;
|
nuclear@19
|
174 }
|
nuclear@19
|
175
|
nuclear@19
|
176 static const char *read_material_attrib(MaterialAttrib *attr, XMLElement *xml_attr)
|
nuclear@19
|
177 {
|
nuclear@19
|
178 const char *name;
|
nuclear@19
|
179
|
nuclear@19
|
180 XMLElement *elem;
|
nuclear@19
|
181 if((elem = xml_attr->FirstChildElement("name"))) {
|
nuclear@19
|
182 if(!(name = elem->Attribute("string"))) {
|
nuclear@19
|
183 return 0;
|
nuclear@19
|
184 }
|
nuclear@19
|
185 }
|
nuclear@19
|
186
|
nuclear@19
|
187 if((elem = xml_attr->FirstChildElement("val"))) {
|
nuclear@19
|
188 if(elem->QueryFloatAttribute("float", &attr->value.x) != XML_NO_ERROR) {
|
nuclear@19
|
189 // try a float3
|
nuclear@19
|
190 const char *valstr = elem->Attribute("float3");
|
nuclear@19
|
191 if(!valstr || sscanf(valstr, "%f %f %f", &attr->value.x, &attr->value.y,
|
nuclear@19
|
192 &attr->value.z) != 3) {
|
nuclear@19
|
193 // try a float4
|
nuclear@19
|
194 valstr = elem->Attribute("float4");
|
nuclear@19
|
195 if(!valstr || sscanf(valstr, "%f %f %f %f", &attr->value.x, &attr->value.y,
|
nuclear@19
|
196 &attr->value.z, &attr->value.w) != 4) {
|
nuclear@19
|
197 // no valid val attribute found
|
nuclear@19
|
198 return 0;
|
nuclear@19
|
199 }
|
nuclear@19
|
200 }
|
nuclear@19
|
201 }
|
nuclear@19
|
202 }
|
nuclear@19
|
203
|
nuclear@19
|
204 if((elem = xml_attr->FirstChildElement("map"))) {
|
nuclear@19
|
205 const char *tex = elem->Attribute("string");
|
nuclear@19
|
206 if(tex) {
|
nuclear@19
|
207 attr->map = std::string(tex);
|
nuclear@19
|
208 }
|
nuclear@19
|
209 }
|
nuclear@19
|
210
|
nuclear@19
|
211 return name;
|
nuclear@19
|
212 }
|
nuclear@19
|
213
|
nuclear@19
|
214 static Mesh *read_mesh(Scene *scn, XMLElement *xml_mesh)
|
nuclear@19
|
215 {
|
nuclear@19
|
216 Mesh *mesh = new Mesh;
|
nuclear@75
|
217 mesh->name = get_name(xml_mesh, scn->get_mesh_count(), "mesh");
|
nuclear@19
|
218
|
nuclear@19
|
219 XMLElement *elem;
|
nuclear@19
|
220 if((elem = xml_mesh->FirstChildElement("material"))) {
|
nuclear@19
|
221 int idx;
|
nuclear@19
|
222 if(elem->QueryIntAttribute("int", &idx) == XML_NO_ERROR) {
|
nuclear@19
|
223 mesh->material = scn->get_material(idx);
|
nuclear@19
|
224 } else {
|
nuclear@19
|
225 // try string
|
nuclear@19
|
226 const char *mtlstr = elem->Attribute("string");
|
nuclear@19
|
227 if(mtlstr) {
|
nuclear@19
|
228 mesh->material = scn->get_material(mtlstr);
|
nuclear@19
|
229 }
|
nuclear@19
|
230 }
|
nuclear@19
|
231 }
|
nuclear@19
|
232
|
nuclear@19
|
233 /* reading mesh data from XML is not supported, only MESH_FILE can be used to
|
nuclear@19
|
234 * specify an external mesh file to be loaded
|
nuclear@19
|
235 */
|
nuclear@19
|
236
|
nuclear@19
|
237 if((elem = xml_mesh->FirstChildElement("file"))) {
|
nuclear@19
|
238 const char *fname = elem->Attribute("string");
|
nuclear@19
|
239 if(fname) {
|
nuclear@74
|
240 char *path = (char*)fname;
|
nuclear@74
|
241 if(scn->goat->search_path) {
|
nuclear@74
|
242 path = (char*)alloca(strlen(fname) + strlen(scn->goat->search_path) + 2);
|
nuclear@74
|
243 sprintf(path, "%s/%s", scn->goat->search_path, fname);
|
nuclear@74
|
244 }
|
nuclear@74
|
245 if(!mesh->load(path)) {
|
nuclear@19
|
246 delete mesh;
|
nuclear@19
|
247 return 0;
|
nuclear@19
|
248 }
|
nuclear@19
|
249 }
|
nuclear@19
|
250 }
|
nuclear@19
|
251
|
nuclear@19
|
252 return mesh;
|
nuclear@19
|
253 }
|
nuclear@19
|
254
|
nuclear@75
|
255 static Node *read_node(Scene *scn, XMLElement *xml_node, std::map<Node*, std::string> &linkmap)
|
nuclear@75
|
256 {
|
nuclear@75
|
257 Node *node = new Node;
|
nuclear@75
|
258 node->set_name(get_name(xml_node, scn->get_node_count(), "node").c_str());
|
nuclear@75
|
259
|
nuclear@75
|
260 XMLElement *elem;
|
nuclear@75
|
261 if((elem = xml_node->FirstChildElement("parent"))) {
|
nuclear@75
|
262 const char *pname = elem->Attribute("string");
|
nuclear@75
|
263 if(pname) {
|
nuclear@75
|
264 linkmap[node] = pname;
|
nuclear@75
|
265 }
|
nuclear@75
|
266 }
|
nuclear@75
|
267
|
nuclear@75
|
268 if((elem = xml_node->FirstChildElement("mesh"))) {
|
nuclear@75
|
269 Mesh *mesh = scn->get_mesh(elem->Attribute("string"));
|
nuclear@75
|
270 if(mesh) {
|
nuclear@75
|
271 node->set_object(mesh);
|
nuclear@75
|
272 }
|
nuclear@75
|
273 } else if((elem = xml_node->FirstChildElement("light"))) {
|
nuclear@75
|
274 Light *lt = scn->get_light(elem->Attribute("string"));
|
nuclear@75
|
275 if(lt) {
|
nuclear@75
|
276 node->set_object(lt);
|
nuclear@75
|
277 }
|
nuclear@75
|
278 } else if((elem = xml_node->FirstChildElement("camera"))) {
|
nuclear@75
|
279 Camera *cam = scn->get_camera(elem->Attribute("string"));
|
nuclear@75
|
280 if(cam) {
|
nuclear@75
|
281 node->set_object(cam);
|
nuclear@75
|
282 }
|
nuclear@75
|
283 }
|
nuclear@75
|
284
|
nuclear@75
|
285 float vec[4];
|
nuclear@75
|
286 if((elem = xml_node->FirstChildElement("pos"))) {
|
nuclear@75
|
287 const char *val = elem->Attribute("float3");
|
nuclear@75
|
288 if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
|
nuclear@75
|
289 node->set_position(Vector3(val[0], val[1], val[2]));
|
nuclear@75
|
290 } else {
|
nuclear@75
|
291 logmsg(LOG_ERROR, "node %s: invalid position tag\n", node->get_name());
|
nuclear@75
|
292 }
|
nuclear@75
|
293 }
|
nuclear@75
|
294 if((elem = xml_node->FirstChildElement("rot"))) {
|
nuclear@75
|
295 const char *val = elem->Attribute("float4");
|
nuclear@75
|
296 if(val && sscanf(val, "%f %f %f %f", vec, vec + 1, vec + 2, vec + 3) == 4) {
|
nuclear@75
|
297 node->set_rotation(Quaternion(vec[3], Vector3(vec[0], vec[1], vec[2])));
|
nuclear@75
|
298 } else {
|
nuclear@75
|
299 logmsg(LOG_ERROR, "node %s: invalid rotation tag\n", node->get_name());
|
nuclear@75
|
300 }
|
nuclear@75
|
301 }
|
nuclear@75
|
302 if((elem = xml_node->FirstChildElement("scale"))) {
|
nuclear@75
|
303 const char *val = elem->Attribute("float3");
|
nuclear@75
|
304 if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
|
nuclear@75
|
305 node->set_scaling(Vector3(vec[0], vec[1], vec[2]));
|
nuclear@75
|
306 } else {
|
nuclear@75
|
307 logmsg(LOG_ERROR, "node %s: invalid scaling tag\n", node->get_name());
|
nuclear@75
|
308 }
|
nuclear@75
|
309 }
|
nuclear@75
|
310 if((elem = xml_node->FirstChildElement("pivot"))) {
|
nuclear@75
|
311 const char *val = elem->Attribute("float3");
|
nuclear@75
|
312 if(val && sscanf(val, "%f %f %f", vec, vec + 1, vec + 2) == 3) {
|
nuclear@75
|
313 node->set_pivot(Vector3(vec[0], vec[1], vec[2]));
|
nuclear@75
|
314 } else {
|
nuclear@75
|
315 logmsg(LOG_ERROR, "node %s: invalid pivot tag\n", node->get_name());
|
nuclear@75
|
316 }
|
nuclear@75
|
317 }
|
nuclear@75
|
318
|
nuclear@75
|
319 return node;
|
nuclear@75
|
320 }
|
nuclear@75
|
321
|
nuclear@75
|
322 static std::string get_name(XMLElement *node, int idx, const char *def_prefix)
|
nuclear@19
|
323 {
|
nuclear@19
|
324 char buf[64];
|
nuclear@19
|
325 const char *name = 0;
|
nuclear@19
|
326
|
nuclear@19
|
327 XMLElement *elem;
|
nuclear@19
|
328 if((elem = node->FirstChildElement("name"))) {
|
nuclear@19
|
329 name = elem->Attribute("string");
|
nuclear@19
|
330 }
|
nuclear@19
|
331
|
nuclear@19
|
332 if(!name) {
|
nuclear@75
|
333 sprintf(buf, "%s%04d", def_prefix, idx);
|
nuclear@19
|
334 name = buf;
|
nuclear@19
|
335 }
|
nuclear@19
|
336
|
nuclear@19
|
337 return std::string(name);
|
nuclear@19
|
338 }
|