/src/assimp/code/AssetLib/Terragen/TerragenLoader.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2025, 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 | | namespace Assimp { |
55 | | |
56 | | static constexpr 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 |
71 | | TerragenImporter::TerragenImporter() : |
72 | 220 | configComputeUVs(false) { |
73 | | // empty |
74 | 220 | } |
75 | | |
76 | | // ------------------------------------------------------------------------------------------------ |
77 | | // Returns whether the class can handle the format of the given file. |
78 | 112 | bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { |
79 | 112 | static const char *tokens[] = { "terragen" }; |
80 | 112 | return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); |
81 | 112 | } |
82 | | |
83 | | // ------------------------------------------------------------------------------------------------ |
84 | | // Build a string of all file extensions supported |
85 | 210 | const aiImporterDesc *TerragenImporter::GetInfo() const { |
86 | 210 | return &desc; |
87 | 210 | } |
88 | | |
89 | | // ------------------------------------------------------------------------------------------------ |
90 | | // Setup import properties |
91 | 0 | void TerragenImporter::SetupProperties(const Importer *pImp) { |
92 | | // AI_CONFIG_IMPORT_TER_MAKE_UVS |
93 | 0 | configComputeUVs = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_TER_MAKE_UVS, 0)); |
94 | 0 | } |
95 | | |
96 | | // ------------------------------------------------------------------------------------------------ |
97 | | // Imports the given file into the given scene structure. |
98 | | void TerragenImporter::InternReadFile(const std::string &pFile, |
99 | 0 | aiScene *pScene, IOSystem *pIOHandler) { |
100 | 0 | IOStream *file = pIOHandler->Open(pFile, "rb"); |
101 | | |
102 | | // Check whether we can read from the file |
103 | 0 | if (file == nullptr) |
104 | 0 | throw DeadlyImportError("Failed to open TERRAGEN TERRAIN file ", pFile, "."); |
105 | | |
106 | | // Construct a stream reader to read all data in the correct endianness |
107 | 0 | StreamReaderLE reader(file); |
108 | 0 | if (reader.GetRemainingSize() < 16) |
109 | 0 | throw DeadlyImportError("TER: file is too small"); |
110 | | |
111 | | // Check for the existence of the two magic strings 'TERRAGEN' and 'TERRAIN ' |
112 | 0 | if (::strncmp((const char *)reader.GetPtr(), AI_TERR_BASE_STRING, 8)) |
113 | 0 | throw DeadlyImportError("TER: Magic string \'TERRAGEN\' not found"); |
114 | | |
115 | 0 | if (::strncmp((const char *)reader.GetPtr() + 8, AI_TERR_TERRAIN_STRING, 8)) |
116 | 0 | throw DeadlyImportError("TER: Magic string \'TERRAIN\' not found"); |
117 | | |
118 | 0 | unsigned int x = 0, y = 0, mode = 0; |
119 | |
|
120 | 0 | aiNode *root = pScene->mRootNode = new aiNode(); |
121 | 0 | root->mName.Set("<TERRAGEN.TERRAIN>"); |
122 | | |
123 | | // Default scaling is 30 |
124 | 0 | root->mTransformation.a1 = root->mTransformation.b2 = root->mTransformation.c3 = 30.f; |
125 | | |
126 | | // Now read all chunks until we're finished or an EOF marker is encountered |
127 | 0 | reader.IncPtr(16); |
128 | 0 | while (reader.GetRemainingSize() >= 4) { |
129 | 0 | const char *head = (const char *)reader.GetPtr(); |
130 | 0 | reader.IncPtr(4); |
131 | | |
132 | | // EOF, break in every case |
133 | 0 | if (!::strncmp(head, AI_TERR_EOF_STRING, 4)) |
134 | 0 | break; |
135 | | |
136 | | // Number of x-data points |
137 | 0 | if (!::strncmp(head, AI_TERR_CHUNK_XPTS, 4)) { |
138 | 0 | x = (uint16_t)reader.GetI2(); |
139 | 0 | } |
140 | | // Number of y-data points |
141 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_YPTS, 4)) { |
142 | 0 | y = (uint16_t)reader.GetI2(); |
143 | 0 | } |
144 | | // Squared terrains width-1. |
145 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_SIZE, 4)) { |
146 | 0 | x = y = (uint16_t)reader.GetI2() + 1; |
147 | 0 | } |
148 | | // terrain scaling |
149 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_SCAL, 4)) { |
150 | 0 | root->mTransformation.a1 = reader.GetF4(); |
151 | 0 | root->mTransformation.b2 = reader.GetF4(); |
152 | 0 | root->mTransformation.c3 = reader.GetF4(); |
153 | 0 | } |
154 | | // mapping == 1: earth radius |
155 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_CRAD, 4)) { |
156 | 0 | reader.GetF4(); |
157 | 0 | } |
158 | | // mapping mode |
159 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_CRVM, 4)) { |
160 | 0 | mode = reader.GetI1(); |
161 | 0 | if (0 != mode) |
162 | 0 | ASSIMP_LOG_ERROR("TER: Unsupported mapping mode, a flat terrain is returned"); |
163 | 0 | } |
164 | | // actual terrain data |
165 | 0 | else if (!::strncmp(head, AI_TERR_CHUNK_ALTW, 4)) { |
166 | 0 | float hscale = (float)reader.GetI2() / 65536; |
167 | 0 | float bheight = (float)reader.GetI2(); |
168 | |
|
169 | 0 | if (!hscale) hscale = 1; |
170 | | |
171 | | // Ensure we have enough data |
172 | 0 | if (reader.GetRemainingSize() < x * y * 2) |
173 | 0 | throw DeadlyImportError("TER: ALTW chunk is too small"); |
174 | | |
175 | 0 | if (x <= 1 || y <= 1) |
176 | 0 | throw DeadlyImportError("TER: Invalid terrain size"); |
177 | | |
178 | | // Allocate the output mesh |
179 | 0 | pScene->mMeshes = new aiMesh *[pScene->mNumMeshes = 1]; |
180 | 0 | aiMesh *m = pScene->mMeshes[0] = new aiMesh(); |
181 | | |
182 | | // We return quads |
183 | 0 | aiFace *f = m->mFaces = new aiFace[m->mNumFaces = (x - 1) * (y - 1)]; |
184 | 0 | aiVector3D *pv = m->mVertices = new aiVector3D[m->mNumVertices = m->mNumFaces * 4]; |
185 | |
|
186 | 0 | aiVector3D *uv(nullptr); |
187 | 0 | float step_y(0.0f), step_x(0.0f); |
188 | 0 | if (configComputeUVs) { |
189 | 0 | uv = m->mTextureCoords[0] = new aiVector3D[m->mNumVertices]; |
190 | 0 | step_y = 1.f / y; |
191 | 0 | step_x = 1.f / x; |
192 | 0 | } |
193 | 0 | const int16_t *data = (const int16_t *)reader.GetPtr(); |
194 | |
|
195 | 0 | for (unsigned int yy = 0, t = 0; yy < y - 1; ++yy) { |
196 | 0 | for (unsigned int xx = 0; xx < x - 1; ++xx, ++f) { |
197 | | |
198 | | // make verts |
199 | 0 | const float fy = (float)yy, fx = (float)xx; |
200 | 0 | unsigned tmp, tmp2; |
201 | 0 | *pv++ = aiVector3D(fx, fy, (float)data[(tmp2 = x * yy) + xx] * hscale + bheight); |
202 | 0 | *pv++ = aiVector3D(fx, fy + 1, (float)data[(tmp = x * (yy + 1)) + xx] * hscale + bheight); |
203 | 0 | *pv++ = aiVector3D(fx + 1, fy + 1, (float)data[tmp + xx + 1] * hscale + bheight); |
204 | 0 | *pv++ = aiVector3D(fx + 1, fy, (float)data[tmp2 + xx + 1] * hscale + bheight); |
205 | | |
206 | | // also make texture coordinates, if necessary |
207 | 0 | if (configComputeUVs) { |
208 | 0 | *uv++ = aiVector3D(step_x * xx, step_y * yy, 0.f); |
209 | 0 | *uv++ = aiVector3D(step_x * xx, step_y * (yy + 1), 0.f); |
210 | 0 | *uv++ = aiVector3D(step_x * (xx + 1), step_y * (yy + 1), 0.f); |
211 | 0 | *uv++ = aiVector3D(step_x * (xx + 1), step_y * yy, 0.f); |
212 | 0 | } |
213 | | |
214 | | // make indices |
215 | 0 | f->mIndices = new unsigned int[f->mNumIndices = 4]; |
216 | 0 | for (unsigned int i = 0; i < 4; ++i) { |
217 | 0 | f->mIndices[i] = t; |
218 | 0 | t++; |
219 | 0 | } |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | | // Add the mesh to the root node |
224 | 0 | root->mMeshes = new unsigned int[root->mNumMeshes = 1]; |
225 | 0 | root->mMeshes[0] = 0; |
226 | 0 | } |
227 | | |
228 | | // Get to the next chunk (4 byte aligned) |
229 | 0 | unsigned dtt = reader.GetCurrentPos() & 0x3; |
230 | 0 | if (dtt) { |
231 | 0 | reader.IncPtr(4 - dtt); |
232 | 0 | } |
233 | 0 | } |
234 | | |
235 | | // Check whether we have a mesh now |
236 | 0 | if (pScene->mNumMeshes != 1) |
237 | 0 | throw DeadlyImportError("TER: Unable to load terrain"); |
238 | | |
239 | | // Set the AI_SCENE_FLAGS_TERRAIN bit |
240 | 0 | pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN; |
241 | 0 | } |
242 | | |
243 | | } // namespace Assimp |
244 | | |
245 | | #endif // !! ASSIMP_BUILD_NO_TERRAGEN_IMPORTER |