1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5
6 Copyright (c) 2006-2019, 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 /** @file Implementation of the MDL importer class */
45
46
47 #ifndef ASSIMP_BUILD_NO_HMP_IMPORTER
48
49 // internal headers
50 #include "HMP/HMPLoader.h"
51 #include "MD2/MD2FileData.h"
52
53 #include <assimp/IOSystem.hpp>
54 #include <assimp/DefaultLogger.hpp>
55 #include <assimp/scene.h>
56 #include <assimp/importerdesc.h>
57
58 #include <memory>
59
60 using namespace Assimp;
61
62 static const aiImporterDesc desc = {
63 "3D GameStudio Heightmap (HMP) Importer",
64 "",
65 "",
66 "",
67 aiImporterFlags_SupportBinaryFlavour,
68 0,
69 0,
70 0,
71 0,
72 "hmp"
73 };
74
75 // ------------------------------------------------------------------------------------------------
76 // Constructor to be privately used by Importer
HMPImporter()77 HMPImporter::HMPImporter()
78 {
79 // nothing to do here
80 }
81
82 // ------------------------------------------------------------------------------------------------
83 // Destructor, private as well
~HMPImporter()84 HMPImporter::~HMPImporter()
85 {
86 // nothing to do here
87 }
88
89 // ------------------------------------------------------------------------------------------------
90 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool cs) const91 bool HMPImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const
92 {
93 const std::string extension = GetExtension(pFile);
94 if (extension == "hmp" )
95 return true;
96
97 // if check for extension is not enough, check for the magic tokens
98 if (!extension.length() || cs) {
99 uint32_t tokens[3];
100 tokens[0] = AI_HMP_MAGIC_NUMBER_LE_4;
101 tokens[1] = AI_HMP_MAGIC_NUMBER_LE_5;
102 tokens[2] = AI_HMP_MAGIC_NUMBER_LE_7;
103 return CheckMagicToken(pIOHandler,pFile,tokens,3,0);
104 }
105 return false;
106 }
107
108 // ------------------------------------------------------------------------------------------------
109 // Get list of all file extensions that are handled by this loader
GetInfo() const110 const aiImporterDesc* HMPImporter::GetInfo () const
111 {
112 return &desc;
113 }
114
115 // ------------------------------------------------------------------------------------------------
116 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * _pScene,IOSystem * _pIOHandler)117 void HMPImporter::InternReadFile( const std::string& pFile,
118 aiScene* _pScene, IOSystem* _pIOHandler)
119 {
120 pScene = _pScene;
121 pIOHandler = _pIOHandler;
122 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
123
124 // Check whether we can read from the file
125 if( file.get() == NULL)
126 throw DeadlyImportError( "Failed to open HMP file " + pFile + ".");
127
128 // Check whether the HMP file is large enough to contain
129 // at least the file header
130 const size_t fileSize = file->FileSize();
131 if( fileSize < 50)
132 throw DeadlyImportError( "HMP File is too small.");
133
134 // Allocate storage and copy the contents of the file to a memory buffer
135 mBuffer = new uint8_t[fileSize];
136 file->Read( (void*)mBuffer, 1, fileSize);
137 iFileSize = (unsigned int)fileSize;
138
139 // Determine the file subtype and call the appropriate member function
140 const uint32_t iMagic = *((uint32_t*)this->mBuffer);
141
142 // HMP4 format
143 if (AI_HMP_MAGIC_NUMBER_LE_4 == iMagic ||
144 AI_HMP_MAGIC_NUMBER_BE_4 == iMagic)
145 {
146 ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A4, magic word is HMP4");
147 InternReadFile_HMP4();
148 }
149 // HMP5 format
150 else if (AI_HMP_MAGIC_NUMBER_LE_5 == iMagic ||
151 AI_HMP_MAGIC_NUMBER_BE_5 == iMagic)
152 {
153 ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A5, magic word is HMP5");
154 InternReadFile_HMP5();
155 }
156 // HMP7 format
157 else if (AI_HMP_MAGIC_NUMBER_LE_7 == iMagic ||
158 AI_HMP_MAGIC_NUMBER_BE_7 == iMagic)
159 {
160 ASSIMP_LOG_DEBUG("HMP subtype: 3D GameStudio A7, magic word is HMP7");
161 InternReadFile_HMP7();
162 }
163 else
164 {
165 // Print the magic word to the logger
166 char szBuffer[5];
167 szBuffer[0] = ((char*)&iMagic)[0];
168 szBuffer[1] = ((char*)&iMagic)[1];
169 szBuffer[2] = ((char*)&iMagic)[2];
170 szBuffer[3] = ((char*)&iMagic)[3];
171 szBuffer[4] = '\0';
172
173 // We're definitely unable to load this file
174 throw DeadlyImportError( "Unknown HMP subformat " + pFile +
175 ". Magic word (" + szBuffer + ") is not known");
176 }
177
178 // Set the AI_SCENE_FLAGS_TERRAIN bit
179 pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN;
180
181 delete[] mBuffer;
182 mBuffer= nullptr;
183
184 }
185
186 // ------------------------------------------------------------------------------------------------
ValidateHeader_HMP457()187 void HMPImporter::ValidateHeader_HMP457( )
188 {
189 const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)mBuffer;
190
191 if (120 > iFileSize)
192 {
193 throw DeadlyImportError("HMP file is too small (header size is "
194 "120 bytes, this file is smaller)");
195 }
196
197 if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y)
198 throw DeadlyImportError("Size of triangles in either x or y direction is zero");
199
200 if(pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts/pcHeader->fnumverts_x) < 1.0f)
201 throw DeadlyImportError("Number of triangles in either x or y direction is zero");
202
203 if(!pcHeader->numframes)
204 throw DeadlyImportError("There are no frames. At least one should be there");
205
206 }
207
208 // ------------------------------------------------------------------------------------------------
InternReadFile_HMP4()209 void HMPImporter::InternReadFile_HMP4( )
210 {
211 throw DeadlyImportError("HMP4 is currently not supported");
212 }
213
214 // ------------------------------------------------------------------------------------------------
InternReadFile_HMP5()215 void HMPImporter::InternReadFile_HMP5( )
216 {
217 // read the file header and skip everything to byte 84
218 const HMP::Header_HMP5* pcHeader = (const HMP::Header_HMP5*)mBuffer;
219 const unsigned char* szCurrent = (const unsigned char*)(mBuffer+84);
220 ValidateHeader_HMP457();
221
222 // generate an output mesh
223 pScene->mNumMeshes = 1;
224 pScene->mMeshes = new aiMesh*[1];
225 aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh();
226
227 pcMesh->mMaterialIndex = 0;
228 pcMesh->mVertices = new aiVector3D[pcHeader->numverts];
229 pcMesh->mNormals = new aiVector3D[pcHeader->numverts];
230
231 const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x);
232 const unsigned int width = (unsigned int)pcHeader->fnumverts_x;
233
234 // generate/load a material for the terrain
235 CreateMaterial(szCurrent,&szCurrent);
236
237 // goto offset 120, I don't know why ...
238 // (fixme) is this the frame header? I assume yes since it starts with 2.
239 szCurrent += 36;
240 SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width);
241
242 // now load all vertices from the file
243 aiVector3D* pcVertOut = pcMesh->mVertices;
244 aiVector3D* pcNorOut = pcMesh->mNormals;
245 const HMP::Vertex_HMP5* src = (const HMP::Vertex_HMP5*) szCurrent;
246 for (unsigned int y = 0; y < height;++y)
247 {
248 for (unsigned int x = 0; x < width;++x)
249 {
250 pcVertOut->x = x * pcHeader->ftrisize_x;
251 pcVertOut->y = y * pcHeader->ftrisize_y;
252 pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f;
253 MD2::LookupNormalIndex(src->normals162index, *pcNorOut );
254 ++pcVertOut;++pcNorOut;++src;
255 }
256 }
257
258 // generate texture coordinates if necessary
259 if (pcHeader->numskins)
260 GenerateTextureCoords(width,height);
261
262 // now build a list of faces
263 CreateOutputFaceList(width,height);
264
265 // there is no nodegraph in HMP files. Simply assign the one mesh
266 // (no, not the one ring) to the root node
267 pScene->mRootNode = new aiNode();
268 pScene->mRootNode->mName.Set("terrain_root");
269 pScene->mRootNode->mNumMeshes = 1;
270 pScene->mRootNode->mMeshes = new unsigned int[1];
271 pScene->mRootNode->mMeshes[0] = 0;
272 }
273
274 // ------------------------------------------------------------------------------------------------
InternReadFile_HMP7()275 void HMPImporter::InternReadFile_HMP7( )
276 {
277 // read the file header and skip everything to byte 84
278 const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)mBuffer;
279 const unsigned char* szCurrent = (const unsigned char*)(mBuffer+84);
280 ValidateHeader_HMP457();
281
282 // generate an output mesh
283 pScene->mNumMeshes = 1;
284 pScene->mMeshes = new aiMesh*[1];
285 aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh();
286
287 pcMesh->mMaterialIndex = 0;
288 pcMesh->mVertices = new aiVector3D[pcHeader->numverts];
289 pcMesh->mNormals = new aiVector3D[pcHeader->numverts];
290
291 const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x);
292 const unsigned int width = (unsigned int)pcHeader->fnumverts_x;
293
294 // generate/load a material for the terrain
295 CreateMaterial(szCurrent,&szCurrent);
296
297 // goto offset 120, I don't know why ...
298 // (fixme) is this the frame header? I assume yes since it starts with 2.
299 szCurrent += 36;
300
301 SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width);
302
303 // now load all vertices from the file
304 aiVector3D* pcVertOut = pcMesh->mVertices;
305 aiVector3D* pcNorOut = pcMesh->mNormals;
306 const HMP::Vertex_HMP7* src = (const HMP::Vertex_HMP7*) szCurrent;
307 for (unsigned int y = 0; y < height;++y)
308 {
309 for (unsigned int x = 0; x < width;++x)
310 {
311 pcVertOut->x = x * pcHeader->ftrisize_x;
312 pcVertOut->y = y * pcHeader->ftrisize_y;
313
314 // FIXME: What exctly is the correct scaling factor to use?
315 // possibly pcHeader->scale_origin[2] in combination with a
316 // signed interpretation of src->z?
317 pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f;
318
319 pcNorOut->x = ((float)src->normal_x / 0x80 ); // * pcHeader->scale_origin[0];
320 pcNorOut->y = ((float)src->normal_y / 0x80 ); // * pcHeader->scale_origin[1];
321 pcNorOut->z = 1.0f;
322 pcNorOut->Normalize();
323
324 ++pcVertOut;++pcNorOut;++src;
325 }
326 }
327
328 // generate texture coordinates if necessary
329 if (pcHeader->numskins)GenerateTextureCoords(width,height);
330
331 // now build a list of faces
332 CreateOutputFaceList(width,height);
333
334 // there is no nodegraph in HMP files. Simply assign the one mesh
335 // (no, not the One Ring) to the root node
336 pScene->mRootNode = new aiNode();
337 pScene->mRootNode->mName.Set("terrain_root");
338 pScene->mRootNode->mNumMeshes = 1;
339 pScene->mRootNode->mMeshes = new unsigned int[1];
340 pScene->mRootNode->mMeshes[0] = 0;
341 }
342
343 // ------------------------------------------------------------------------------------------------
CreateMaterial(const unsigned char * szCurrent,const unsigned char ** szCurrentOut)344 void HMPImporter::CreateMaterial(const unsigned char* szCurrent,
345 const unsigned char** szCurrentOut)
346 {
347 aiMesh* const pcMesh = pScene->mMeshes[0];
348 const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)mBuffer;
349
350 // we don't need to generate texture coordinates if
351 // we have no textures in the file ...
352 if (pcHeader->numskins)
353 {
354 pcMesh->mTextureCoords[0] = new aiVector3D[pcHeader->numverts];
355 pcMesh->mNumUVComponents[0] = 2;
356
357 // now read the first skin and skip all others
358 ReadFirstSkin(pcHeader->numskins,szCurrent,&szCurrent);
359 }
360 else
361 {
362 // generate a default material
363 const int iMode = (int)aiShadingMode_Gouraud;
364 aiMaterial* pcHelper = new aiMaterial();
365 pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
366
367 aiColor3D clr;
368 clr.b = clr.g = clr.r = 0.6f;
369 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
370 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
371
372 clr.b = clr.g = clr.r = 0.05f;
373 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
374
375 aiString szName;
376 szName.Set(AI_DEFAULT_MATERIAL_NAME);
377 pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
378
379 // add the material to the scene
380 pScene->mNumMaterials = 1;
381 pScene->mMaterials = new aiMaterial*[1];
382 pScene->mMaterials[0] = pcHelper;
383 }
384 *szCurrentOut = szCurrent;
385 }
386
387 // ------------------------------------------------------------------------------------------------
CreateOutputFaceList(unsigned int width,unsigned int height)388 void HMPImporter::CreateOutputFaceList(unsigned int width,unsigned int height)
389 {
390 aiMesh* const pcMesh = this->pScene->mMeshes[0];
391
392 // Allocate enough storage
393 pcMesh->mNumFaces = (width-1) * (height-1);
394 pcMesh->mFaces = new aiFace[pcMesh->mNumFaces];
395
396 pcMesh->mNumVertices = pcMesh->mNumFaces*4;
397 aiVector3D* pcVertices = new aiVector3D[pcMesh->mNumVertices];
398 aiVector3D* pcNormals = new aiVector3D[pcMesh->mNumVertices];
399
400 aiFace* pcFaceOut(pcMesh->mFaces);
401 aiVector3D* pcVertOut = pcVertices;
402 aiVector3D* pcNorOut = pcNormals;
403
404 aiVector3D* pcUVs = pcMesh->mTextureCoords[0] ? new aiVector3D[pcMesh->mNumVertices] : NULL;
405 aiVector3D* pcUVOut(pcUVs);
406
407 // Build the terrain square
408 unsigned int iCurrent = 0;
409 for (unsigned int y = 0; y < height-1;++y) {
410 for (unsigned int x = 0; x < width-1;++x,++pcFaceOut) {
411 pcFaceOut->mNumIndices = 4;
412 pcFaceOut->mIndices = new unsigned int[4];
413
414 *pcVertOut++ = pcMesh->mVertices[y*width+x];
415 *pcVertOut++ = pcMesh->mVertices[(y+1)*width+x];
416 *pcVertOut++ = pcMesh->mVertices[(y+1)*width+x+1];
417 *pcVertOut++ = pcMesh->mVertices[y*width+x+1];
418
419
420 *pcNorOut++ = pcMesh->mNormals[y*width+x];
421 *pcNorOut++ = pcMesh->mNormals[(y+1)*width+x];
422 *pcNorOut++ = pcMesh->mNormals[(y+1)*width+x+1];
423 *pcNorOut++ = pcMesh->mNormals[y*width+x+1];
424
425 if (pcMesh->mTextureCoords[0])
426 {
427 *pcUVOut++ = pcMesh->mTextureCoords[0][y*width+x];
428 *pcUVOut++ = pcMesh->mTextureCoords[0][(y+1)*width+x];
429 *pcUVOut++ = pcMesh->mTextureCoords[0][(y+1)*width+x+1];
430 *pcUVOut++ = pcMesh->mTextureCoords[0][y*width+x+1];
431 }
432
433 for (unsigned int i = 0; i < 4;++i)
434 pcFaceOut->mIndices[i] = iCurrent++;
435 }
436 }
437 delete[] pcMesh->mVertices;
438 pcMesh->mVertices = pcVertices;
439
440 delete[] pcMesh->mNormals;
441 pcMesh->mNormals = pcNormals;
442
443 if (pcMesh->mTextureCoords[0])
444 {
445 delete[] pcMesh->mTextureCoords[0];
446 pcMesh->mTextureCoords[0] = pcUVs;
447 }
448 }
449
450 // ------------------------------------------------------------------------------------------------
ReadFirstSkin(unsigned int iNumSkins,const unsigned char * szCursor,const unsigned char ** szCursorOut)451 void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char* szCursor,
452 const unsigned char** szCursorOut)
453 {
454 ai_assert( 0 != iNumSkins );
455 ai_assert( nullptr != szCursor);
456
457 // read the type of the skin ...
458 // sometimes we need to skip 12 bytes here, I don't know why ...
459 uint32_t iType = *((uint32_t*)szCursor);
460 szCursor += sizeof(uint32_t);
461 if (0 == iType)
462 {
463 szCursor += sizeof(uint32_t) * 2;
464 iType = *((uint32_t*)szCursor);
465 szCursor += sizeof(uint32_t);
466 if (!iType)
467 throw DeadlyImportError("Unable to read HMP7 skin chunk");
468
469 }
470 // read width and height
471 uint32_t iWidth = *((uint32_t*)szCursor); szCursor += sizeof(uint32_t);
472 uint32_t iHeight = *((uint32_t*)szCursor); szCursor += sizeof(uint32_t);
473
474 // allocate an output material
475 aiMaterial* pcMat = new aiMaterial();
476
477 // read the skin, this works exactly as for MDL7
478 ParseSkinLump_3DGS_MDL7(szCursor,&szCursor,
479 pcMat,iType,iWidth,iHeight);
480
481 // now we need to skip any other skins ...
482 for (unsigned int i = 1; i< iNumSkins;++i)
483 {
484 iType = *((uint32_t*)szCursor); szCursor += sizeof(uint32_t);
485 iWidth = *((uint32_t*)szCursor); szCursor += sizeof(uint32_t);
486 iHeight = *((uint32_t*)szCursor); szCursor += sizeof(uint32_t);
487
488 SkipSkinLump_3DGS_MDL7(szCursor,&szCursor,iType,iWidth,iHeight);
489 SizeCheck(szCursor);
490 }
491
492 // setup the material ...
493 pScene->mNumMaterials = 1;
494 pScene->mMaterials = new aiMaterial*[1];
495 pScene->mMaterials[0] = pcMat;
496
497 *szCursorOut = szCursor;
498 }
499
500 // ------------------------------------------------------------------------------------------------
501 // Generate proepr texture coords
GenerateTextureCoords(const unsigned int width,const unsigned int height)502 void HMPImporter::GenerateTextureCoords(
503 const unsigned int width, const unsigned int height)
504 {
505 ai_assert(NULL != pScene->mMeshes && NULL != pScene->mMeshes[0] &&
506 NULL != pScene->mMeshes[0]->mTextureCoords[0]);
507
508 aiVector3D* uv = pScene->mMeshes[0]->mTextureCoords[0];
509
510 const float fY = (1.0f / height) + (1.0f / height) / (height-1);
511 const float fX = (1.0f / width) + (1.0f / width) / (width-1);
512
513 for (unsigned int y = 0; y < height;++y) {
514 for (unsigned int x = 0; x < width;++x,++uv) {
515 uv->y = fY*y;
516 uv->x = fX*x;
517 uv->z = 0.0f;
518 }
519 }
520 }
521
522 #endif // !! ASSIMP_BUILD_NO_HMP_IMPORTER
523