terrImport.cpp
Engine/source/terrain/terrImport.cpp
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