Coverage Report

Created: 2025-08-26 06:41

/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