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