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