Coverage Report

Created: 2026-10-28 07:46

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