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 Implementation of the MDC importer class */
44
45
46#ifndef ASSIMP_BUILD_NO_MDC_IMPORTER
47
48// internal headers
49#include "MDCLoader.h"
50#include "MD3FileData.h"
51#include "MDCNormalTable.h" // shouldn't be included by other units
52#include <assimp/DefaultLogger.hpp>
53#include <assimp/Importer.hpp>
54#include <assimp/IOSystem.hpp>
55#include <assimp/scene.h>
56#include <assimp/importerdesc.h>
57#include <memory>
58
59
60using namespace Assimp;
61using namespace Assimp::MDC;
62
63static const aiImporterDesc desc = {
64 "Return To Castle Wolfenstein Mesh Importer",
65 "",
66 "",
67 "",
68 aiImporterFlags_SupportBinaryFlavour,
69 0,
70 0,
71 0,
72 0,
73 "mdc"
74};
75
76// ------------------------------------------------------------------------------------------------
77void MDC::BuildVertex(const Frame& frame,
78 const BaseVertex& bvert,
79 const CompressedVertex& cvert,
80 aiVector3D& vXYZOut,
81 aiVector3D& vNorOut)
82{
83 // compute the position
84 const float xd = (cvert.xd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING;
85 const float yd = (cvert.yd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING;
86 const float zd = (cvert.zd - AI_MDC_CVERT_BIAS) * AI_MDC_DELTA_SCALING;
87 vXYZOut.x = frame.localOrigin.x + AI_MDC_BASE_SCALING * (bvert.x + xd);
88 vXYZOut.y = frame.localOrigin.y + AI_MDC_BASE_SCALING * (bvert.y + yd);
89 vXYZOut.z = frame.localOrigin.z + AI_MDC_BASE_SCALING * (bvert.z + zd);
90
91 // compute the normal vector .. ehm ... lookup it in the table :-)
92 vNorOut.x = mdcNormals[cvert.nd][0];
93 vNorOut.y = mdcNormals[cvert.nd][1];
94 vNorOut.z = mdcNormals[cvert.nd][2];
95}
96
97// ------------------------------------------------------------------------------------------------
98// Constructor to be privately used by Importer
99MDCImporter::MDCImporter()
100 : configFrameID(),
101 pcHeader(),
102 mBuffer(),
103 fileSize()
104{
105}
106
107// ------------------------------------------------------------------------------------------------
108// Destructor, private as well
109MDCImporter::~MDCImporter()
110{
111}
112// ------------------------------------------------------------------------------------------------
113// Returns whether the class can handle the format of the given file.
114bool MDCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
115{
116 const std::string extension = GetExtension(pFile);
117 if (extension == "mdc")
118 return true;
119
120 // if check for extension is not enough, check for the magic tokens
121 if (!extension.length() || checkSig) {
122 uint32_t tokens[1];
123 tokens[0] = AI_MDC_MAGIC_NUMBER_LE;
124 return CheckMagicToken(pIOHandler,pFile,tokens,1);
125 }
126 return false;
127}
128
129// ------------------------------------------------------------------------------------------------
130const aiImporterDesc* MDCImporter::GetInfo () const
131{
132 return &desc;
133}
134
135// ------------------------------------------------------------------------------------------------
136// Validate the header of the given MDC file
137void MDCImporter::ValidateHeader()
138{
139 AI_SWAP4( this->pcHeader->ulVersion );
140 AI_SWAP4( this->pcHeader->ulFlags );
141 AI_SWAP4( this->pcHeader->ulNumFrames );
142 AI_SWAP4( this->pcHeader->ulNumTags );
143 AI_SWAP4( this->pcHeader->ulNumSurfaces );
144 AI_SWAP4( this->pcHeader->ulNumSkins );
145 AI_SWAP4( this->pcHeader->ulOffsetBorderFrames );
146
147 if (pcHeader->ulIdent != AI_MDC_MAGIC_NUMBER_BE &&
148 pcHeader->ulIdent != AI_MDC_MAGIC_NUMBER_LE)
149 {
150 char szBuffer[5];
151 szBuffer[0] = ((char*)&pcHeader->ulIdent)[0];
152 szBuffer[1] = ((char*)&pcHeader->ulIdent)[1];
153 szBuffer[2] = ((char*)&pcHeader->ulIdent)[2];
154 szBuffer[3] = ((char*)&pcHeader->ulIdent)[3];
155 szBuffer[4] = '\0';
156
157 throw DeadlyImportError("Invalid MDC magic word: should be IDPC, the "
158 "magic word found is " + std::string( szBuffer ));
159 }
160
161 if (pcHeader->ulVersion != AI_MDC_VERSION)
162 DefaultLogger::get()->warn("Unsupported MDC file version (2 (AI_MDC_VERSION) was expected)");
163
164 if (pcHeader->ulOffsetBorderFrames + pcHeader->ulNumFrames * sizeof(MDC::Frame) > this->fileSize ||
165 pcHeader->ulOffsetSurfaces + pcHeader->ulNumSurfaces * sizeof(MDC::Surface) > this->fileSize)
166 {
167 throw DeadlyImportError("Some of the offset values in the MDC header are invalid "
168 "and point to something behind the file.");
169 }
170
171 if (this->configFrameID >= this->pcHeader->ulNumFrames)
172 throw DeadlyImportError("The requested frame is not available");
173}
174
175// ------------------------------------------------------------------------------------------------
176// Validate the header of a given MDC file surface
177void MDCImporter::ValidateSurfaceHeader(BE_NCONST MDC::Surface* pcSurf)
178{
179 AI_SWAP4(pcSurf->ulFlags);
180 AI_SWAP4(pcSurf->ulNumCompFrames);
181 AI_SWAP4(pcSurf->ulNumBaseFrames);
182 AI_SWAP4(pcSurf->ulNumShaders);
183 AI_SWAP4(pcSurf->ulNumVertices);
184 AI_SWAP4(pcSurf->ulNumTriangles);
185 AI_SWAP4(pcSurf->ulOffsetTriangles);
186 AI_SWAP4(pcSurf->ulOffsetTexCoords);
187 AI_SWAP4(pcSurf->ulOffsetBaseVerts);
188 AI_SWAP4(pcSurf->ulOffsetCompVerts);
189 AI_SWAP4(pcSurf->ulOffsetFrameBaseFrames);
190 AI_SWAP4(pcSurf->ulOffsetFrameCompFrames);
191 AI_SWAP4(pcSurf->ulOffsetEnd);
192
193 const unsigned int iMax = this->fileSize - (unsigned int)((int8_t*)pcSurf-(int8_t*)pcHeader);
194
195 if (pcSurf->ulOffsetBaseVerts + pcSurf->ulNumVertices * sizeof(MDC::BaseVertex) > iMax ||
196 (pcSurf->ulNumCompFrames && pcSurf->ulOffsetCompVerts + pcSurf->ulNumVertices * sizeof(MDC::CompressedVertex) > iMax) ||
197 pcSurf->ulOffsetTriangles + pcSurf->ulNumTriangles * sizeof(MDC::Triangle) > iMax ||
198 pcSurf->ulOffsetTexCoords + pcSurf->ulNumVertices * sizeof(MDC::TexturCoord) > iMax ||
199 pcSurf->ulOffsetShaders + pcSurf->ulNumShaders * sizeof(MDC::Shader) > iMax ||
200 pcSurf->ulOffsetFrameBaseFrames + pcSurf->ulNumBaseFrames * 2 > iMax ||
201 (pcSurf->ulNumCompFrames && pcSurf->ulOffsetFrameCompFrames + pcSurf->ulNumCompFrames * 2 > iMax))
202 {
203 throw DeadlyImportError("Some of the offset values in the MDC surface header "
204 "are invalid and point somewhere behind the file.");
205 }
206}
207
208// ------------------------------------------------------------------------------------------------
209// Setup configuration properties
210void MDCImporter::SetupProperties(const Importer* pImp)
211{
212 // The AI_CONFIG_IMPORT_MDC_KEYFRAME option overrides the
213 // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
214 if(static_cast<unsigned int>(-1) == (configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MDC_KEYFRAME,-1))){
215 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
216 }
217}
218
219// ------------------------------------------------------------------------------------------------
220// Imports the given file into the given scene structure.
221void MDCImporter::InternReadFile(
222 const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
223{
224 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
225
226 // Check whether we can read from the file
227 if( file.get() == NULL)
228 throw DeadlyImportError( "Failed to open MDC file " + pFile + ".");
229
230 // check whether the mdc file is large enough to contain the file header
231 fileSize = (unsigned int)file->FileSize();
232 if( fileSize < sizeof(MDC::Header))
233 throw DeadlyImportError( "MDC File is too small.");
234
235 std::vector<unsigned char> mBuffer2(fileSize);
236 file->Read( &mBuffer2[0], 1, fileSize);
237 mBuffer = &mBuffer2[0];
238
239 // validate the file header
240 this->pcHeader = (BE_NCONST MDC::Header*)this->mBuffer;
241 this->ValidateHeader();
242
243 std::vector<std::string> aszShaders;
244
245 // get a pointer to the frame we want to read
246 BE_NCONST MDC::Frame* pcFrame = (BE_NCONST MDC::Frame*)(this->mBuffer+
247 this->pcHeader->ulOffsetBorderFrames);
248
249 // no need to swap the other members, we won't need them
250 pcFrame += configFrameID;
251 AI_SWAP4( pcFrame->localOrigin[0] );
252 AI_SWAP4( pcFrame->localOrigin[1] );
253 AI_SWAP4( pcFrame->localOrigin[2] );
254
255 // get the number of valid surfaces
256 BE_NCONST MDC::Surface* pcSurface, *pcSurface2;
257 pcSurface = pcSurface2 = new (mBuffer + pcHeader->ulOffsetSurfaces) MDC::Surface;
258 unsigned int iNumShaders = 0;
259 for (unsigned int i = 0; i < pcHeader->ulNumSurfaces;++i)
260 {
261 // validate the surface header
262 this->ValidateSurfaceHeader(pcSurface2);
263
264 if (pcSurface2->ulNumVertices && pcSurface2->ulNumTriangles)++pScene->mNumMeshes;
265 iNumShaders += pcSurface2->ulNumShaders;
266 pcSurface2 = new ((int8_t*)pcSurface2 + pcSurface2->ulOffsetEnd) MDC::Surface;
267 }
268 aszShaders.reserve(iNumShaders);
269 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
270
271 // necessary that we don't crash if an exception occurs
272 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
273 pScene->mMeshes[i] = NULL;
274
275 // now read all surfaces
276 unsigned int iDefaultMatIndex = UINT_MAX;
277 for (unsigned int i = 0, iNum = 0; i < pcHeader->ulNumSurfaces;++i)
278 {
279 if (!pcSurface->ulNumVertices || !pcSurface->ulNumTriangles)continue;
280 aiMesh* pcMesh = pScene->mMeshes[iNum++] = new aiMesh();
281
282 pcMesh->mNumFaces = pcSurface->ulNumTriangles;
283 pcMesh->mNumVertices = pcMesh->mNumFaces * 3;
284
285 // store the name of the surface for use as node name.
286 // FIX: make sure there is a 0 termination
287 const_cast<char&>(pcSurface->ucName[AI_MDC_MAXQPATH-1]) = '\0';
288 pcMesh->mTextureCoords[3] = (aiVector3D*)pcSurface->ucName;
289
290 // go to the first shader in the file. ignore the others.
291 if (pcSurface->ulNumShaders)
292 {
293 const MDC::Shader* pcShader = (const MDC::Shader*)((int8_t*)pcSurface + pcSurface->ulOffsetShaders);
294 pcMesh->mMaterialIndex = (unsigned int)aszShaders.size();
295
296 // create a new shader
297 aszShaders.push_back(std::string( pcShader->ucName, std::min(
298 ::strlen(pcShader->ucName),sizeof(pcShader->ucName)) ));
299 }
300 // need to create a default material
301 else if (UINT_MAX == iDefaultMatIndex)
302 {
303 pcMesh->mMaterialIndex = iDefaultMatIndex = (unsigned int)aszShaders.size();
304 aszShaders.push_back(std::string());
305 }
306 // otherwise assign a reference to the default material
307 else pcMesh->mMaterialIndex = iDefaultMatIndex;
308
309 // allocate output storage for the mesh
310 aiVector3D* pcVertCur = pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
311 aiVector3D* pcNorCur = pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
312 aiVector3D* pcUVCur = pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
313 aiFace* pcFaceCur = pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
314
315 // create all vertices/faces
316 BE_NCONST MDC::Triangle* pcTriangle = (BE_NCONST MDC::Triangle*)
317 ((int8_t*)pcSurface+pcSurface->ulOffsetTriangles);
318
319 BE_NCONST MDC::TexturCoord* const pcUVs = (BE_NCONST MDC::TexturCoord*)
320 ((int8_t*)pcSurface+pcSurface->ulOffsetTexCoords);
321
322 // get a pointer to the uncompressed vertices
323 int16_t iOfs = *((int16_t*) ((int8_t*) pcSurface +
324 pcSurface->ulOffsetFrameBaseFrames) + this->configFrameID);
325
326 AI_SWAP2(iOfs);
327
328 BE_NCONST MDC::BaseVertex* const pcVerts = (BE_NCONST MDC::BaseVertex*)
329 ((int8_t*)pcSurface+pcSurface->ulOffsetBaseVerts) +
330 ((int)iOfs * pcSurface->ulNumVertices * 4);
331
332 // do the main swapping stuff ...
333#if (defined AI_BUILD_BIG_ENDIAN)
334
335 // swap all triangles
336 for (unsigned int i = 0; i < pcSurface->ulNumTriangles;++i)
337 {
338 AI_SWAP4( pcTriangle[i].aiIndices[0] );
339 AI_SWAP4( pcTriangle[i].aiIndices[1] );
340 AI_SWAP4( pcTriangle[i].aiIndices[2] );
341 }
342
343 // swap all vertices
344 for (unsigned int i = 0; i < pcSurface->ulNumVertices*pcSurface->ulNumBaseFrames;++i)
345 {
346 AI_SWAP2( pcVerts->normal );
347 AI_SWAP2( pcVerts->x );
348 AI_SWAP2( pcVerts->y );
349 AI_SWAP2( pcVerts->z );
350 }
351
352 // swap all texture coordinates
353 for (unsigned int i = 0; i < pcSurface->ulNumVertices;++i)
354 {
355 AI_SWAP4( pcUVs->v );
356 AI_SWAP4( pcUVs->v );
357 }
358
359#endif
360
361 const MDC::CompressedVertex* pcCVerts = NULL;
362 int16_t* mdcCompVert = NULL;
363
364 // access compressed frames for large frame numbers, but never for the first
365 if( this->configFrameID && pcSurface->ulNumCompFrames > 0 )
366 {
367 mdcCompVert = (int16_t*) ((int8_t*)pcSurface+pcSurface->ulOffsetFrameCompFrames) + this->configFrameID;
368 AI_SWAP2P(mdcCompVert);
369 if( *mdcCompVert >= 0 )
370 {
371 pcCVerts = (const MDC::CompressedVertex*)((int8_t*)pcSurface +
372 pcSurface->ulOffsetCompVerts) + *mdcCompVert * pcSurface->ulNumVertices;
373 }
374 else mdcCompVert = NULL;
375 }
376
377 // copy all faces
378 for (unsigned int iFace = 0; iFace < pcSurface->ulNumTriangles;++iFace,
379 ++pcTriangle,++pcFaceCur)
380 {
381 const unsigned int iOutIndex = iFace*3;
382 pcFaceCur->mNumIndices = 3;
383 pcFaceCur->mIndices = new unsigned int[3];
384
385 for (unsigned int iIndex = 0; iIndex < 3;++iIndex,
386 ++pcVertCur,++pcUVCur,++pcNorCur)
387 {
388 uint32_t quak = pcTriangle->aiIndices[iIndex];
389 if (quak >= pcSurface->ulNumVertices)
390 {
391 DefaultLogger::get()->error("MDC vertex index is out of range");
392 quak = pcSurface->ulNumVertices-1;
393 }
394
395 // compressed vertices?
396 if (mdcCompVert)
397 {
398 MDC::BuildVertex(*pcFrame,pcVerts[quak],pcCVerts[quak],
399 *pcVertCur,*pcNorCur);
400 }
401 else
402 {
403 // copy position
404 pcVertCur->x = pcVerts[quak].x * AI_MDC_BASE_SCALING;
405 pcVertCur->y = pcVerts[quak].y * AI_MDC_BASE_SCALING;
406 pcVertCur->z = pcVerts[quak].z * AI_MDC_BASE_SCALING;
407
408 // copy normals
409 MD3::LatLngNormalToVec3( pcVerts[quak].normal, &pcNorCur->x );
410
411 // copy texture coordinates
412 pcUVCur->x = pcUVs[quak].u;
413 pcUVCur->y = ai_real( 1.0 )-pcUVs[quak].v; // DX to OGL
414 }
415 pcVertCur->x += pcFrame->localOrigin[0] ;
416 pcVertCur->y += pcFrame->localOrigin[1] ;
417 pcVertCur->z += pcFrame->localOrigin[2] ;
418 }
419
420 // swap the face order - DX to OGL
421 pcFaceCur->mIndices[0] = iOutIndex + 2;
422 pcFaceCur->mIndices[1] = iOutIndex + 1;
423 pcFaceCur->mIndices[2] = iOutIndex + 0;
424 }
425
426 pcSurface = new ((int8_t*)pcSurface + pcSurface->ulOffsetEnd) MDC::Surface;
427 }
428
429 // create a flat node graph with a root node and one child for each surface
430 if (!pScene->mNumMeshes)
431 throw DeadlyImportError( "Invalid MDC file: File contains no valid mesh");
432 else if (1 == pScene->mNumMeshes)
433 {
434 pScene->mRootNode = new aiNode();
435 pScene->mRootNode->mName.Set(std::string((const char*)pScene->mMeshes[0]->mTextureCoords[3]));
436 pScene->mRootNode->mNumMeshes = 1;
437 pScene->mRootNode->mMeshes = new unsigned int[1];
438 pScene->mRootNode->mMeshes[0] = 0;
439 }
440 else
441 {
442 pScene->mRootNode = new aiNode();
443 pScene->mRootNode->mNumChildren = pScene->mNumMeshes;
444 pScene->mRootNode->mChildren = new aiNode*[pScene->mNumMeshes];
445 pScene->mRootNode->mName.Set("<root>");
446 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
447 {
448 aiNode* pcNode = pScene->mRootNode->mChildren[i] = new aiNode();
449 pcNode->mParent = pScene->mRootNode;
450 pcNode->mName.Set(std::string((const char*)pScene->mMeshes[i]->mTextureCoords[3]));
451 pcNode->mNumMeshes = 1;
452 pcNode->mMeshes = new unsigned int[1];
453 pcNode->mMeshes[0] = i;
454 }
455 }
456
457 // make sure we invalidate the pointer to the mesh name
458 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
459 pScene->mMeshes[i]->mTextureCoords[3] = NULL;
460
461 // create materials
462 pScene->mNumMaterials = (unsigned int)aszShaders.size();
463 pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
464 for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
465 {
466 aiMaterial* pcMat = new aiMaterial();
467 pScene->mMaterials[i] = pcMat;
468
469 const std::string& name = aszShaders[i];
470
471 int iMode = (int)aiShadingMode_Gouraud;
472 pcMat->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
473
474 // add a small ambient color value - RtCW seems to have one
475 aiColor3D clr;
476 clr.b = clr.g = clr.r = 0.05f;
477 pcMat->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
478
479 if (name.length())clr.b = clr.g = clr.r = 1.0f;
480 else clr.b = clr.g = clr.r = 0.6f;
481
482 pcMat->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
483 pcMat->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
484
485 if (name.length())
486 {
487 aiString path;
488 path.Set(name);
489 pcMat->AddProperty(&path,AI_MATKEY_TEXTURE_DIFFUSE(0));
490 }
491 }
492}
493
494#endif // !! ASSIMP_BUILD_NO_MDC_IMPORTER
495