1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2021, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file Implementation of the Terragen importer class */
43 
44 #ifndef ASSIMP_BUILD_NO_TERRAGEN_IMPORTER
45 
46 #include "TerragenLoader.h"
47 #include <assimp/StreamReader.h>
48 #include <assimp/importerdesc.h>
49 #include <assimp/scene.h>
50 #include <assimp/DefaultLogger.hpp>
51 #include <assimp/IOSystem.hpp>
52 #include <assimp/Importer.hpp>
53 
54 using namespace Assimp;
55 
56 static const aiImporterDesc desc = {
57     "Terragen Heightmap Importer",
58     "",
59     "",
60     "http://www.planetside.co.uk/",
61     aiImporterFlags_SupportBinaryFlavour,
62     0,
63     0,
64     0,
65     0,
66     "ter"
67 };
68 
69 // ------------------------------------------------------------------------------------------------
70 // Constructor to be privately used by Importer
TerragenImporter()71 TerragenImporter::TerragenImporter() :
72         configComputeUVs(false) {
73     // empty
74 }
75 
76 // ------------------------------------------------------------------------------------------------
77 // Destructor, private as well
~TerragenImporter()78 TerragenImporter::~TerragenImporter() {
79     // empty
80 }
81 
82 // ------------------------------------------------------------------------------------------------
83 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const84 bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
85     // check file extension
86     std::string extension = GetExtension(pFile);
87 
88     if (extension == "ter")
89         return true;
90 
91     if (!extension.length() || checkSig) {
92         /*  If CanRead() is called in order to check whether we
93          *  support a specific file extension in general pIOHandler
94          *  might be nullptr and it's our duty to return true here.
95          */
96         if (!pIOHandler) {
97             return true;
98         }
99 
100         static const char * const tokens[] = { "terragen" };
101         return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
102     }
103 
104     return false;
105 }
106 
107 // ------------------------------------------------------------------------------------------------
108 // Build a string of all file extensions supported
GetInfo() const109 const aiImporterDesc *TerragenImporter::GetInfo() const {
110     return &desc;
111 }
112 
113 // ------------------------------------------------------------------------------------------------
114 // Setup import properties
SetupProperties(const Importer * pImp)115 void TerragenImporter::SetupProperties(const Importer *pImp) {
116     // AI_CONFIG_IMPORT_TER_MAKE_UVS
117     configComputeUVs = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_TER_MAKE_UVS, 0));
118 }
119 
120 // ------------------------------------------------------------------------------------------------
121 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)122 void TerragenImporter::InternReadFile(const std::string &pFile,
123         aiScene *pScene, IOSystem *pIOHandler) {
124     IOStream *file = pIOHandler->Open(pFile, "rb");
125 
126     // Check whether we can read from the file
127     if (file == nullptr)
128         throw DeadlyImportError("Failed to open TERRAGEN TERRAIN file ", pFile, ".");
129 
130     // Construct a stream reader to read all data in the correct endianness
131     StreamReaderLE reader(file);
132     if (reader.GetRemainingSize() < 16)
133         throw DeadlyImportError("TER: file is too small");
134 
135     // Check for the existence of the two magic strings 'TERRAGEN' and 'TERRAIN '
136     if (::strncmp((const char *)reader.GetPtr(), AI_TERR_BASE_STRING, 8))
137         throw DeadlyImportError("TER: Magic string \'TERRAGEN\' not found");
138 
139     if (::strncmp((const char *)reader.GetPtr() + 8, AI_TERR_TERRAIN_STRING, 8))
140         throw DeadlyImportError("TER: Magic string \'TERRAIN\' not found");
141 
142     unsigned int x = 0, y = 0, mode = 0;
143 
144     aiNode *root = pScene->mRootNode = new aiNode();
145     root->mName.Set("<TERRAGEN.TERRAIN>");
146 
147     // Default scaling is 30
148     root->mTransformation.a1 = root->mTransformation.b2 = root->mTransformation.c3 = 30.f;
149 
150     // Now read all chunks until we're finished or an EOF marker is encountered
151     reader.IncPtr(16);
152     while (reader.GetRemainingSize() >= 4) {
153         const char *head = (const char *)reader.GetPtr();
154         reader.IncPtr(4);
155 
156         // EOF, break in every case
157         if (!::strncmp(head, AI_TERR_EOF_STRING, 4))
158             break;
159 
160         // Number of x-data points
161         if (!::strncmp(head, AI_TERR_CHUNK_XPTS, 4)) {
162             x = (uint16_t)reader.GetI2();
163         }
164         // Number of y-data points
165         else if (!::strncmp(head, AI_TERR_CHUNK_YPTS, 4)) {
166             y = (uint16_t)reader.GetI2();
167         }
168         // Squared terrains width-1.
169         else if (!::strncmp(head, AI_TERR_CHUNK_SIZE, 4)) {
170             x = y = (uint16_t)reader.GetI2() + 1;
171         }
172         // terrain scaling
173         else if (!::strncmp(head, AI_TERR_CHUNK_SCAL, 4)) {
174             root->mTransformation.a1 = reader.GetF4();
175             root->mTransformation.b2 = reader.GetF4();
176             root->mTransformation.c3 = reader.GetF4();
177         }
178         // mapping == 1: earth radius
179         else if (!::strncmp(head, AI_TERR_CHUNK_CRAD, 4)) {
180             reader.GetF4();
181         }
182         // mapping mode
183         else if (!::strncmp(head, AI_TERR_CHUNK_CRVM, 4)) {
184             mode = reader.GetI1();
185             if (0 != mode)
186                 ASSIMP_LOG_ERROR("TER: Unsupported mapping mode, a flat terrain is returned");
187         }
188         // actual terrain data
189         else if (!::strncmp(head, AI_TERR_CHUNK_ALTW, 4)) {
190             float hscale = (float)reader.GetI2() / 65536;
191             float bheight = (float)reader.GetI2();
192 
193             if (!hscale) hscale = 1;
194 
195             // Ensure we have enough data
196             if (reader.GetRemainingSize() < x * y * 2)
197                 throw DeadlyImportError("TER: ALTW chunk is too small");
198 
199             if (x <= 1 || y <= 1)
200                 throw DeadlyImportError("TER: Invalid terrain size");
201 
202             // Allocate the output mesh
203             pScene->mMeshes = new aiMesh *[pScene->mNumMeshes = 1];
204             aiMesh *m = pScene->mMeshes[0] = new aiMesh();
205 
206             // We return quads
207             aiFace *f = m->mFaces = new aiFace[m->mNumFaces = (x - 1) * (y - 1)];
208             aiVector3D *pv = m->mVertices = new aiVector3D[m->mNumVertices = m->mNumFaces * 4];
209 
210             aiVector3D *uv(nullptr);
211             float step_y(0.0f), step_x(0.0f);
212             if (configComputeUVs) {
213                 uv = m->mTextureCoords[0] = new aiVector3D[m->mNumVertices];
214                 step_y = 1.f / y;
215                 step_x = 1.f / x;
216             }
217             const int16_t *data = (const int16_t *)reader.GetPtr();
218 
219             for (unsigned int yy = 0, t = 0; yy < y - 1; ++yy) {
220                 for (unsigned int xx = 0; xx < x - 1; ++xx, ++f) {
221 
222                     // make verts
223                     const float fy = (float)yy, fx = (float)xx;
224                     unsigned tmp, tmp2;
225                     *pv++ = aiVector3D(fx, fy, (float)data[(tmp2 = x * yy) + xx] * hscale + bheight);
226                     *pv++ = aiVector3D(fx, fy + 1, (float)data[(tmp = x * (yy + 1)) + xx] * hscale + bheight);
227                     *pv++ = aiVector3D(fx + 1, fy + 1, (float)data[tmp + xx + 1] * hscale + bheight);
228                     *pv++ = aiVector3D(fx + 1, fy, (float)data[tmp2 + xx + 1] * hscale + bheight);
229 
230                     // also make texture coordinates, if necessary
231                     if (configComputeUVs) {
232                         *uv++ = aiVector3D(step_x * xx, step_y * yy, 0.f);
233                         *uv++ = aiVector3D(step_x * xx, step_y * (yy + 1), 0.f);
234                         *uv++ = aiVector3D(step_x * (xx + 1), step_y * (yy + 1), 0.f);
235                         *uv++ = aiVector3D(step_x * (xx + 1), step_y * yy, 0.f);
236                     }
237 
238                     // make indices
239                     f->mIndices = new unsigned int[f->mNumIndices = 4];
240                     for (unsigned int i = 0; i < 4; ++i) {
241                         f->mIndices[i] = t;
242                         t++;
243                     }
244                 }
245             }
246 
247             // Add the mesh to the root node
248             root->mMeshes = new unsigned int[root->mNumMeshes = 1];
249             root->mMeshes[0] = 0;
250         }
251 
252         // Get to the next chunk (4 byte aligned)
253         unsigned dtt = reader.GetCurrentPos();
254         if (dtt & 0x3) {
255             reader.IncPtr(4 - dtt);
256         }
257     }
258 
259     // Check whether we have a mesh now
260     if (pScene->mNumMeshes != 1)
261         throw DeadlyImportError("TER: Unable to load terrain");
262 
263     // Set the AI_SCENE_FLAGS_TERRAIN bit
264     pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN;
265 }
266 
267 #endif // !! ASSIMP_BUILD_NO_TERRAGEN_IMPORTER
268