terrImport.cpp 11 KB

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