terrImport.cpp

Engine/source/terrain/terrImport.cpp

More...

Public Functions

DefineEngineStaticMethod(TerrainBlock , createNew , S32 , (String terrainName, U32 resolution, String materialName, bool genNoise) , "" )
DefineEngineStaticMethod(TerrainBlock , import , S32 , (S32 terrainObjectId, String heightMapFile, F32 metersPerPixel, F32 heightScale, String opacityLayerFiles, String materialsStr, bool flipYAxis) , (true) , "" )

Detailed Description

Public Functions

DefineEngineStaticMethod(TerrainBlock , createNew , S32 , (String terrainName, U32 resolution, String materialName, bool genNoise) , "" )

DefineEngineStaticMethod(TerrainBlock , import , S32 , (S32 terrainObjectId, String heightMapFile, F32 metersPerPixel, F32 heightScale, String opacityLayerFiles, String materialsStr, bool flipYAxis) , (true) , "" )

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25
 26#include "terrain/terrData.h"
 27#include "gfx/bitmap/gBitmap.h"
 28#include "sim/netConnection.h"
 29#include "core/strings/stringUnit.h"
 30#include "core/resourceManager.h"
 31#include "gui/worldEditor/terrainEditor.h"
 32#include "util/noise2d.h"
 33#include "core/volume.h"
 34
 35#include "T3D/Scene.h"
 36
 37using namespace Torque;
 38
 39DefineEngineStaticMethod( TerrainBlock, createNew, S32, (String terrainName, U32 resolution, String materialName, bool genNoise),, 
 40   "" )
 41{
 42   Vector<String> materials;
 43   materials.push_back( materialName );
 44
 45   TerrainBlock *terrain = new TerrainBlock();
 46
 47   // We create terrains based on level name. If the user wants to rename the terrain names; they have to
 48   // rename it themselves in their file browser. The main reason for this is so we can easily increment for ourselves;
 49   // and because its too easy to rename the terrain object and forget to take care of the terrain filename afterwards.
 50 
 51   String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
 52   if ( terrainDirectory.isEmpty() )
 53   {
 54      terrainDirectory = "data/terrains/";
 55   }
 56
 57   String terrFileName = terrainDirectory + "/" + terrainName + ".ter";
 58
 59   TerrainFile::create( &terrFileName, resolution, materials );
 60
 61   if( !terrain->setFile( terrFileName ) )
 62   {
 63      Con::errorf( "TerrainBlock::createNew - error creating '%s'", terrFileName.c_str() );
 64      return 0;
 65   }
 66   
 67   terrain->setPosition( Point3F( 0, 0, 0 ) );
 68
 69   const U32 blockSize = terrain->getBlockSize();
 70
 71   if ( genNoise )
 72   {
 73      TerrainFile *file = terrain->getFile();
 74
 75      Vector<F32> floatHeights;
 76      floatHeights.setSize( blockSize * blockSize );
 77
 78      Noise2D noise;
 79      noise.setSeed( 134208587 );
 80      
 81      // Set up some defaults.
 82      const F32 octaves = 3.0f;
 83      const U32 freq = 4;
 84      const F32 roughness = 0.0f;
 85      noise.fBm( &floatHeights, blockSize, freq, 1.0f - roughness, octaves );
 86
 87      F32 height = 0;
 88
 89      F32 omax, omin;
 90      noise.getMinMax( &floatHeights, &omin, &omax, blockSize );
 91
 92      const F32 terrscale = 300.0f / (omax - omin);
 93      for ( S32 y = 0; y < blockSize; y++ )
 94      {
 95         for ( S32 x = 0; x < blockSize; x++ )
 96         {
 97            // Very important to subtract the min
 98            // noise value when using the noise functions
 99            // for terrain, otherwise floatToFixed() will
100            // wrap negative values to U16_MAX, creating
101            // a very ugly terrain.
102            height = (floatHeights[ x + (y * blockSize) ] - omin) * terrscale + 30.0f;
103            file->setHeight( x, y, floatToFixed( height ) );
104         }
105      }
106
107      terrain->updateGrid( Point2I::Zero, Point2I( blockSize, blockSize ) );
108      terrain->updateGridMaterials( Point2I::Zero, Point2I( blockSize, blockSize ) );
109   }
110
111   terrain->registerObject( terrainName.c_str() );
112
113   // Add to mission group!
114   Scene* scene = Scene::getRootScene();
115   if(scene)
116      scene->addObject( terrain );
117
118   return terrain->getId();
119}
120
121DefineEngineStaticMethod( TerrainBlock, import, S32, (S32 terrainObjectId, String heightMapFile, F32 metersPerPixel, F32 heightScale, String opacityLayerFiles, String materialsStr, bool flipYAxis), (true),
122   "" )
123{
124   // First load the height map and validate it.
125   Resource<GBitmap> heightmap = GBitmap::load(heightMapFile);
126   if ( !heightmap )
127   {
128      Con::errorf( "Heightmap failed to load!" );
129      return 0;
130   }
131
132   U32 terrSize = heightmap->getWidth();
133   U32 hheight = heightmap->getHeight();
134   if ( terrSize != hheight || !isPow2( terrSize ) )
135   {
136      Con::errorf( "Height map must be square and power of two in size!" );
137      return 0;
138   }
139   else if ( terrSize < 128 || terrSize > 4096 )
140   {
141      Con::errorf( "Height map must be between 128 and 4096 in size!" );
142      return 0;
143   }
144
145   U32 fileCount = StringUnit::getUnitCount(opacityLayerFiles, "\n" );
146   Vector<U8> layerMap;
147   layerMap.setSize( terrSize * terrSize );
148   {
149      Vector<GBitmap*> bitmaps;
150  
151      for ( U32 i = 0; i < fileCount; i++ )
152      {
153         String fileNameWithChannel = StringUnit::getUnit(opacityLayerFiles, i, "\n" );
154         String fileName = StringUnit::getUnit( fileNameWithChannel, 0, "\t" );
155         String channel = StringUnit::getUnit( fileNameWithChannel, 1, "\t" );
156         
157         if ( fileName.isEmpty() )
158            continue;
159
160         if ( !channel.isEmpty() )
161         {
162            // Load and push back the bitmap here.
163            Resource<GBitmap> opacityMap = ResourceManager::get().load( fileName );
164            if ( terrSize != opacityMap->getWidth() || terrSize != opacityMap->getHeight() )
165            {
166               Con::errorf( "The opacity map '%s' doesn't match height map size!", fileName.c_str() );
167               return 0;
168            }
169
170            // Always going to be one channel.
171            GBitmap *opacityMapChannel = new GBitmap( terrSize, 
172                                                      terrSize, 
173                                                      false, 
174                                                      GFXFormatA8 );         
175
176            if ( opacityMap->getBytesPerPixel() > 1 )
177            {
178               if ( channel.equal( "R", 1 ) )
179                  opacityMap->copyChannel( 0, opacityMapChannel );
180               else if ( channel.equal( "G", 1 ) )
181                  opacityMap->copyChannel( 1, opacityMapChannel );
182               else if ( channel.equal( "B", 1 ) )
183                  opacityMap->copyChannel( 2, opacityMapChannel );
184               else if ( channel.equal( "A", 1 ) )
185                  opacityMap->copyChannel( 3, opacityMapChannel );
186
187               bitmaps.push_back( opacityMapChannel );
188            }
189            else
190            {
191               opacityMapChannel->copyRect( opacityMap, RectI( 0, 0, terrSize, terrSize ), Point2I( 0, 0 ) );
192               bitmaps.push_back( opacityMapChannel );
193            }
194         }
195      }
196
197      // Ok... time to convert all this opacity layer 
198      // mess to the layer index map!
199      U32 layerCount = bitmaps.size() - 1;
200      U32 layer, lastValue;
201      U8 value;
202      for ( U32 i = 0; i < terrSize * terrSize; i++ )
203      {
204         // Find the greatest layer.
205         layer = lastValue = 0;
206         for ( U32 k=0; k < bitmaps.size(); k++ )
207         {
208            value = bitmaps[k]->getBits()[i];
209            if ( value >= lastValue )
210            {
211               layer = k;
212               lastValue = value;
213            }
214         }
215
216         // Set the layer index.   
217         layerMap[i] = getMin( layer, layerCount );
218      }
219
220      // Cleanup the bitmaps.
221      for ( U32 i=0; i < bitmaps.size(); i++ )
222         delete bitmaps[i];
223   }
224
225   U32 matCount = StringUnit::getUnitCount( materialsStr, "\t\n" );
226   if( matCount != fileCount)
227   {
228      Con::errorf("Number of Materials and Layer maps must be equal.");
229      return 0;
230   }
231
232   Vector<String> materials;
233   for ( U32 i = 0; i < matCount; i++ )
234   {
235      String matStr = StringUnit::getUnit( materialsStr, i, "\t\n" );
236      // even if matStr is empty, insert it as a placeholder (will be replaced with warning material later)
237      materials.push_back( matStr );
238   }
239
240   // Do we have an existing terrain with that name... then update it!
241   TerrainBlock *terrain = dynamic_cast<TerrainBlock*>( Sim::findObject( terrainObjectId ) );
242   if ( terrain )
243      terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials, flipYAxis );
244   else
245   {
246      terrain = new TerrainBlock();
247      terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials, flipYAxis );
248      terrain->registerObject();
249
250      // Add to scene!
251      Scene* scene = Scene::getRootScene();
252      if (scene)
253         scene->addObject( terrain );
254   }
255
256   return terrain->getId();
257}
258
259bool TerrainBlock::import( const GBitmap &heightMap, 
260                           F32 heightScale, 
261                           F32 metersPerPixel,
262                           const Vector<U8> &layerMap, 
263                           const Vector<String> &materials,
264                           bool flipYAxis)
265{
266   AssertFatal( isServerObject(), "TerrainBlock::import - This should only be called on the server terrain!" );
267
268   AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainBlock::import - Height map is not square!" );
269   AssertFatal( isPow2( heightMap.getWidth() ), "TerrainBlock::import - Height map is not power of two!" );
270
271   // If we don't have a terrain file then add one.
272   if ( !mFile )
273   {
274      // Get a unique file name for the terrain.
275      String fileName( getName() );
276      if (fileName.isEmpty())
277      {
278         fileName = Torque::Path(Con::getVariable("$Client::MissionFile")).getFileName();
279
280         if (fileName.isEmpty())
281            fileName = "terrain";
282      }
283      String terrainFileName = FS::MakeUniquePath( "levels", fileName, "ter" );
284
285      if (!TerrainAsset::getAssetByFilename(terrainFileName, &mTerrainAsset))
286      {
287         return false;
288      }
289      else
290      {
291         mFile = mTerrainAsset->getTerrainResource();
292      }
293
294      /*// TODO: We have to save and reload the file to get
295      // it into the resource system.  This creates lots
296      // of temporary unused files when the terrain is
297      // discarded because of undo or quit.
298      TerrainFile *file = new TerrainFile;
299      file->save( mTerrFileName );
300      delete file;
301      mFile = ResourceManager::get().load( mTerrFileName );*/
302   }
303
304   // The file does a bunch of the work.
305   mFile->import( heightMap, heightScale, layerMap, materials, flipYAxis );
306
307   // Set the square size.
308   mSquareSize = metersPerPixel;
309
310   if ( isProperlyAdded() )
311   {
312      // Update the server bounds.
313      _updateBounds();
314
315      // Make sure the client gets updated.
316      setMaskBits( HeightMapChangeMask | SizeMask );
317   }
318
319   return true;
320}
321
322