1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5
6 Copyright (c) 2006-2021, assimp team
7
8
9
10 All rights reserved.
11
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15
16 * Redistributions of source code must retain the above
17 copyright notice, this list of conditions and the
18 following disclaimer.
19
20 * Redistributions in binary form must reproduce the above
21 copyright notice, this list of conditions and the
22 following disclaimer in the documentation and/or other
23 materials provided with the distribution.
24
25 * Neither the name of the assimp team, nor the names of its
26 contributors may be used to endorse or promote products
27 derived from this software without specific prior
28 written permission of the assimp team.
29
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43
44
45 #ifndef ASSIMP_BUILD_NO_MD2_IMPORTER
46
47 /** @file Implementation of the MD2 importer class */
48 #include "MD2Loader.h"
49 #include <assimp/ByteSwapper.h>
50 #include "MD2NormalTable.h" // shouldn't be included by other units
51 #include <assimp/DefaultLogger.hpp>
52 #include <assimp/Importer.hpp>
53 #include <assimp/IOSystem.hpp>
54 #include <assimp/scene.h>
55 #include <assimp/importerdesc.h>
56 #include <assimp/StringUtils.h>
57
58 #include <memory>
59
60 using namespace Assimp;
61 using namespace Assimp::MD2;
62
63 // helper macro to determine the size of an array
64 #if (!defined ARRAYSIZE)
65 # define ARRAYSIZE(_array) (int(sizeof(_array) / sizeof(_array[0])))
66 #endif
67
68 static const aiImporterDesc desc = {
69 "Quake II Mesh Importer",
70 "",
71 "",
72 "",
73 aiImporterFlags_SupportBinaryFlavour,
74 0,
75 0,
76 0,
77 0,
78 "md2"
79 };
80
81 // ------------------------------------------------------------------------------------------------
82 // Helper function to lookup a normal in Quake 2's precalculated table
LookupNormalIndex(uint8_t iNormalIndex,aiVector3D & vOut)83 void MD2::LookupNormalIndex(uint8_t iNormalIndex,aiVector3D& vOut)
84 {
85 // make sure the normal index has a valid value
86 if (iNormalIndex >= ARRAYSIZE(g_avNormals)) {
87 ASSIMP_LOG_WARN("Index overflow in Quake II normal vector list");
88 iNormalIndex = ARRAYSIZE(g_avNormals) - 1;
89 }
90 vOut = *((const aiVector3D*)(&g_avNormals[iNormalIndex]));
91 }
92
93
94 // ------------------------------------------------------------------------------------------------
95 // Constructor to be privately used by Importer
MD2Importer()96 MD2Importer::MD2Importer()
97 : configFrameID(),
98 m_pcHeader(),
99 mBuffer(),
100 fileSize()
101 {}
102
103 // ------------------------------------------------------------------------------------------------
104 // Destructor, private as well
~MD2Importer()105 MD2Importer::~MD2Importer()
106 {}
107
108 // ------------------------------------------------------------------------------------------------
109 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const110 bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
111 {
112 const std::string extension = GetExtension(pFile);
113 if (extension == "md2")
114 return true;
115
116 // if check for extension is not enough, check for the magic tokens
117 if (!extension.length() || checkSig) {
118 uint32_t tokens[1];
119 tokens[0] = AI_MD2_MAGIC_NUMBER_LE;
120 return CheckMagicToken(pIOHandler,pFile,tokens,1);
121 }
122 return false;
123 }
124
125 // ------------------------------------------------------------------------------------------------
126 // Get a list of all extensions supported by this loader
GetInfo() const127 const aiImporterDesc* MD2Importer::GetInfo () const
128 {
129 return &desc;
130 }
131
132 // ------------------------------------------------------------------------------------------------
133 // Setup configuration properties
SetupProperties(const Importer * pImp)134 void MD2Importer::SetupProperties(const Importer* pImp)
135 {
136 // The
137 // AI_CONFIG_IMPORT_MD2_KEYFRAME option overrides the
138 // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
139 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD2_KEYFRAME,-1);
140 if(static_cast<unsigned int>(-1) == configFrameID){
141 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
142 }
143 }
144 // ------------------------------------------------------------------------------------------------
145 // Validate the file header
ValidateHeader()146 void MD2Importer::ValidateHeader( )
147 {
148 // check magic number
149 if (m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE &&
150 m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE)
151 {
152 throw DeadlyImportError("Invalid MD2 magic word: expected IDP2, found ",
153 ai_str_toprintable((char *)&m_pcHeader->magic, 4));
154 }
155
156 // check file format version
157 if (m_pcHeader->version != 8)
158 ASSIMP_LOG_WARN( "Unsupported MD2 file version. Continuing happily ...");
159
160 // check some values whether they are valid
161 if (0 == m_pcHeader->numFrames)
162 throw DeadlyImportError( "Invalid MD2 file: NUM_FRAMES is 0");
163
164 if (m_pcHeader->offsetEnd > (uint32_t)fileSize)
165 throw DeadlyImportError( "Invalid MD2 file: File is too small");
166
167 if (m_pcHeader->numSkins > AI_MAX_ALLOC(MD2::Skin)) {
168 throw DeadlyImportError("Invalid MD2 header: Too many skins, would overflow");
169 }
170
171 if (m_pcHeader->numVertices > AI_MAX_ALLOC(MD2::Vertex)) {
172 throw DeadlyImportError("Invalid MD2 header: Too many vertices, would overflow");
173 }
174
175 if (m_pcHeader->numTexCoords > AI_MAX_ALLOC(MD2::TexCoord)) {
176 throw DeadlyImportError("Invalid MD2 header: Too many texcoords, would overflow");
177 }
178
179 if (m_pcHeader->numTriangles > AI_MAX_ALLOC(MD2::Triangle)) {
180 throw DeadlyImportError("Invalid MD2 header: Too many triangles, would overflow");
181 }
182
183 if (m_pcHeader->numFrames > AI_MAX_ALLOC(MD2::Frame)) {
184 throw DeadlyImportError("Invalid MD2 header: Too many frames, would overflow");
185 }
186
187 // -1 because Frame already contains one
188 unsigned int frameSize = sizeof (MD2::Frame) + (m_pcHeader->numVertices - 1) * sizeof(MD2::Vertex);
189
190 if (m_pcHeader->offsetSkins + m_pcHeader->numSkins * sizeof (MD2::Skin) >= fileSize ||
191 m_pcHeader->offsetTexCoords + m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= fileSize ||
192 m_pcHeader->offsetTriangles + m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= fileSize ||
193 m_pcHeader->offsetFrames + m_pcHeader->numFrames * frameSize >= fileSize ||
194 m_pcHeader->offsetEnd > fileSize)
195 {
196 throw DeadlyImportError("Invalid MD2 header: Some offsets are outside the file");
197 }
198
199 if (m_pcHeader->numSkins > AI_MD2_MAX_SKINS)
200 ASSIMP_LOG_WARN("The model contains more skins than Quake 2 supports");
201 if ( m_pcHeader->numFrames > AI_MD2_MAX_FRAMES)
202 ASSIMP_LOG_WARN("The model contains more frames than Quake 2 supports");
203 if (m_pcHeader->numVertices > AI_MD2_MAX_VERTS)
204 ASSIMP_LOG_WARN("The model contains more vertices than Quake 2 supports");
205
206 if (m_pcHeader->numFrames <= configFrameID )
207 throw DeadlyImportError("MD2: The requested frame (", configFrameID, ") does not exist in the file");
208 }
209
210 // ------------------------------------------------------------------------------------------------
211 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)212 void MD2Importer::InternReadFile( const std::string& pFile,
213 aiScene* pScene, IOSystem* pIOHandler)
214 {
215 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
216
217 // Check whether we can read from the file
218 if (file.get() == nullptr) {
219 throw DeadlyImportError("Failed to open MD2 file ", pFile, "");
220 }
221
222 // check whether the md3 file is large enough to contain
223 // at least the file header
224 fileSize = (unsigned int)file->FileSize();
225 if (fileSize < sizeof(MD2::Header)) {
226 throw DeadlyImportError("MD2 File is too small");
227 }
228
229 std::vector<uint8_t> mBuffer2(fileSize);
230 file->Read(&mBuffer2[0], 1, fileSize);
231 mBuffer = &mBuffer2[0];
232
233 m_pcHeader = (BE_NCONST MD2::Header*)mBuffer;
234
235 #ifdef AI_BUILD_BIG_ENDIAN
236
237 ByteSwap::Swap4(&m_pcHeader->frameSize);
238 ByteSwap::Swap4(&m_pcHeader->magic);
239 ByteSwap::Swap4(&m_pcHeader->numFrames);
240 ByteSwap::Swap4(&m_pcHeader->numGlCommands);
241 ByteSwap::Swap4(&m_pcHeader->numSkins);
242 ByteSwap::Swap4(&m_pcHeader->numTexCoords);
243 ByteSwap::Swap4(&m_pcHeader->numTriangles);
244 ByteSwap::Swap4(&m_pcHeader->numVertices);
245 ByteSwap::Swap4(&m_pcHeader->offsetEnd);
246 ByteSwap::Swap4(&m_pcHeader->offsetFrames);
247 ByteSwap::Swap4(&m_pcHeader->offsetGlCommands);
248 ByteSwap::Swap4(&m_pcHeader->offsetSkins);
249 ByteSwap::Swap4(&m_pcHeader->offsetTexCoords);
250 ByteSwap::Swap4(&m_pcHeader->offsetTriangles);
251 ByteSwap::Swap4(&m_pcHeader->skinHeight);
252 ByteSwap::Swap4(&m_pcHeader->skinWidth);
253 ByteSwap::Swap4(&m_pcHeader->version);
254
255 #endif
256
257 ValidateHeader();
258
259 // there won't be more than one mesh inside the file
260 pScene->mNumMaterials = 1;
261 pScene->mRootNode = new aiNode();
262 pScene->mRootNode->mNumMeshes = 1;
263 pScene->mRootNode->mMeshes = new unsigned int[1];
264 pScene->mRootNode->mMeshes[0] = 0;
265 pScene->mMaterials = new aiMaterial*[1];
266 pScene->mMaterials[0] = new aiMaterial();
267 pScene->mNumMeshes = 1;
268 pScene->mMeshes = new aiMesh*[1];
269
270 aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh();
271 pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
272
273 // navigate to the begin of the current frame data
274 BE_NCONST MD2::Frame* pcFrame = (BE_NCONST MD2::Frame*) ((uint8_t*)
275 m_pcHeader + m_pcHeader->offsetFrames + (m_pcHeader->frameSize * configFrameID));
276
277 // navigate to the begin of the triangle data
278 MD2::Triangle* pcTriangles = (MD2::Triangle*) ((uint8_t*)
279 m_pcHeader + m_pcHeader->offsetTriangles);
280
281 // navigate to the begin of the tex coords data
282 BE_NCONST MD2::TexCoord* pcTexCoords = (BE_NCONST MD2::TexCoord*) ((uint8_t*)
283 m_pcHeader + m_pcHeader->offsetTexCoords);
284
285 // navigate to the begin of the vertex data
286 BE_NCONST MD2::Vertex* pcVerts = (BE_NCONST MD2::Vertex*) (pcFrame->vertices);
287
288 #ifdef AI_BUILD_BIG_ENDIAN
289 for (uint32_t i = 0; i< m_pcHeader->numTriangles; ++i)
290 {
291 for (unsigned int p = 0; p < 3;++p)
292 {
293 ByteSwap::Swap2(& pcTriangles[i].textureIndices[p]);
294 ByteSwap::Swap2(& pcTriangles[i].vertexIndices[p]);
295 }
296 }
297 for (uint32_t i = 0; i < m_pcHeader->offsetTexCoords;++i)
298 {
299 ByteSwap::Swap2(& pcTexCoords[i].s);
300 ByteSwap::Swap2(& pcTexCoords[i].t);
301 }
302 ByteSwap::Swap4( & pcFrame->scale[0] );
303 ByteSwap::Swap4( & pcFrame->scale[1] );
304 ByteSwap::Swap4( & pcFrame->scale[2] );
305 ByteSwap::Swap4( & pcFrame->translate[0] );
306 ByteSwap::Swap4( & pcFrame->translate[1] );
307 ByteSwap::Swap4( & pcFrame->translate[2] );
308 #endif
309
310 pcMesh->mNumFaces = m_pcHeader->numTriangles;
311 pcMesh->mFaces = new aiFace[m_pcHeader->numTriangles];
312
313 // allocate output storage
314 pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3;
315 pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
316 pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
317
318 // Not sure whether there are MD2 files without texture coordinates
319 // NOTE: texture coordinates can be there without a texture,
320 // but a texture can't be there without a valid UV channel
321 aiMaterial* pcHelper = (aiMaterial*)pScene->mMaterials[0];
322 const int iMode = (int)aiShadingMode_Gouraud;
323 pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
324
325 if (m_pcHeader->numTexCoords && m_pcHeader->numSkins)
326 {
327 // navigate to the first texture associated with the mesh
328 const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)m_pcHeader +
329 m_pcHeader->offsetSkins);
330
331 aiColor3D clr;
332 clr.b = clr.g = clr.r = 1.0f;
333 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
334 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
335
336 clr.b = clr.g = clr.r = 0.05f;
337 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
338
339 if (pcSkins->name[0])
340 {
341 aiString szString;
342 const ai_uint32 iLen = (ai_uint32) ::strlen(pcSkins->name);
343 ::memcpy(szString.data,pcSkins->name,iLen);
344 szString.data[iLen] = '\0';
345 szString.length = iLen;
346
347 pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
348 }
349 else{
350 ASSIMP_LOG_WARN("Texture file name has zero length. It will be skipped.");
351 }
352 }
353 else {
354 // apply a default material
355 aiColor3D clr;
356 clr.b = clr.g = clr.r = 0.6f;
357 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
358 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
359
360 clr.b = clr.g = clr.r = 0.05f;
361 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
362
363 aiString szName;
364 szName.Set(AI_DEFAULT_MATERIAL_NAME);
365 pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
366
367 aiString sz;
368
369 // TODO: Try to guess the name of the texture file from the model file name
370
371 sz.Set("$texture_dummy.bmp");
372 pcHelper->AddProperty(&sz,AI_MATKEY_TEXTURE_DIFFUSE(0));
373 }
374
375
376 // now read all triangles of the first frame, apply scaling and translation
377 unsigned int iCurrent = 0;
378
379 float fDivisorU = 1.0f,fDivisorV = 1.0f;
380 if (m_pcHeader->numTexCoords) {
381 // allocate storage for texture coordinates, too
382 pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
383 pcMesh->mNumUVComponents[0] = 2;
384
385 // check whether the skin width or height are zero (this would
386 // cause a division through zero)
387 if (!m_pcHeader->skinWidth) {
388 ASSIMP_LOG_ERROR("MD2: No valid skin width given");
389 }
390 else fDivisorU = (float)m_pcHeader->skinWidth;
391 if (!m_pcHeader->skinHeight){
392 ASSIMP_LOG_ERROR("MD2: No valid skin height given");
393 }
394 else fDivisorV = (float)m_pcHeader->skinHeight;
395 }
396
397 for (unsigned int i = 0; i < (unsigned int)m_pcHeader->numTriangles;++i) {
398 // Allocate the face
399 pScene->mMeshes[0]->mFaces[i].mIndices = new unsigned int[3];
400 pScene->mMeshes[0]->mFaces[i].mNumIndices = 3;
401
402 // copy texture coordinates
403 // check whether they are different from the previous value at this index.
404 // In this case, create a full separate set of vertices/normals/texcoords
405 for (unsigned int c = 0; c < 3;++c,++iCurrent) {
406
407 // validate vertex indices
408 unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c];
409 if (iIndex >= m_pcHeader->numVertices) {
410 ASSIMP_LOG_ERROR("MD2: Vertex index is outside the allowed range");
411 iIndex = m_pcHeader->numVertices-1;
412 }
413
414 // read x,y, and z component of the vertex
415 aiVector3D& vec = pcMesh->mVertices[iCurrent];
416
417 vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0];
418 vec.x += pcFrame->translate[0];
419
420 vec.y = (float)pcVerts[iIndex].vertex[1] * pcFrame->scale[1];
421 vec.y += pcFrame->translate[1];
422
423 vec.z = (float)pcVerts[iIndex].vertex[2] * pcFrame->scale[2];
424 vec.z += pcFrame->translate[2];
425
426 // read the normal vector from the precalculated normal table
427 aiVector3D& vNormal = pcMesh->mNormals[iCurrent];
428 LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal);
429
430 if (m_pcHeader->numTexCoords) {
431 // validate texture coordinates
432 iIndex = pcTriangles[i].textureIndices[c];
433 if (iIndex >= m_pcHeader->numTexCoords) {
434 ASSIMP_LOG_ERROR("MD2: UV index is outside the allowed range");
435 iIndex = m_pcHeader->numTexCoords-1;
436 }
437
438 aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent];
439
440 // the texture coordinates are absolute values but we
441 // need relative values between 0 and 1
442 pcOut.x = pcTexCoords[iIndex].s / fDivisorU;
443 pcOut.y = 1.f-pcTexCoords[iIndex].t / fDivisorV;
444 }
445 pScene->mMeshes[0]->mFaces[i].mIndices[c] = iCurrent;
446 }
447 // flip the face order
448 std::swap( pScene->mMeshes[0]->mFaces[i].mIndices[0], pScene->mMeshes[0]->mFaces[i].mIndices[2] );
449 }
450 // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
451 pScene->mRootNode->mTransformation = aiMatrix4x4(
452 1.f, 0.f, 0.f, 0.f,
453 0.f, 0.f, 1.f, 0.f,
454 0.f, -1.f, 0.f, 0.f,
455 0.f, 0.f, 0.f, 1.f);
456 }
457
458 #endif // !! ASSIMP_BUILD_NO_MD2_IMPORTER
459