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