1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16 copyright notice, this list of conditions and the
17 following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20 copyright notice, this list of conditions and the
21 following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25 contributors may be used to endorse or promote products
26 derived from this software without specific prior
27 written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42
43/** @file SMDLoader.cpp
44 * @brief Implementation of the SMD importer class
45 */
46
47
48#ifndef ASSIMP_BUILD_NO_SMD_IMPORTER
49
50// internal headers
51#include "SMDLoader.h"
52#include "fast_atof.h"
53#include "SkeletonMeshBuilder.h"
54#include <assimp/Importer.hpp>
55#include <assimp/IOSystem.hpp>
56#include <assimp/scene.h>
57#include <assimp/DefaultLogger.hpp>
58#include <assimp/importerdesc.h>
59#include <memory>
60
61using namespace Assimp;
62
63static const aiImporterDesc desc = {
64 "Valve SMD Importer",
65 "",
66 "",
67 "",
68 aiImporterFlags_SupportTextFlavour,
69 0,
70 0,
71 0,
72 0,
73 "smd vta"
74};
75
76// ------------------------------------------------------------------------------------------------
77// Constructor to be privately used by Importer
78SMDImporter::SMDImporter()
79: configFrameID(),
80mBuffer(),
81pScene( nullptr ),
82iFileSize( 0 ),
83iSmallestFrame( -1 ),
84dLengthOfAnim( 0.0 ),
85bHasUVs(false ),
86iLineNumber(-1) {
87 // empty
88}
89
90// ------------------------------------------------------------------------------------------------
91// Destructor, private as well
92SMDImporter::~SMDImporter() {
93 // empty
94}
95
96// ------------------------------------------------------------------------------------------------
97// Returns whether the class can handle the format of the given file.
98bool SMDImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool) const
99{
100 // fixme: auto format detection
101 return SimpleExtensionCheck(pFile,"smd","vta");
102}
103
104// ------------------------------------------------------------------------------------------------
105// Get a list of all supported file extensions
106const aiImporterDesc* SMDImporter::GetInfo () const
107{
108 return &desc;
109}
110
111// ------------------------------------------------------------------------------------------------
112// Setup configuration properties
113void SMDImporter::SetupProperties(const Importer* pImp)
114{
115 // The
116 // AI_CONFIG_IMPORT_SMD_KEYFRAME option overrides the
117 // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
118 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_SMD_KEYFRAME,-1);
119 if(static_cast<unsigned int>(-1) == configFrameID) {
120 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
121 }
122}
123
124// ------------------------------------------------------------------------------------------------
125// Imports the given file into the given scene structure.
126void SMDImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
127{
128 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
129
130 // Check whether we can read from the file
131 if( file.get() == NULL) {
132 throw DeadlyImportError( "Failed to open SMD/VTA file " + pFile + ".");
133 }
134
135 iFileSize = (unsigned int)file->FileSize();
136
137 // Allocate storage and copy the contents of the file to a memory buffer
138 this->pScene = pScene;
139
140 mBuffer.resize( iFileSize + 1 );
141 TextFileToBuffer(file.get(), mBuffer );
142
143 iSmallestFrame = (1 << 31);
144 bHasUVs = true;
145 iLineNumber = 1;
146
147 // Reserve enough space for ... hm ... 10 textures
148 aszTextures.reserve(10);
149
150 // Reserve enough space for ... hm ... 1000 triangles
151 asTriangles.reserve(1000);
152
153 // Reserve enough space for ... hm ... 20 bones
154 asBones.reserve(20);
155
156
157 // parse the file ...
158 ParseFile();
159
160 // If there are no triangles it seems to be an animation SMD,
161 // containing only the animation skeleton.
162 if (asTriangles.empty())
163 {
164 if (asBones.empty())
165 {
166 throw DeadlyImportError("SMD: No triangles and no bones have "
167 "been found in the file. This file seems to be invalid.");
168 }
169
170 // Set the flag in the scene structure which indicates
171 // that there is nothing than an animation skeleton
172 pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
173 }
174
175 if (!asBones.empty())
176 {
177 // Check whether all bones have been initialized
178 for (std::vector<SMD::Bone>::const_iterator
179 i = asBones.begin();
180 i != asBones.end();++i)
181 {
182 if (!(*i).mName.length())
183 {
184 DefaultLogger::get()->warn("SMD: Not all bones have been initialized");
185 break;
186 }
187 }
188
189 // now fix invalid time values and make sure the animation starts at frame 0
190 FixTimeValues();
191
192 // compute absolute bone transformation matrices
193 // ComputeAbsoluteBoneTransformations();
194 }
195
196 if (!(pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE))
197 {
198 // create output meshes
199 CreateOutputMeshes();
200
201 // build an output material list
202 CreateOutputMaterials();
203 }
204
205 // build the output animation
206 CreateOutputAnimations();
207
208 // build output nodes (bones are added as empty dummy nodes)
209 CreateOutputNodes();
210
211 if (pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE)
212 {
213 SkeletonMeshBuilder skeleton(pScene);
214 }
215}
216// ------------------------------------------------------------------------------------------------
217// Write an error message with line number to the log file
218void SMDImporter::LogErrorNoThrow(const char* msg)
219{
220 char szTemp[1024];
221 ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,msg);
222 DefaultLogger::get()->error(szTemp);
223}
224
225// ------------------------------------------------------------------------------------------------
226// Write a warning with line number to the log file
227void SMDImporter::LogWarning(const char* msg)
228{
229 char szTemp[1024];
230 ai_assert(strlen(msg) < 1000);
231 ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,msg);
232 DefaultLogger::get()->warn(szTemp);
233}
234
235// ------------------------------------------------------------------------------------------------
236// Fix invalid time values in the file
237void SMDImporter::FixTimeValues()
238{
239 double dDelta = (double)iSmallestFrame;
240 double dMax = 0.0f;
241 for (std::vector<SMD::Bone>::iterator
242 iBone = asBones.begin();
243 iBone != asBones.end();++iBone)
244 {
245 for (std::vector<SMD::Bone::Animation::MatrixKey>::iterator
246 iKey = (*iBone).sAnim.asKeys.begin();
247 iKey != (*iBone).sAnim.asKeys.end();++iKey)
248 {
249 (*iKey).dTime -= dDelta;
250 dMax = std::max(dMax, (*iKey).dTime);
251 }
252 }
253 dLengthOfAnim = dMax;
254}
255
256// ------------------------------------------------------------------------------------------------
257// create output meshes
258void SMDImporter::CreateOutputMeshes()
259{
260 if (aszTextures.empty())
261 aszTextures.push_back(std::string());
262
263 // we need to sort all faces by their material index
264 // in opposition to other loaders we can be sure that each
265 // material is at least used once.
266 pScene->mNumMeshes = (unsigned int) aszTextures.size();
267 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
268
269 typedef std::vector<unsigned int> FaceList;
270 FaceList* aaiFaces = new FaceList[pScene->mNumMeshes];
271
272 // approximate the space that will be required
273 unsigned int iNum = (unsigned int)asTriangles.size() / pScene->mNumMeshes;
274 iNum += iNum >> 1;
275 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
276 aaiFaces[i].reserve(iNum);
277
278
279 // collect all faces
280 iNum = 0;
281 for (std::vector<SMD::Face>::const_iterator
282 iFace = asTriangles.begin();
283 iFace != asTriangles.end();++iFace,++iNum)
284 {
285 if (UINT_MAX == (*iFace).iTexture)aaiFaces[(*iFace).iTexture].push_back( 0 );
286 else if ((*iFace).iTexture >= aszTextures.size())
287 {
288 DefaultLogger::get()->error("[SMD/VTA] Material index overflow in face");
289 aaiFaces[(*iFace).iTexture].push_back((unsigned int)aszTextures.size()-1);
290 }
291 else aaiFaces[(*iFace).iTexture].push_back(iNum);
292 }
293
294 // now create the output meshes
295 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
296 {
297 aiMesh*& pcMesh = pScene->mMeshes[i] = new aiMesh();
298 ai_assert(!aaiFaces[i].empty()); // should not be empty ...
299
300 pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
301 pcMesh->mNumVertices = (unsigned int)aaiFaces[i].size()*3;
302 pcMesh->mNumFaces = (unsigned int)aaiFaces[i].size();
303 pcMesh->mMaterialIndex = i;
304
305 // storage for bones
306 typedef std::pair<unsigned int,float> TempWeightListEntry;
307 typedef std::vector< TempWeightListEntry > TempBoneWeightList;
308
309 TempBoneWeightList* aaiBones = new TempBoneWeightList[asBones.size()]();
310
311 // try to reserve enough memory without wasting too much
312 for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
313 {
314 aaiBones[iBone].reserve(pcMesh->mNumVertices/asBones.size());
315 }
316
317 // allocate storage
318 pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
319 aiVector3D* pcNormals = pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
320 aiVector3D* pcVerts = pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
321
322 aiVector3D* pcUVs = NULL;
323 if (bHasUVs)
324 {
325 pcUVs = pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
326 pcMesh->mNumUVComponents[0] = 2;
327 }
328
329 iNum = 0;
330 for (unsigned int iFace = 0; iFace < pcMesh->mNumFaces;++iFace)
331 {
332 pcMesh->mFaces[iFace].mIndices = new unsigned int[3];
333 pcMesh->mFaces[iFace].mNumIndices = 3;
334
335 // fill the vertices
336 unsigned int iSrcFace = aaiFaces[i][iFace];
337 SMD::Face& face = asTriangles[iSrcFace];
338
339 *pcVerts++ = face.avVertices[0].pos;
340 *pcVerts++ = face.avVertices[1].pos;
341 *pcVerts++ = face.avVertices[2].pos;
342
343 // fill the normals
344 *pcNormals++ = face.avVertices[0].nor;
345 *pcNormals++ = face.avVertices[1].nor;
346 *pcNormals++ = face.avVertices[2].nor;
347
348 // fill the texture coordinates
349 if (pcUVs)
350 {
351 *pcUVs++ = face.avVertices[0].uv;
352 *pcUVs++ = face.avVertices[1].uv;
353 *pcUVs++ = face.avVertices[2].uv;
354 }
355
356 for (unsigned int iVert = 0; iVert < 3;++iVert)
357 {
358 float fSum = 0.0f;
359 for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone)
360 {
361 TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
362
363 // FIX: The second check is here just to make sure we won't
364 // assign more than one weight to a single vertex index
365 if (pairval.first >= asBones.size() ||
366 pairval.first == face.avVertices[iVert].iParentNode)
367 {
368 DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
369 "The bone index will be ignored, the weight will be assigned "
370 "to the vertex' parent node");
371 continue;
372 }
373 aaiBones[pairval.first].push_back(TempWeightListEntry(iNum,pairval.second));
374 fSum += pairval.second;
375 }
376 // ******************************************************************
377 // If the sum of all vertex weights is not 1.0 we must assign
378 // the rest to the vertex' parent node. Well, at least the doc says
379 // we should ...
380 // FIX: We use 0.975 as limit, floating-point inaccuracies seem to
381 // be very strong in some SMD exporters. Furthermore it is possible
382 // that the parent of a vertex is 0xffffffff (if the corresponding
383 // entry in the file was unreadable)
384 // ******************************************************************
385 if (fSum < 0.975f && face.avVertices[iVert].iParentNode != UINT_MAX)
386 {
387 if (face.avVertices[iVert].iParentNode >= asBones.size())
388 {
389 DefaultLogger::get()->error("[SMD/VTA] Bone index overflow. "
390 "The index of the vertex parent bone is invalid. "
391 "The remaining weights will be normalized to 1.0");
392
393 if (fSum)
394 {
395 fSum = 1 / fSum;
396 for (unsigned int iBone = 0;iBone < face.avVertices[iVert].aiBoneLinks.size();++iBone)
397 {
398 TempWeightListEntry& pairval = face.avVertices[iVert].aiBoneLinks[iBone];
399 if (pairval.first >= asBones.size())continue;
400 aaiBones[pairval.first].back().second *= fSum;
401 }
402 }
403 }
404 else
405 {
406 aaiBones[face.avVertices[iVert].iParentNode].push_back(
407 TempWeightListEntry(iNum,1.0f-fSum));
408 }
409 }
410 pcMesh->mFaces[iFace].mIndices[iVert] = iNum++;
411 }
412 }
413
414 // now build all bones of the mesh
415 iNum = 0;
416 for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
417 if (!aaiBones[iBone].empty())++iNum;
418
419 if (false && iNum)
420 {
421 pcMesh->mNumBones = iNum;
422 pcMesh->mBones = new aiBone*[pcMesh->mNumBones];
423 iNum = 0;
424 for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
425 {
426 if (aaiBones[iBone].empty())continue;
427 aiBone*& bone = pcMesh->mBones[iNum] = new aiBone();
428
429 bone->mNumWeights = (unsigned int)aaiBones[iBone].size();
430 bone->mWeights = new aiVertexWeight[bone->mNumWeights];
431 bone->mOffsetMatrix = asBones[iBone].mOffsetMatrix;
432 bone->mName.Set( asBones[iBone].mName );
433
434 asBones[iBone].bIsUsed = true;
435
436 for (unsigned int iWeight = 0; iWeight < bone->mNumWeights;++iWeight)
437 {
438 bone->mWeights[iWeight].mVertexId = aaiBones[iBone][iWeight].first;
439 bone->mWeights[iWeight].mWeight = aaiBones[iBone][iWeight].second;
440 }
441 ++iNum;
442 }
443 }
444 delete[] aaiBones;
445 }
446 delete[] aaiFaces;
447}
448
449// ------------------------------------------------------------------------------------------------
450// add bone child nodes
451void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent)
452{
453 ai_assert( NULL != pcNode );
454 ai_assert( 0 == pcNode->mNumChildren );
455 ai_assert( NULL == pcNode->mChildren);
456
457 // first count ...
458 for (unsigned int i = 0; i < asBones.size();++i)
459 {
460 SMD::Bone& bone = asBones[i];
461 if (bone.iParent == iParent)++pcNode->mNumChildren;
462 }
463
464 // now allocate the output array
465 pcNode->mChildren = new aiNode*[pcNode->mNumChildren];
466
467 // and fill all subnodes
468 unsigned int qq = 0;
469 for (unsigned int i = 0; i < asBones.size();++i)
470 {
471 SMD::Bone& bone = asBones[i];
472 if (bone.iParent != iParent)continue;
473
474 aiNode* pc = pcNode->mChildren[qq++] = new aiNode();
475 pc->mName.Set(bone.mName);
476
477 // store the local transformation matrix of the bind pose
478 pc->mTransformation = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrix;
479 pc->mParent = pcNode;
480
481 // add children to this node, too
482 AddBoneChildren(pc,i);
483 }
484}
485
486// ------------------------------------------------------------------------------------------------
487// create output nodes
488void SMDImporter::CreateOutputNodes()
489{
490 pScene->mRootNode = new aiNode();
491 if (!(pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE))
492 {
493 // create one root node that renders all meshes
494 pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
495 pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
496 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
497 pScene->mRootNode->mMeshes[i] = i;
498 }
499
500 // now add all bones as dummy sub nodes to the graph
501 // AddBoneChildren(pScene->mRootNode,(uint32_t)-1);
502
503 // if we have only one bone we can even remove the root node
504 if (pScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE &&
505 1 == pScene->mRootNode->mNumChildren)
506 {
507 aiNode* pcOldRoot = pScene->mRootNode;
508 pScene->mRootNode = pcOldRoot->mChildren[0];
509 pcOldRoot->mChildren[0] = NULL;
510 delete pcOldRoot;
511
512 pScene->mRootNode->mParent = NULL;
513 }
514 else
515 {
516 ::strcpy(pScene->mRootNode->mName.data, "<SMD_root>");
517 pScene->mRootNode->mName.length = 10;
518 }
519}
520
521// ------------------------------------------------------------------------------------------------
522// create output animations
523void SMDImporter::CreateOutputAnimations()
524{
525 unsigned int iNumBones = 0;
526 for (std::vector<SMD::Bone>::const_iterator
527 i = asBones.begin();
528 i != asBones.end();++i)
529 {
530 if ((*i).bIsUsed)++iNumBones;
531 }
532 if (!iNumBones)
533 {
534 // just make sure this case doesn't occur ... (it could occur
535 // if the file was invalid)
536 return;
537 }
538
539 pScene->mNumAnimations = 1;
540 pScene->mAnimations = new aiAnimation*[1];
541 aiAnimation*& anim = pScene->mAnimations[0] = new aiAnimation();
542
543 anim->mDuration = dLengthOfAnim;
544 anim->mNumChannels = iNumBones;
545 anim->mTicksPerSecond = 25.0; // FIXME: is this correct?
546
547 aiNodeAnim** pp = anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
548
549 // now build valid keys
550 unsigned int a = 0;
551 for (std::vector<SMD::Bone>::const_iterator
552 i = asBones.begin();
553 i != asBones.end();++i)
554 {
555 if (!(*i).bIsUsed)continue;
556
557 aiNodeAnim* p = pp[a] = new aiNodeAnim();
558
559 // copy the name of the bone
560 p->mNodeName.Set( i->mName);
561
562 p->mNumRotationKeys = (unsigned int) (*i).sAnim.asKeys.size();
563 if (p->mNumRotationKeys)
564 {
565 p->mNumPositionKeys = p->mNumRotationKeys;
566 aiVectorKey* pVecKeys = p->mPositionKeys = new aiVectorKey[p->mNumRotationKeys];
567 aiQuatKey* pRotKeys = p->mRotationKeys = new aiQuatKey[p->mNumRotationKeys];
568
569 for (std::vector<SMD::Bone::Animation::MatrixKey>::const_iterator
570 qq = (*i).sAnim.asKeys.begin();
571 qq != (*i).sAnim.asKeys.end(); ++qq)
572 {
573 pRotKeys->mTime = pVecKeys->mTime = (*qq).dTime;
574
575 // compute the rotation quaternion from the euler angles
576 pRotKeys->mValue = aiQuaternion( (*qq).vRot.x, (*qq).vRot.y, (*qq).vRot.z );
577 pVecKeys->mValue = (*qq).vPos;
578
579 ++pVecKeys; ++pRotKeys;
580 }
581 }
582 ++a;
583
584 // there are no scaling keys ...
585 }
586}
587
588// ------------------------------------------------------------------------------------------------
589void SMDImporter::ComputeAbsoluteBoneTransformations()
590{
591 // For each bone: determine the key with the lowest time value
592 // theoretically the SMD format should have all keyframes
593 // in order. However, I've seen a file where this wasn't true.
594 for (unsigned int i = 0; i < asBones.size();++i)
595 {
596 SMD::Bone& bone = asBones[i];
597
598 uint32_t iIndex = 0;
599 double dMin = 10e10;
600 for (unsigned int i = 0; i < bone.sAnim.asKeys.size();++i)
601 {
602 double d = std::min(bone.sAnim.asKeys[i].dTime,dMin);
603 if (d < dMin)
604 {
605 dMin = d;
606 iIndex = i;
607 }
608 }
609 bone.sAnim.iFirstTimeKey = iIndex;
610 }
611
612 unsigned int iParent = 0;
613 while (iParent < asBones.size())
614 {
615 for (unsigned int iBone = 0; iBone < asBones.size();++iBone)
616 {
617 SMD::Bone& bone = asBones[iBone];
618
619 if (iParent == bone.iParent)
620 {
621 SMD::Bone& parentBone = asBones[iParent];
622
623
624 uint32_t iIndex = bone.sAnim.iFirstTimeKey;
625 const aiMatrix4x4& mat = bone.sAnim.asKeys[iIndex].matrix;
626 aiMatrix4x4& matOut = bone.sAnim.asKeys[iIndex].matrixAbsolute;
627
628 // The same for the parent bone ...
629 iIndex = parentBone.sAnim.iFirstTimeKey;
630 const aiMatrix4x4& mat2 = parentBone.sAnim.asKeys[iIndex].matrixAbsolute;
631
632 // Compute the absolute transformation matrix
633 matOut = mat * mat2;
634 }
635 }
636 ++iParent;
637 }
638
639 // Store the inverse of the absolute transformation matrix
640 // of the first key as bone offset matrix
641 for (iParent = 0; iParent < asBones.size();++iParent)
642 {
643 SMD::Bone& bone = asBones[iParent];
644 bone.mOffsetMatrix = bone.sAnim.asKeys[bone.sAnim.iFirstTimeKey].matrixAbsolute;
645 bone.mOffsetMatrix.Inverse();
646 }
647}
648\
649// ------------------------------------------------------------------------------------------------
650// create output materials
651void SMDImporter::CreateOutputMaterials()
652{
653 ai_assert( nullptr != pScene );
654
655 pScene->mNumMaterials = (unsigned int)aszTextures.size();
656 pScene->mMaterials = new aiMaterial*[std::max(1u, pScene->mNumMaterials)];
657
658 for (unsigned int iMat = 0; iMat < pScene->mNumMaterials; ++iMat) {
659 aiMaterial* pcMat = new aiMaterial();
660 ai_assert( nullptr != pcMat );
661 pScene->mMaterials[iMat] = pcMat;
662
663 aiString szName;
664 szName.length = (size_t)ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat);
665 pcMat->AddProperty(&szName,AI_MATKEY_NAME);
666
667 if (aszTextures[iMat].length())
668 {
669 ::strncpy(szName.data, aszTextures[iMat].c_str(),MAXLEN-1);
670 szName.length = aszTextures[iMat].length();
671 pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0));
672 }
673 }
674
675 // create a default material if necessary
676 if (0 == pScene->mNumMaterials)
677 {
678 pScene->mNumMaterials = 1;
679
680 aiMaterial* pcHelper = new aiMaterial();
681 pScene->mMaterials[0] = pcHelper;
682
683 int iMode = (int)aiShadingMode_Gouraud;
684 pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
685
686 aiColor3D clr;
687 clr.b = clr.g = clr.r = 0.7f;
688 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
689 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
690
691 clr.b = clr.g = clr.r = 0.05f;
692 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
693
694 aiString szName;
695 szName.Set(AI_DEFAULT_MATERIAL_NAME);
696 pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
697 }
698}
699
700// ------------------------------------------------------------------------------------------------
701// Parse the file
702void SMDImporter::ParseFile()
703{
704 const char* szCurrent = &mBuffer[0];
705
706 // read line per line ...
707 for ( ;; )
708 {
709 if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
710
711 // "version <n> \n", <n> should be 1 for hl and hl2 SMD files
712 if (TokenMatch(szCurrent,"version",7))
713 {
714 if(!SkipSpaces(szCurrent,&szCurrent)) break;
715 if (1 != strtoul10(szCurrent,&szCurrent))
716 {
717 DefaultLogger::get()->warn("SMD.version is not 1. This "
718 "file format is not known. Continuing happily ...");
719 }
720 continue;
721 }
722 // "nodes\n" - Starts the node section
723 if (TokenMatch(szCurrent,"nodes",5))
724 {
725 ParseNodesSection(szCurrent,&szCurrent);
726 continue;
727 }
728 // "triangles\n" - Starts the triangle section
729 if (TokenMatch(szCurrent,"triangles",9))
730 {
731 ParseTrianglesSection(szCurrent,&szCurrent);
732 continue;
733 }
734 // "vertexanimation\n" - Starts the vertex animation section
735 if (TokenMatch(szCurrent,"vertexanimation",15))
736 {
737 bHasUVs = false;
738 ParseVASection(szCurrent,&szCurrent);
739 continue;
740 }
741 // "skeleton\n" - Starts the skeleton section
742 if (TokenMatch(szCurrent,"skeleton",8))
743 {
744 ParseSkeletonSection(szCurrent,&szCurrent);
745 continue;
746 }
747 SkipLine(szCurrent,&szCurrent);
748 }
749 return;
750}
751
752// ------------------------------------------------------------------------------------------------
753unsigned int SMDImporter::GetTextureIndex(const std::string& filename)
754{
755 unsigned int iIndex = 0;
756 for (std::vector<std::string>::const_iterator
757 i = aszTextures.begin();
758 i != aszTextures.end();++i,++iIndex)
759 {
760 // case-insensitive ... it's a path
761 if (0 == ASSIMP_stricmp ( filename.c_str(),(*i).c_str()))return iIndex;
762 }
763 iIndex = (unsigned int)aszTextures.size();
764 aszTextures.push_back(filename);
765 return iIndex;
766}
767
768// ------------------------------------------------------------------------------------------------
769// Parse the nodes section of the file
770void SMDImporter::ParseNodesSection(const char* szCurrent,
771 const char** szCurrentOut)
772{
773 for ( ;; )
774 {
775 // "end\n" - Ends the nodes section
776 if (0 == ASSIMP_strincmp(szCurrent,"end",3) &&
777 IsSpaceOrNewLine(*(szCurrent+3)))
778 {
779 szCurrent += 4;
780 break;
781 }
782 ParseNodeInfo(szCurrent,&szCurrent);
783 }
784 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
785 *szCurrentOut = szCurrent;
786}
787
788// ------------------------------------------------------------------------------------------------
789// Parse the triangles section of the file
790void SMDImporter::ParseTrianglesSection(const char* szCurrent,
791 const char** szCurrentOut)
792{
793 // Parse a triangle, parse another triangle, parse the next triangle ...
794 // and so on until we reach a token that looks quite similar to "end"
795 for ( ;; )
796 {
797 if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
798
799 // "end\n" - Ends the triangles section
800 if (TokenMatch(szCurrent,"end",3))
801 break;
802 ParseTriangle(szCurrent,&szCurrent);
803 }
804 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
805 *szCurrentOut = szCurrent;
806}
807// ------------------------------------------------------------------------------------------------
808// Parse the vertex animation section of the file
809void SMDImporter::ParseVASection(const char* szCurrent,
810 const char** szCurrentOut)
811{
812 unsigned int iCurIndex = 0;
813 for ( ;; )
814 {
815 if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
816
817 // "end\n" - Ends the "vertexanimation" section
818 if (TokenMatch(szCurrent,"end",3))
819 break;
820
821 // "time <n>\n"
822 if (TokenMatch(szCurrent,"time",4))
823 {
824 // NOTE: The doc says that time values COULD be negative ...
825 // NOTE2: this is the shape key -> valve docs
826 int iTime = 0;
827 if(!ParseSignedInt(szCurrent,&szCurrent,iTime) || configFrameID != (unsigned int)iTime)break;
828 SkipLine(szCurrent,&szCurrent);
829 }
830 else
831 {
832 if(0 == iCurIndex)
833 {
834 asTriangles.push_back(SMD::Face());
835 }
836 if (++iCurIndex == 3)iCurIndex = 0;
837 ParseVertex(szCurrent,&szCurrent,asTriangles.back().avVertices[iCurIndex],true);
838 }
839 }
840
841 if (iCurIndex != 2 && !asTriangles.empty())
842 {
843 // we want to no degenerates, so throw this triangle away
844 asTriangles.pop_back();
845 }
846
847 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
848 *szCurrentOut = szCurrent;
849}
850// ------------------------------------------------------------------------------------------------
851// Parse the skeleton section of the file
852void SMDImporter::ParseSkeletonSection(const char* szCurrent,
853 const char** szCurrentOut)
854{
855 int iTime = 0;
856 for ( ;; )
857 {
858 if(!SkipSpacesAndLineEnd(szCurrent,&szCurrent)) break;
859
860 // "end\n" - Ends the skeleton section
861 if (TokenMatch(szCurrent,"end",3))
862 break;
863
864 // "time <n>\n" - Specifies the current animation frame
865 else if (TokenMatch(szCurrent,"time",4))
866 {
867 // NOTE: The doc says that time values COULD be negative ...
868 if(!ParseSignedInt(szCurrent,&szCurrent,iTime))break;
869
870 iSmallestFrame = std::min(iSmallestFrame,iTime);
871 SkipLine(szCurrent,&szCurrent);
872 }
873 else ParseSkeletonElement(szCurrent,&szCurrent,iTime);
874 }
875 *szCurrentOut = szCurrent;
876}
877
878// ------------------------------------------------------------------------------------------------
879#define SMDI_PARSE_RETURN { \
880 SkipLine(szCurrent,&szCurrent); \
881 *szCurrentOut = szCurrent; \
882 return; \
883}
884// ------------------------------------------------------------------------------------------------
885// Parse a node line
886void SMDImporter::ParseNodeInfo(const char* szCurrent,
887 const char** szCurrentOut)
888{
889 unsigned int iBone = 0;
890 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
891 if(!ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent))
892 {
893 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index");
894 SMDI_PARSE_RETURN;
895 }
896 // add our bone to the list
897 if (iBone >= asBones.size())asBones.resize(iBone+1);
898 SMD::Bone& bone = asBones[iBone];
899
900 bool bQuota = true;
901 if ('\"' != *szCurrent)
902 {
903 LogWarning("Bone name is expcted to be enclosed in "
904 "double quotation marks. ");
905 bQuota = false;
906 }
907 else ++szCurrent;
908
909 const char* szEnd = szCurrent;
910 for ( ;; )
911 {
912 if (bQuota && '\"' == *szEnd)
913 {
914 iBone = (unsigned int)(szEnd - szCurrent);
915 ++szEnd;
916 break;
917 }
918 else if (IsSpaceOrNewLine(*szEnd))
919 {
920 iBone = (unsigned int)(szEnd - szCurrent);
921 break;
922 }
923 else if (!(*szEnd))
924 {
925 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone name");
926 SMDI_PARSE_RETURN;
927 }
928 ++szEnd;
929 }
930 bone.mName = std::string(szCurrent,iBone);
931 szCurrent = szEnd;
932
933 // the only negative bone parent index that could occur is -1 AFAIK
934 if(!ParseSignedInt(szCurrent,&szCurrent,(int&)bone.iParent))
935 {
936 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone parent index. Assuming -1");
937 SMDI_PARSE_RETURN;
938 }
939
940 // go to the beginning of the next line
941 SMDI_PARSE_RETURN;
942}
943
944// ------------------------------------------------------------------------------------------------
945// Parse a skeleton element
946void SMDImporter::ParseSkeletonElement(const char* szCurrent,
947 const char** szCurrentOut,int iTime)
948{
949 aiVector3D vPos;
950 aiVector3D vRot;
951
952 unsigned int iBone = 0;
953 if(!ParseUnsignedInt(szCurrent,&szCurrent,iBone))
954 {
955 DefaultLogger::get()->error("Unexpected EOF/EOL while parsing bone index");
956 SMDI_PARSE_RETURN;
957 }
958 if (iBone >= asBones.size())
959 {
960 LogErrorNoThrow("Bone index in skeleton section is out of range");
961 SMDI_PARSE_RETURN;
962 }
963 SMD::Bone& bone = asBones[iBone];
964
965 bone.sAnim.asKeys.push_back(SMD::Bone::Animation::MatrixKey());
966 SMD::Bone::Animation::MatrixKey& key = bone.sAnim.asKeys.back();
967
968 key.dTime = (double)iTime;
969 if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.x))
970 {
971 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.x");
972 SMDI_PARSE_RETURN;
973 }
974 if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.y))
975 {
976 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.y");
977 SMDI_PARSE_RETURN;
978 }
979 if(!ParseFloat(szCurrent,&szCurrent,(float&)vPos.z))
980 {
981 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.pos.z");
982 SMDI_PARSE_RETURN;
983 }
984 if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.x))
985 {
986 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.x");
987 SMDI_PARSE_RETURN;
988 }
989 if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.y))
990 {
991 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.y");
992 SMDI_PARSE_RETURN;
993 }
994 if(!ParseFloat(szCurrent,&szCurrent,(float&)vRot.z))
995 {
996 LogErrorNoThrow("Unexpected EOF/EOL while parsing bone.rot.z");
997 SMDI_PARSE_RETURN;
998 }
999 // build the transformation matrix of the key
1000 key.matrix.FromEulerAnglesXYZ(vRot.x,vRot.y,vRot.z);
1001 {
1002 aiMatrix4x4 mTemp;
1003 mTemp.a4 = vPos.x;
1004 mTemp.b4 = vPos.y;
1005 mTemp.c4 = vPos.z;
1006 key.matrix = key.matrix * mTemp;
1007 }
1008
1009 // go to the beginning of the next line
1010 SMDI_PARSE_RETURN;
1011}
1012
1013// ------------------------------------------------------------------------------------------------
1014// Parse a triangle
1015void SMDImporter::ParseTriangle(const char* szCurrent,
1016 const char** szCurrentOut)
1017{
1018 asTriangles.push_back(SMD::Face());
1019 SMD::Face& face = asTriangles.back();
1020
1021 if(!SkipSpaces(szCurrent,&szCurrent))
1022 {
1023 LogErrorNoThrow("Unexpected EOF/EOL while parsing a triangle");
1024 return;
1025 }
1026
1027 // read the texture file name
1028 const char* szLast = szCurrent;
1029 while (!IsSpaceOrNewLine(*++szCurrent));
1030
1031 // ... and get the index that belongs to this file name
1032 face.iTexture = GetTextureIndex(std::string(szLast,(uintptr_t)szCurrent-(uintptr_t)szLast));
1033
1034 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
1035
1036 // load three vertices
1037 for (unsigned int iVert = 0; iVert < 3;++iVert)
1038 {
1039 ParseVertex(szCurrent,&szCurrent,
1040 face.avVertices[iVert]);
1041 }
1042 *szCurrentOut = szCurrent;
1043}
1044
1045// ------------------------------------------------------------------------------------------------
1046// Parse a float
1047bool SMDImporter::ParseFloat(const char* szCurrent,
1048 const char** szCurrentOut, float& out)
1049{
1050 if(!SkipSpaces(&szCurrent))
1051 return false;
1052
1053 *szCurrentOut = fast_atoreal_move<float>(szCurrent,out);
1054 return true;
1055}
1056
1057// ------------------------------------------------------------------------------------------------
1058// Parse an unsigned int
1059bool SMDImporter::ParseUnsignedInt(const char* szCurrent,
1060 const char** szCurrentOut, unsigned int& out)
1061{
1062 if(!SkipSpaces(&szCurrent))
1063 return false;
1064
1065 out = strtoul10(szCurrent,szCurrentOut);
1066 return true;
1067}
1068
1069// ------------------------------------------------------------------------------------------------
1070// Parse a signed int
1071bool SMDImporter::ParseSignedInt(const char* szCurrent,
1072 const char** szCurrentOut, int& out)
1073{
1074 if(!SkipSpaces(&szCurrent))
1075 return false;
1076
1077 out = strtol10(szCurrent,szCurrentOut);
1078 return true;
1079}
1080
1081// ------------------------------------------------------------------------------------------------
1082// Parse a vertex
1083void SMDImporter::ParseVertex(const char* szCurrent,
1084 const char** szCurrentOut, SMD::Vertex& vertex,
1085 bool bVASection /*= false*/)
1086{
1087 if (SkipSpaces(&szCurrent) && IsLineEnd(*szCurrent))
1088 {
1089 SkipSpacesAndLineEnd(szCurrent,&szCurrent);
1090 return ParseVertex(szCurrent,szCurrentOut,vertex,bVASection);
1091 }
1092 if(!ParseSignedInt(szCurrent,&szCurrent,(int&)vertex.iParentNode))
1093 {
1094 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.parent");
1095 SMDI_PARSE_RETURN;
1096 }
1097 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.x))
1098 {
1099 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.x");
1100 SMDI_PARSE_RETURN;
1101 }
1102 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.y))
1103 {
1104 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.y");
1105 SMDI_PARSE_RETURN;
1106 }
1107 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.pos.z))
1108 {
1109 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.pos.z");
1110 SMDI_PARSE_RETURN;
1111 }
1112 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.x))
1113 {
1114 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.x");
1115 SMDI_PARSE_RETURN;
1116 }
1117 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.y))
1118 {
1119 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.y");
1120 SMDI_PARSE_RETURN;
1121 }
1122 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.nor.z))
1123 {
1124 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.nor.z");
1125 SMDI_PARSE_RETURN;
1126 }
1127
1128 if (bVASection)SMDI_PARSE_RETURN;
1129
1130 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.x))
1131 {
1132 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.x");
1133 SMDI_PARSE_RETURN;
1134 }
1135 if(!ParseFloat(szCurrent,&szCurrent,(float&)vertex.uv.y))
1136 {
1137 LogErrorNoThrow("Unexpected EOF/EOL while parsing vertex.uv.y");
1138 SMDI_PARSE_RETURN;
1139 }
1140
1141 // now read the number of bones affecting this vertex
1142 // all elements from now are fully optional, we don't need them
1143 unsigned int iSize = 0;
1144 if(!ParseUnsignedInt(szCurrent,&szCurrent,iSize))SMDI_PARSE_RETURN;
1145 vertex.aiBoneLinks.resize(iSize,std::pair<unsigned int, float>(0,0.0f));
1146
1147 for (std::vector<std::pair<unsigned int, float> >::iterator
1148 i = vertex.aiBoneLinks.begin();
1149 i != vertex.aiBoneLinks.end();++i)
1150 {
1151 if(!ParseUnsignedInt(szCurrent,&szCurrent,(*i).first))
1152 SMDI_PARSE_RETURN;
1153 if(!ParseFloat(szCurrent,&szCurrent,(*i).second))
1154 SMDI_PARSE_RETURN;
1155 }
1156
1157 // go to the beginning of the next line
1158 SMDI_PARSE_RETURN;
1159}
1160
1161#endif // !! ASSIMP_BUILD_NO_SMD_IMPORTER
1162