1 | /* |
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2017, assimp team |
6 | |
7 | All rights reserved. |
8 | |
9 | Redistribution and use of this software in source and binary forms, |
10 | with or without modification, are permitted provided that the |
11 | following conditions are met: |
12 | |
13 | * Redistributions of source code must retain the above |
14 | copyright notice, this list of conditions and the |
15 | following disclaimer. |
16 | |
17 | * Redistributions in binary form must reproduce the above |
18 | copyright notice, this list of conditions and the |
19 | following disclaimer in the documentation and/or other |
20 | materials provided with the distribution. |
21 | |
22 | * Neither the name of the assimp team, nor the names of its |
23 | contributors may be used to endorse or promote products |
24 | derived from this software without specific prior |
25 | written permission of the assimp team. |
26 | |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | |
39 | ---------------------------------------------------------------------- |
40 | */ |
41 | |
42 | #include "OgreXmlSerializer.h" |
43 | #include "OgreBinarySerializer.h" |
44 | #include "OgreParsingUtils.h" |
45 | |
46 | #include "TinyFormatter.h" |
47 | #include <assimp/DefaultLogger.hpp> |
48 | #include <memory> |
49 | |
50 | #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER |
51 | |
52 | // Define as 1 to get verbose logging. |
53 | #define OGRE_XML_SERIALIZER_DEBUG 0 |
54 | |
55 | namespace Assimp |
56 | { |
57 | namespace Ogre |
58 | { |
59 | |
60 | AI_WONT_RETURN void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "" ) AI_WONT_RETURN_SUFFIX; |
61 | AI_WONT_RETURN void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error) |
62 | { |
63 | if (!error.empty()) |
64 | { |
65 | throw DeadlyImportError(error + " in node '" + std::string(reader->getNodeName()) + "' and attribute '" + name + "'" ); |
66 | } |
67 | else |
68 | { |
69 | throw DeadlyImportError("Attribute '" + name + "' does not exist in node '" + std::string(reader->getNodeName()) + "'" ); |
70 | } |
71 | } |
72 | |
73 | template<> |
74 | int32_t OgreXmlSerializer::ReadAttribute<int32_t>(const std::string &name) const |
75 | { |
76 | if (HasAttribute(name.c_str())) |
77 | { |
78 | return static_cast<int32_t>(m_reader->getAttributeValueAsInt(name.c_str())); |
79 | } |
80 | else |
81 | { |
82 | ThrowAttibuteError(m_reader, name); |
83 | return 0; |
84 | } |
85 | } |
86 | |
87 | template<> |
88 | uint32_t OgreXmlSerializer::ReadAttribute<uint32_t>(const std::string &name) const |
89 | { |
90 | if (HasAttribute(name.c_str())) |
91 | { |
92 | /** @note This is hackish. But we are never expecting unsigned values that go outside the |
93 | int32_t range. Just monitor for negative numbers and kill the import. */ |
94 | int32_t temp = ReadAttribute<int32_t>(name); |
95 | if (temp >= 0) |
96 | { |
97 | return static_cast<uint32_t>(temp); |
98 | } |
99 | else |
100 | { |
101 | ThrowAttibuteError(m_reader, name, "Found a negative number value where expecting a uint32_t value" ); |
102 | } |
103 | } |
104 | else |
105 | { |
106 | ThrowAttibuteError(m_reader, name); |
107 | } |
108 | return 0; |
109 | } |
110 | |
111 | template<> |
112 | uint16_t OgreXmlSerializer::ReadAttribute<uint16_t>(const std::string &name) const |
113 | { |
114 | if (HasAttribute(name.c_str())) |
115 | { |
116 | return static_cast<uint16_t>(ReadAttribute<uint32_t>(name)); |
117 | } |
118 | else |
119 | { |
120 | ThrowAttibuteError(m_reader, name); |
121 | } |
122 | return 0; |
123 | } |
124 | |
125 | template<> |
126 | float OgreXmlSerializer::ReadAttribute<float>(const std::string &name) const |
127 | { |
128 | if (HasAttribute(name.c_str())) |
129 | { |
130 | return m_reader->getAttributeValueAsFloat(name.c_str()); |
131 | } |
132 | else |
133 | { |
134 | ThrowAttibuteError(m_reader, name); |
135 | return 0; |
136 | } |
137 | } |
138 | |
139 | template<> |
140 | std::string OgreXmlSerializer::ReadAttribute<std::string>(const std::string &name) const |
141 | { |
142 | const char* value = m_reader->getAttributeValue(name.c_str()); |
143 | if (value) |
144 | { |
145 | return std::string(value); |
146 | } |
147 | else |
148 | { |
149 | ThrowAttibuteError(m_reader, name); |
150 | return "" ; |
151 | } |
152 | } |
153 | |
154 | template<> |
155 | bool OgreXmlSerializer::ReadAttribute<bool>(const std::string &name) const |
156 | { |
157 | std::string value = Ogre::ToLower(ReadAttribute<std::string>(name)); |
158 | if (ASSIMP_stricmp(value, "true" ) == 0) |
159 | { |
160 | return true; |
161 | } |
162 | else if (ASSIMP_stricmp(value, "false" ) == 0) |
163 | { |
164 | return false; |
165 | } |
166 | else |
167 | { |
168 | ThrowAttibuteError(m_reader, name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'" ); |
169 | return false; |
170 | } |
171 | } |
172 | |
173 | bool OgreXmlSerializer::HasAttribute(const std::string &name) const |
174 | { |
175 | return (m_reader->getAttributeValue(name.c_str()) != 0); |
176 | } |
177 | |
178 | std::string &OgreXmlSerializer::NextNode() |
179 | { |
180 | do |
181 | { |
182 | if (!m_reader->read()) |
183 | { |
184 | m_currentNodeName = "" ; |
185 | return m_currentNodeName; |
186 | } |
187 | } |
188 | while(m_reader->getNodeType() != irr::io::EXN_ELEMENT); |
189 | |
190 | CurrentNodeName(true); |
191 | #if (OGRE_XML_SERIALIZER_DEBUG == 1) |
192 | DefaultLogger::get()->debug("<" + m_currentNodeName + ">" ); |
193 | #endif |
194 | return m_currentNodeName; |
195 | } |
196 | |
197 | bool OgreXmlSerializer::CurrentNodeNameEquals(const std::string &name) const |
198 | { |
199 | return (ASSIMP_stricmp(m_currentNodeName, name) == 0); |
200 | } |
201 | |
202 | std::string OgreXmlSerializer::CurrentNodeName(bool forceRead) |
203 | { |
204 | if (forceRead) |
205 | m_currentNodeName = std::string(m_reader->getNodeName()); |
206 | return m_currentNodeName; |
207 | } |
208 | |
209 | std::string &OgreXmlSerializer::SkipCurrentNode() |
210 | { |
211 | #if (OGRE_XML_SERIALIZER_DEBUG == 1) |
212 | DefaultLogger::get()->debug("Skipping node <" + m_currentNodeName + ">" ); |
213 | #endif |
214 | |
215 | for(;;) |
216 | { |
217 | if (!m_reader->read()) |
218 | { |
219 | m_currentNodeName = "" ; |
220 | return m_currentNodeName; |
221 | } |
222 | if (m_reader->getNodeType() != irr::io::EXN_ELEMENT_END) |
223 | continue; |
224 | else if (std::string(m_reader->getNodeName()) == m_currentNodeName) |
225 | break; |
226 | } |
227 | return NextNode(); |
228 | } |
229 | |
230 | // Mesh XML constants |
231 | |
232 | // <mesh> |
233 | const std::string nnMesh = "mesh" ; |
234 | const std::string nnSharedGeometry = "sharedgeometry" ; |
235 | const std::string nnSubMeshes = "submeshes" ; |
236 | const std::string nnSubMesh = "submesh" ; |
237 | const std::string nnSubMeshNames = "submeshnames" ; |
238 | const std::string nnSkeletonLink = "skeletonlink" ; |
239 | const std::string nnLOD = "levelofdetail" ; |
240 | const std::string nnExtremes = "extremes" ; |
241 | const std::string nnPoses = "poses" ; |
242 | const std::string nnAnimations = "animations" ; |
243 | |
244 | // <submesh> |
245 | const std::string nnFaces = "faces" ; |
246 | const std::string nnFace = "face" ; |
247 | const std::string nnGeometry = "geometry" ; |
248 | const std::string nnTextures = "textures" ; |
249 | |
250 | // <mesh/submesh> |
251 | const std::string nnBoneAssignments = "boneassignments" ; |
252 | |
253 | // <sharedgeometry/geometry> |
254 | const std::string nnVertexBuffer = "vertexbuffer" ; |
255 | |
256 | // <vertexbuffer> |
257 | const std::string nnVertex = "vertex" ; |
258 | const std::string nnPosition = "position" ; |
259 | const std::string nnNormal = "normal" ; |
260 | const std::string nnTangent = "tangent" ; |
261 | const std::string nnBinormal = "binormal" ; |
262 | const std::string nnTexCoord = "texcoord" ; |
263 | const std::string nnColorDiffuse = "colour_diffuse" ; |
264 | const std::string nnColorSpecular = "colour_specular" ; |
265 | |
266 | // <boneassignments> |
267 | const std::string nnVertexBoneAssignment = "vertexboneassignment" ; |
268 | |
269 | // Skeleton XML constants |
270 | |
271 | // <skeleton> |
272 | const std::string nnSkeleton = "skeleton" ; |
273 | const std::string nnBones = "bones" ; |
274 | const std::string nnBoneHierarchy = "bonehierarchy" ; |
275 | const std::string nnAnimationLinks = "animationlinks" ; |
276 | |
277 | // <bones> |
278 | const std::string nnBone = "bone" ; |
279 | const std::string nnRotation = "rotation" ; |
280 | const std::string nnAxis = "axis" ; |
281 | const std::string nnScale = "scale" ; |
282 | |
283 | // <bonehierarchy> |
284 | const std::string nnBoneParent = "boneparent" ; |
285 | |
286 | // <animations> |
287 | const std::string nnAnimation = "animation" ; |
288 | const std::string nnTracks = "tracks" ; |
289 | |
290 | // <tracks> |
291 | const std::string nnTrack = "track" ; |
292 | const std::string nnKeyFrames = "keyframes" ; |
293 | const std::string nnKeyFrame = "keyframe" ; |
294 | const std::string nnTranslate = "translate" ; |
295 | const std::string nnRotate = "rotate" ; |
296 | |
297 | // Common XML constants |
298 | |
299 | const std::string anX = "x" ; |
300 | const std::string anY = "y" ; |
301 | const std::string anZ = "z" ; |
302 | |
303 | // Mesh |
304 | |
305 | MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader) |
306 | { |
307 | OgreXmlSerializer serializer(reader); |
308 | |
309 | MeshXml *mesh = new MeshXml(); |
310 | serializer.ReadMesh(mesh); |
311 | return mesh; |
312 | } |
313 | |
314 | void OgreXmlSerializer::ReadMesh(MeshXml *mesh) |
315 | { |
316 | if (NextNode() != nnMesh) { |
317 | throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting <mesh>" ); |
318 | } |
319 | |
320 | DefaultLogger::get()->debug("Reading Mesh" ); |
321 | |
322 | NextNode(); |
323 | |
324 | // Root level nodes |
325 | while(m_currentNodeName == nnSharedGeometry || |
326 | m_currentNodeName == nnSubMeshes || |
327 | m_currentNodeName == nnSkeletonLink || |
328 | m_currentNodeName == nnBoneAssignments || |
329 | m_currentNodeName == nnLOD || |
330 | m_currentNodeName == nnSubMeshNames || |
331 | m_currentNodeName == nnExtremes || |
332 | m_currentNodeName == nnPoses || |
333 | m_currentNodeName == nnAnimations) |
334 | { |
335 | if (m_currentNodeName == nnSharedGeometry) |
336 | { |
337 | mesh->sharedVertexData = new VertexDataXml(); |
338 | ReadGeometry(mesh->sharedVertexData); |
339 | } |
340 | else if (m_currentNodeName == nnSubMeshes) |
341 | { |
342 | NextNode(); |
343 | while(m_currentNodeName == nnSubMesh) { |
344 | ReadSubMesh(mesh); |
345 | } |
346 | } |
347 | else if (m_currentNodeName == nnBoneAssignments) |
348 | { |
349 | ReadBoneAssignments(mesh->sharedVertexData); |
350 | } |
351 | else if (m_currentNodeName == nnSkeletonLink) |
352 | { |
353 | mesh->skeletonRef = ReadAttribute<std::string>("name" ); |
354 | DefaultLogger::get()->debug("Read skeleton link " + mesh->skeletonRef); |
355 | NextNode(); |
356 | } |
357 | // Assimp incompatible/ignored nodes |
358 | else |
359 | SkipCurrentNode(); |
360 | } |
361 | } |
362 | |
363 | void OgreXmlSerializer::ReadGeometry(VertexDataXml *dest) |
364 | { |
365 | dest->count = ReadAttribute<uint32_t>("vertexcount" ); |
366 | DefaultLogger::get()->debug(Formatter::format() << " - Reading geometry of " << dest->count << " vertices" ); |
367 | |
368 | NextNode(); |
369 | while(m_currentNodeName == nnVertexBuffer) { |
370 | ReadGeometryVertexBuffer(dest); |
371 | } |
372 | } |
373 | |
374 | void OgreXmlSerializer::ReadGeometryVertexBuffer(VertexDataXml *dest) |
375 | { |
376 | bool positions = (HasAttribute("positions" ) && ReadAttribute<bool>("positions" )); |
377 | bool normals = (HasAttribute("normals" ) && ReadAttribute<bool>("normals" )); |
378 | bool tangents = (HasAttribute("tangents" ) && ReadAttribute<bool>("tangents" )); |
379 | uint32_t uvs = (HasAttribute("texture_coords" ) ? ReadAttribute<uint32_t>("texture_coords" ) : 0); |
380 | |
381 | // Not having positions is a error only if a previous vertex buffer did not have them. |
382 | if (!positions && !dest->HasPositions()) { |
383 | throw DeadlyImportError("Vertex buffer does not contain positions!" ); |
384 | } |
385 | |
386 | if (positions) |
387 | { |
388 | DefaultLogger::get()->debug(" - Contains positions" ); |
389 | dest->positions.reserve(dest->count); |
390 | } |
391 | if (normals) |
392 | { |
393 | DefaultLogger::get()->debug(" - Contains normals" ); |
394 | dest->normals.reserve(dest->count); |
395 | } |
396 | if (tangents) |
397 | { |
398 | DefaultLogger::get()->debug(" - Contains tangents" ); |
399 | dest->tangents.reserve(dest->count); |
400 | } |
401 | if (uvs > 0) |
402 | { |
403 | DefaultLogger::get()->debug(Formatter::format() << " - Contains " << uvs << " texture coords" ); |
404 | dest->uvs.resize(uvs); |
405 | for(size_t i=0, len=dest->uvs.size(); i<len; ++i) { |
406 | dest->uvs[i].reserve(dest->count); |
407 | } |
408 | } |
409 | |
410 | bool warnBinormal = true; |
411 | bool warnColorDiffuse = true; |
412 | bool warnColorSpecular = true; |
413 | |
414 | NextNode(); |
415 | |
416 | while(m_currentNodeName == nnVertex || |
417 | m_currentNodeName == nnPosition || |
418 | m_currentNodeName == nnNormal || |
419 | m_currentNodeName == nnTangent || |
420 | m_currentNodeName == nnBinormal || |
421 | m_currentNodeName == nnTexCoord || |
422 | m_currentNodeName == nnColorDiffuse || |
423 | m_currentNodeName == nnColorSpecular) |
424 | { |
425 | if (m_currentNodeName == nnVertex) { |
426 | NextNode(); |
427 | } |
428 | |
429 | /// @todo Implement nnBinormal, nnColorDiffuse and nnColorSpecular |
430 | |
431 | if (positions && m_currentNodeName == nnPosition) |
432 | { |
433 | aiVector3D pos; |
434 | pos.x = ReadAttribute<float>(anX); |
435 | pos.y = ReadAttribute<float>(anY); |
436 | pos.z = ReadAttribute<float>(anZ); |
437 | dest->positions.push_back(pos); |
438 | } |
439 | else if (normals && m_currentNodeName == nnNormal) |
440 | { |
441 | aiVector3D normal; |
442 | normal.x = ReadAttribute<float>(anX); |
443 | normal.y = ReadAttribute<float>(anY); |
444 | normal.z = ReadAttribute<float>(anZ); |
445 | dest->normals.push_back(normal); |
446 | } |
447 | else if (tangents && m_currentNodeName == nnTangent) |
448 | { |
449 | aiVector3D tangent; |
450 | tangent.x = ReadAttribute<float>(anX); |
451 | tangent.y = ReadAttribute<float>(anY); |
452 | tangent.z = ReadAttribute<float>(anZ); |
453 | dest->tangents.push_back(tangent); |
454 | } |
455 | else if (uvs > 0 && m_currentNodeName == nnTexCoord) |
456 | { |
457 | for(auto &uvs : dest->uvs) |
458 | { |
459 | if (m_currentNodeName != nnTexCoord) { |
460 | throw DeadlyImportError("Vertex buffer declared more UVs than can be found in a vertex" ); |
461 | } |
462 | |
463 | aiVector3D uv; |
464 | uv.x = ReadAttribute<float>("u" ); |
465 | uv.y = (ReadAttribute<float>("v" ) * -1) + 1; // Flip UV from Ogre to Assimp form |
466 | uvs.push_back(uv); |
467 | |
468 | NextNode(); |
469 | } |
470 | // Continue main loop as above already read next node |
471 | continue; |
472 | } |
473 | else |
474 | { |
475 | /// @todo Remove this stuff once implemented. We only want to log warnings once per element. |
476 | bool warn = true; |
477 | if (m_currentNodeName == nnBinormal) |
478 | { |
479 | if (warnBinormal) |
480 | { |
481 | warnBinormal = false; |
482 | } |
483 | else |
484 | { |
485 | warn = false; |
486 | } |
487 | } |
488 | else if (m_currentNodeName == nnColorDiffuse) |
489 | { |
490 | if (warnColorDiffuse) |
491 | { |
492 | warnColorDiffuse = false; |
493 | } |
494 | else |
495 | { |
496 | warn = false; |
497 | } |
498 | } |
499 | else if (m_currentNodeName == nnColorSpecular) |
500 | { |
501 | if (warnColorSpecular) |
502 | { |
503 | warnColorSpecular = false; |
504 | } |
505 | else |
506 | { |
507 | warn = false; |
508 | } |
509 | } |
510 | if (warn) { |
511 | DefaultLogger::get()->warn("Vertex buffer attribute read not implemented for element: " + m_currentNodeName); |
512 | } |
513 | } |
514 | |
515 | // Advance |
516 | NextNode(); |
517 | } |
518 | |
519 | // Sanity checks |
520 | if (dest->positions.size() != dest->count) { |
521 | throw DeadlyImportError(Formatter::format() << "Read only " << dest->positions.size() << " positions when should have read " << dest->count); |
522 | } |
523 | if (normals && dest->normals.size() != dest->count) { |
524 | throw DeadlyImportError(Formatter::format() << "Read only " << dest->normals.size() << " normals when should have read " << dest->count); |
525 | } |
526 | if (tangents && dest->tangents.size() != dest->count) { |
527 | throw DeadlyImportError(Formatter::format() << "Read only " << dest->tangents.size() << " tangents when should have read " << dest->count); |
528 | } |
529 | for(unsigned int i=0; i<dest->uvs.size(); ++i) |
530 | { |
531 | if (dest->uvs[i].size() != dest->count) { |
532 | throw DeadlyImportError(Formatter::format() << "Read only " << dest->uvs[i].size() |
533 | << " uvs for uv index " << i << " when should have read " << dest->count); |
534 | } |
535 | } |
536 | } |
537 | |
538 | void OgreXmlSerializer::ReadSubMesh(MeshXml *mesh) |
539 | { |
540 | static const std::string anMaterial = "material" ; |
541 | static const std::string anUseSharedVertices = "usesharedvertices" ; |
542 | static const std::string anCount = "count" ; |
543 | static const std::string anV1 = "v1" ; |
544 | static const std::string anV2 = "v2" ; |
545 | static const std::string anV3 = "v3" ; |
546 | static const std::string anV4 = "v4" ; |
547 | |
548 | SubMeshXml* submesh = new SubMeshXml(); |
549 | |
550 | if (HasAttribute(anMaterial)) { |
551 | submesh->materialRef = ReadAttribute<std::string>(anMaterial); |
552 | } |
553 | if (HasAttribute(anUseSharedVertices)) { |
554 | submesh->usesSharedVertexData = ReadAttribute<bool>(anUseSharedVertices); |
555 | } |
556 | |
557 | DefaultLogger::get()->debug(Formatter::format() << "Reading SubMesh " << mesh->subMeshes.size()); |
558 | DefaultLogger::get()->debug(Formatter::format() << " - Material: '" << submesh->materialRef << "'" ); |
559 | DefaultLogger::get()->debug(Formatter::format() << " - Uses shared geometry: " << (submesh->usesSharedVertexData ? "true" : "false" )); |
560 | |
561 | // TODO: maybe we have always just 1 faces and 1 geometry and always in this order. this loop will only work correct, when the order |
562 | // of faces and geometry changed, and not if we have more than one of one |
563 | /// @todo Fix above comment with better read logic below |
564 | |
565 | bool quadWarned = false; |
566 | |
567 | NextNode(); |
568 | while(m_currentNodeName == nnFaces || |
569 | m_currentNodeName == nnGeometry || |
570 | m_currentNodeName == nnTextures || |
571 | m_currentNodeName == nnBoneAssignments) |
572 | { |
573 | if (m_currentNodeName == nnFaces) |
574 | { |
575 | submesh->indexData->faceCount = ReadAttribute<uint32_t>(anCount); |
576 | submesh->indexData->faces.reserve(submesh->indexData->faceCount); |
577 | |
578 | NextNode(); |
579 | while(m_currentNodeName == nnFace) |
580 | { |
581 | aiFace face; |
582 | face.mNumIndices = 3; |
583 | face.mIndices = new unsigned int[3]; |
584 | face.mIndices[0] = ReadAttribute<uint32_t>(anV1); |
585 | face.mIndices[1] = ReadAttribute<uint32_t>(anV2); |
586 | face.mIndices[2] = ReadAttribute<uint32_t>(anV3); |
587 | |
588 | /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it) |
589 | if (!quadWarned && HasAttribute(anV4)) { |
590 | DefaultLogger::get()->warn("Submesh <face> has quads with <v4>, only triangles are supported at the moment!" ); |
591 | quadWarned = true; |
592 | } |
593 | |
594 | submesh->indexData->faces.push_back(face); |
595 | |
596 | // Advance |
597 | NextNode(); |
598 | } |
599 | |
600 | if (submesh->indexData->faces.size() == submesh->indexData->faceCount) |
601 | { |
602 | DefaultLogger::get()->debug(Formatter::format() << " - Faces " << submesh->indexData->faceCount); |
603 | } |
604 | else |
605 | { |
606 | throw DeadlyImportError(Formatter::format() << "Read only " << submesh->indexData->faces.size() << " faces when should have read " << submesh->indexData->faceCount); |
607 | } |
608 | } |
609 | else if (m_currentNodeName == nnGeometry) |
610 | { |
611 | if (submesh->usesSharedVertexData) { |
612 | throw DeadlyImportError("Found <geometry> in <submesh> when use shared geometry is true. Invalid mesh file." ); |
613 | } |
614 | |
615 | submesh->vertexData = new VertexDataXml(); |
616 | ReadGeometry(submesh->vertexData); |
617 | } |
618 | else if (m_currentNodeName == nnBoneAssignments) |
619 | { |
620 | ReadBoneAssignments(submesh->vertexData); |
621 | } |
622 | // Assimp incompatible/ignored nodes |
623 | else |
624 | SkipCurrentNode(); |
625 | } |
626 | |
627 | submesh->index = static_cast<unsigned int>(mesh->subMeshes.size()); |
628 | mesh->subMeshes.push_back(submesh); |
629 | } |
630 | |
631 | void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest) |
632 | { |
633 | if (!dest) { |
634 | throw DeadlyImportError("Cannot read bone assignments, vertex data is null." ); |
635 | } |
636 | |
637 | static const std::string anVertexIndex = "vertexindex" ; |
638 | static const std::string anBoneIndex = "boneindex" ; |
639 | static const std::string anWeight = "weight" ; |
640 | |
641 | std::set<uint32_t> influencedVertices; |
642 | |
643 | NextNode(); |
644 | while(m_currentNodeName == nnVertexBoneAssignment) |
645 | { |
646 | VertexBoneAssignment ba; |
647 | ba.vertexIndex = ReadAttribute<uint32_t>(anVertexIndex); |
648 | ba.boneIndex = ReadAttribute<uint16_t>(anBoneIndex); |
649 | ba.weight = ReadAttribute<float>(anWeight); |
650 | |
651 | dest->boneAssignments.push_back(ba); |
652 | influencedVertices.insert(ba.vertexIndex); |
653 | |
654 | NextNode(); |
655 | } |
656 | |
657 | /** Normalize bone weights. |
658 | Some exporters won't care if the sum of all bone weights |
659 | for a single vertex equals 1 or not, so validate here. */ |
660 | const float epsilon = 0.05f; |
661 | for (const uint32_t vertexIndex : influencedVertices) |
662 | { |
663 | float sum = 0.0f; |
664 | for (VertexBoneAssignmentList::const_iterator baIter=dest->boneAssignments.begin(), baEnd=dest->boneAssignments.end(); baIter != baEnd; ++baIter) |
665 | { |
666 | if (baIter->vertexIndex == vertexIndex) |
667 | sum += baIter->weight; |
668 | } |
669 | if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) |
670 | { |
671 | for (auto &boneAssign : dest->boneAssignments) |
672 | { |
673 | if (boneAssign.vertexIndex == vertexIndex) |
674 | boneAssign.weight /= sum; |
675 | } |
676 | } |
677 | } |
678 | |
679 | DefaultLogger::get()->debug(Formatter::format() << " - " << dest->boneAssignments.size() << " bone assignments" ); |
680 | } |
681 | |
682 | // Skeleton |
683 | |
684 | bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) |
685 | { |
686 | if (!mesh || mesh->skeletonRef.empty()) |
687 | return false; |
688 | |
689 | // Highly unusual to see in read world cases but support |
690 | // XML mesh referencing a binary skeleton file. |
691 | if (EndsWith(mesh->skeletonRef, ".skeleton" , false)) |
692 | { |
693 | if (OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh)) |
694 | return true; |
695 | |
696 | /** Last fallback if .skeleton failed to be read. Try reading from |
697 | .skeleton.xml even if the XML file referenced a binary skeleton. |
698 | @note This logic was in the previous version and I don't want to break |
699 | old code that might depends on it. */ |
700 | mesh->skeletonRef = mesh->skeletonRef + ".xml" ; |
701 | } |
702 | |
703 | XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); |
704 | if (!reader.get()) |
705 | return false; |
706 | |
707 | Skeleton *skeleton = new Skeleton(); |
708 | OgreXmlSerializer serializer(reader.get()); |
709 | serializer.ReadSkeleton(skeleton); |
710 | mesh->skeleton = skeleton; |
711 | return true; |
712 | } |
713 | |
714 | bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) |
715 | { |
716 | if (!mesh || mesh->skeletonRef.empty()) |
717 | return false; |
718 | |
719 | XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); |
720 | if (!reader.get()) |
721 | return false; |
722 | |
723 | Skeleton *skeleton = new Skeleton(); |
724 | OgreXmlSerializer serializer(reader.get()); |
725 | serializer.ReadSkeleton(skeleton); |
726 | mesh->skeleton = skeleton; |
727 | return true; |
728 | } |
729 | |
730 | XmlReaderPtr OgreXmlSerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename) |
731 | { |
732 | if (!EndsWith(filename, ".skeleton.xml" , false)) |
733 | { |
734 | DefaultLogger::get()->error("Imported Mesh is referencing to unsupported '" + filename + "' skeleton file." ); |
735 | return XmlReaderPtr(); |
736 | } |
737 | |
738 | if (!pIOHandler->Exists(filename)) |
739 | { |
740 | DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "' that is referenced by imported Mesh." ); |
741 | return XmlReaderPtr(); |
742 | } |
743 | |
744 | std::unique_ptr<IOStream> file(pIOHandler->Open(filename)); |
745 | if (!file.get()) { |
746 | throw DeadlyImportError("Failed to open skeleton file " + filename); |
747 | } |
748 | |
749 | std::unique_ptr<CIrrXML_IOStreamReader> stream(new CIrrXML_IOStreamReader(file.get())); |
750 | XmlReaderPtr reader = XmlReaderPtr(irr::io::createIrrXMLReader(stream.get())); |
751 | if (!reader.get()) { |
752 | throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename); |
753 | } |
754 | return reader; |
755 | } |
756 | |
757 | void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton) |
758 | { |
759 | if (NextNode() != nnSkeleton) { |
760 | throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting <skeleton>" ); |
761 | } |
762 | |
763 | DefaultLogger::get()->debug("Reading Skeleton" ); |
764 | |
765 | // Optional blend mode from root node |
766 | if (HasAttribute("blendmode" )) { |
767 | skeleton->blendMode = (ToLower(ReadAttribute<std::string>("blendmode" )) == "cumulative" |
768 | ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE); |
769 | } |
770 | |
771 | NextNode(); |
772 | |
773 | // Root level nodes |
774 | while(m_currentNodeName == nnBones || |
775 | m_currentNodeName == nnBoneHierarchy || |
776 | m_currentNodeName == nnAnimations || |
777 | m_currentNodeName == nnAnimationLinks) |
778 | { |
779 | if (m_currentNodeName == nnBones) |
780 | ReadBones(skeleton); |
781 | else if (m_currentNodeName == nnBoneHierarchy) |
782 | ReadBoneHierarchy(skeleton); |
783 | else if (m_currentNodeName == nnAnimations) |
784 | ReadAnimations(skeleton); |
785 | else |
786 | SkipCurrentNode(); |
787 | } |
788 | } |
789 | |
790 | void OgreXmlSerializer::ReadAnimations(Skeleton *skeleton) |
791 | { |
792 | if (skeleton->bones.empty()) { |
793 | throw DeadlyImportError("Cannot read <animations> for a Skeleton without bones" ); |
794 | } |
795 | |
796 | DefaultLogger::get()->debug(" - Animations" ); |
797 | |
798 | NextNode(); |
799 | while(m_currentNodeName == nnAnimation) |
800 | { |
801 | Animation *anim = new Animation(skeleton); |
802 | anim->name = ReadAttribute<std::string>("name" ); |
803 | anim->length = ReadAttribute<float>("length" ); |
804 | |
805 | if (NextNode() != nnTracks) { |
806 | throw DeadlyImportError(Formatter::format() << "No <tracks> found in <animation> " << anim->name); |
807 | } |
808 | |
809 | ReadAnimationTracks(anim); |
810 | skeleton->animations.push_back(anim); |
811 | |
812 | DefaultLogger::get()->debug(Formatter::format() << " " << anim->name << " (" << anim->length << " sec, " << anim->tracks.size() << " tracks)" ); |
813 | } |
814 | } |
815 | |
816 | void OgreXmlSerializer::ReadAnimationTracks(Animation *dest) |
817 | { |
818 | NextNode(); |
819 | while(m_currentNodeName == nnTrack) |
820 | { |
821 | VertexAnimationTrack track; |
822 | track.type = VertexAnimationTrack::VAT_TRANSFORM; |
823 | track.boneName = ReadAttribute<std::string>("bone" ); |
824 | |
825 | if (NextNode() != nnKeyFrames) { |
826 | throw DeadlyImportError(Formatter::format() << "No <keyframes> found in <track> " << dest->name); |
827 | } |
828 | |
829 | ReadAnimationKeyFrames(dest, &track); |
830 | |
831 | dest->tracks.push_back(track); |
832 | } |
833 | } |
834 | |
835 | void OgreXmlSerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest) |
836 | { |
837 | const aiVector3D zeroVec(0.f, 0.f, 0.f); |
838 | |
839 | NextNode(); |
840 | while(m_currentNodeName == nnKeyFrame) |
841 | { |
842 | TransformKeyFrame keyframe; |
843 | keyframe.timePos = ReadAttribute<float>("time" ); |
844 | |
845 | NextNode(); |
846 | while(m_currentNodeName == nnTranslate || m_currentNodeName == nnRotate || m_currentNodeName == nnScale) |
847 | { |
848 | if (m_currentNodeName == nnTranslate) |
849 | { |
850 | keyframe.position.x = ReadAttribute<float>(anX); |
851 | keyframe.position.y = ReadAttribute<float>(anY); |
852 | keyframe.position.z = ReadAttribute<float>(anZ); |
853 | } |
854 | else if (m_currentNodeName == nnRotate) |
855 | { |
856 | float angle = ReadAttribute<float>("angle" ); |
857 | |
858 | if (NextNode() != nnAxis) { |
859 | throw DeadlyImportError("No axis specified for keyframe rotation in animation " + anim->name); |
860 | } |
861 | |
862 | aiVector3D axis; |
863 | axis.x = ReadAttribute<float>(anX); |
864 | axis.y = ReadAttribute<float>(anY); |
865 | axis.z = ReadAttribute<float>(anZ); |
866 | if (axis.Equal(zeroVec)) |
867 | { |
868 | axis.x = 1.0f; |
869 | if (angle != 0) { |
870 | DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation: " + anim->name); |
871 | } |
872 | } |
873 | keyframe.rotation = aiQuaternion(axis, angle); |
874 | } |
875 | else if (m_currentNodeName == nnScale) |
876 | { |
877 | keyframe.scale.x = ReadAttribute<float>(anX); |
878 | keyframe.scale.y = ReadAttribute<float>(anY); |
879 | keyframe.scale.z = ReadAttribute<float>(anZ); |
880 | } |
881 | |
882 | NextNode(); |
883 | } |
884 | |
885 | dest->transformKeyFrames.push_back(keyframe); |
886 | } |
887 | } |
888 | |
889 | void OgreXmlSerializer::ReadBoneHierarchy(Skeleton *skeleton) |
890 | { |
891 | if (skeleton->bones.empty()) { |
892 | throw DeadlyImportError("Cannot read <bonehierarchy> for a Skeleton without bones" ); |
893 | } |
894 | |
895 | while(NextNode() == nnBoneParent) |
896 | { |
897 | const std::string name = ReadAttribute<std::string>("bone" ); |
898 | const std::string parentName = ReadAttribute<std::string>("parent" ); |
899 | |
900 | Bone *bone = skeleton->BoneByName(name); |
901 | Bone *parent = skeleton->BoneByName(parentName); |
902 | |
903 | if (bone && parent) |
904 | parent->AddChild(bone); |
905 | else |
906 | throw DeadlyImportError("Failed to find bones for parenting: Child " + name + " for parent " + parentName); |
907 | } |
908 | |
909 | // Calculate bone matrices for root bones. Recursively calculates their children. |
910 | for (size_t i=0, len=skeleton->bones.size(); i<len; ++i) |
911 | { |
912 | Bone *bone = skeleton->bones[i]; |
913 | if (!bone->IsParented()) |
914 | bone->CalculateWorldMatrixAndDefaultPose(skeleton); |
915 | } |
916 | } |
917 | |
918 | bool BoneCompare(Bone *a, Bone *b) |
919 | { |
920 | return (a->id < b->id); |
921 | } |
922 | |
923 | void OgreXmlSerializer::ReadBones(Skeleton *skeleton) |
924 | { |
925 | DefaultLogger::get()->debug(" - Bones" ); |
926 | |
927 | NextNode(); |
928 | while(m_currentNodeName == nnBone) |
929 | { |
930 | Bone *bone = new Bone(); |
931 | bone->id = ReadAttribute<uint16_t>("id" ); |
932 | bone->name = ReadAttribute<std::string>("name" ); |
933 | |
934 | NextNode(); |
935 | while(m_currentNodeName == nnPosition || |
936 | m_currentNodeName == nnRotation || |
937 | m_currentNodeName == nnScale) |
938 | { |
939 | if (m_currentNodeName == nnPosition) |
940 | { |
941 | bone->position.x = ReadAttribute<float>(anX); |
942 | bone->position.y = ReadAttribute<float>(anY); |
943 | bone->position.z = ReadAttribute<float>(anZ); |
944 | } |
945 | else if (m_currentNodeName == nnRotation) |
946 | { |
947 | float angle = ReadAttribute<float>("angle" ); |
948 | |
949 | if (NextNode() != nnAxis) { |
950 | throw DeadlyImportError(Formatter::format() << "No axis specified for bone rotation in bone " << bone->id); |
951 | } |
952 | |
953 | aiVector3D axis; |
954 | axis.x = ReadAttribute<float>(anX); |
955 | axis.y = ReadAttribute<float>(anY); |
956 | axis.z = ReadAttribute<float>(anZ); |
957 | |
958 | bone->rotation = aiQuaternion(axis, angle); |
959 | } |
960 | else if (m_currentNodeName == nnScale) |
961 | { |
962 | /// @todo Implement taking scale into account in matrix/pose calculations! |
963 | if (HasAttribute("factor" )) |
964 | { |
965 | float factor = ReadAttribute<float>("factor" ); |
966 | bone->scale.Set(factor, factor, factor); |
967 | } |
968 | else |
969 | { |
970 | if (HasAttribute(anX)) |
971 | bone->scale.x = ReadAttribute<float>(anX); |
972 | if (HasAttribute(anY)) |
973 | bone->scale.y = ReadAttribute<float>(anY); |
974 | if (HasAttribute(anZ)) |
975 | bone->scale.z = ReadAttribute<float>(anZ); |
976 | } |
977 | } |
978 | |
979 | NextNode(); |
980 | } |
981 | |
982 | skeleton->bones.push_back(bone); |
983 | } |
984 | |
985 | // Order bones by Id |
986 | std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare); |
987 | |
988 | // Validate that bone indexes are not skipped. |
989 | /** @note Left this from original authors code, but not sure if this is strictly necessary |
990 | as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */ |
991 | for (size_t i=0, len=skeleton->bones.size(); i<len; ++i) |
992 | { |
993 | Bone *b = skeleton->bones[i]; |
994 | DefaultLogger::get()->debug(Formatter::format() << " " << b->id << " " << b->name); |
995 | |
996 | if (b->id != static_cast<uint16_t>(i)) { |
997 | throw DeadlyImportError(Formatter::format() << "Bone ids are not in sequence starting from 0. Missing index " << i); |
998 | } |
999 | } |
1000 | } |
1001 | |
1002 | } // Ogre |
1003 | } // Assimp |
1004 | |
1005 | #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER |
1006 | |