123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "terrain/terrData.h"
- #include "gfx/bitmap/gBitmap.h"
- #include "sim/netConnection.h"
- #include "core/strings/stringUnit.h"
- #include "core/resourceManager.h"
- #include "gui/worldEditor/terrainEditor.h"
- #include "util/noise2d.h"
- #include "core/volume.h"
- #include "T3D/Scene.h"
- using namespace Torque;
- DefineEngineStaticMethod( TerrainBlock, createNew, S32, (String terrainName, U32 resolution, String materialName, bool genNoise),,
- "" )
- {
- Vector<String> materials;
- materials.push_back( materialName );
- TerrainBlock *terrain = new TerrainBlock();
- // We create terrains based on level name. If the user wants to rename the terrain names; they have to
- // rename it themselves in their file browser. The main reason for this is so we can easily increment for ourselves;
- // and because its too easy to rename the terrain object and forget to take care of the terrain filename afterwards.
-
- String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
- if ( terrainDirectory.isEmpty() )
- {
- terrainDirectory = "data/terrains/";
- }
- String terrFileName = terrainDirectory + "/" + terrainName + ".ter";
- TerrainFile::create( &terrFileName, resolution, materials );
- if( !terrain->setFile( terrFileName ) )
- {
- Con::errorf( "TerrainBlock::createNew - error creating '%s'", terrFileName.c_str() );
- return 0;
- }
-
- terrain->setPosition( Point3F( 0, 0, 0 ) );
- const U32 blockSize = terrain->getBlockSize();
- if ( genNoise )
- {
- TerrainFile *file = terrain->getFile();
- Vector<F32> floatHeights;
- floatHeights.setSize( blockSize * blockSize );
- Noise2D noise;
- noise.setSeed( 134208587 );
-
- // Set up some defaults.
- const F32 octaves = 3.0f;
- const U32 freq = 4;
- const F32 roughness = 0.0f;
- noise.fBm( &floatHeights, blockSize, freq, 1.0f - roughness, octaves );
- F32 height = 0;
- F32 omax, omin;
- noise.getMinMax( &floatHeights, &omin, &omax, blockSize );
- const F32 terrscale = 300.0f / (omax - omin);
- for ( S32 y = 0; y < blockSize; y++ )
- {
- for ( S32 x = 0; x < blockSize; x++ )
- {
- // Very important to subtract the min
- // noise value when using the noise functions
- // for terrain, otherwise floatToFixed() will
- // wrap negative values to U16_MAX, creating
- // a very ugly terrain.
- height = (floatHeights[ x + (y * blockSize) ] - omin) * terrscale + 30.0f;
- file->setHeight( x, y, floatToFixed( height ) );
- }
- }
- terrain->updateGrid( Point2I::Zero, Point2I( blockSize, blockSize ) );
- terrain->updateGridMaterials( Point2I::Zero, Point2I( blockSize, blockSize ) );
- }
- terrain->registerObject( terrainName.c_str() );
- // Add to mission group!
- Scene* scene = Scene::getRootScene();
- if(scene)
- scene->addObject( terrain );
- return terrain->getId();
- }
- DefineEngineStaticMethod( TerrainBlock, import, S32, (S32 terrainObjectId, String heightMapFile, F32 metersPerPixel, F32 heightScale, String opacityLayerFiles, String materialsStr, bool flipYAxis), (true),
- "" )
- {
- // First load the height map and validate it.
- Resource<GBitmap> heightmap = GBitmap::load(heightMapFile);
- if ( !heightmap )
- {
- Con::errorf( "Heightmap failed to load!" );
- return 0;
- }
- U32 terrSize = heightmap->getWidth();
- U32 hheight = heightmap->getHeight();
- if ( terrSize != hheight || !isPow2( terrSize ) )
- {
- Con::errorf( "Height map must be square and power of two in size!" );
- return 0;
- }
- else if ( terrSize < 128 || terrSize > 4096 )
- {
- Con::errorf( "Height map must be between 128 and 4096 in size!" );
- return 0;
- }
- U32 fileCount = StringUnit::getUnitCount(opacityLayerFiles, "\n" );
- Vector<U8> layerMap;
- layerMap.setSize( terrSize * terrSize );
- {
- Vector<GBitmap*> bitmaps;
-
- for ( U32 i = 0; i < fileCount; i++ )
- {
- String fileNameWithChannel = StringUnit::getUnit(opacityLayerFiles, i, "\n" );
- String fileName = StringUnit::getUnit( fileNameWithChannel, 0, "\t" );
- String channel = StringUnit::getUnit( fileNameWithChannel, 1, "\t" );
-
- if ( fileName.isEmpty() )
- continue;
- if ( !channel.isEmpty() )
- {
- // Load and push back the bitmap here.
- Resource<GBitmap> opacityMap = ResourceManager::get().load( fileName );
- if ( terrSize != opacityMap->getWidth() || terrSize != opacityMap->getHeight() )
- {
- Con::errorf( "The opacity map '%s' doesn't match height map size!", fileName.c_str() );
- return 0;
- }
- // Always going to be one channel.
- GBitmap *opacityMapChannel = new GBitmap( terrSize,
- terrSize,
- false,
- GFXFormatA8 );
- if ( opacityMap->getBytesPerPixel() > 1 )
- {
- if ( channel.equal( "R", 1 ) )
- opacityMap->copyChannel( 0, opacityMapChannel );
- else if ( channel.equal( "G", 1 ) )
- opacityMap->copyChannel( 1, opacityMapChannel );
- else if ( channel.equal( "B", 1 ) )
- opacityMap->copyChannel( 2, opacityMapChannel );
- else if ( channel.equal( "A", 1 ) )
- opacityMap->copyChannel( 3, opacityMapChannel );
- bitmaps.push_back( opacityMapChannel );
- }
- else
- {
- opacityMapChannel->copyRect( opacityMap, RectI( 0, 0, terrSize, terrSize ), Point2I( 0, 0 ) );
- bitmaps.push_back( opacityMapChannel );
- }
- }
- }
- // Ok... time to convert all this opacity layer
- // mess to the layer index map!
- U32 layerCount = bitmaps.size() - 1;
- U32 layer, lastValue;
- U8 value;
- for ( U32 i = 0; i < terrSize * terrSize; i++ )
- {
- // Find the greatest layer.
- layer = lastValue = 0;
- for ( U32 k=0; k < bitmaps.size(); k++ )
- {
- value = bitmaps[k]->getBits()[i];
- if ( value >= lastValue )
- {
- layer = k;
- lastValue = value;
- }
- }
- // Set the layer index.
- layerMap[i] = getMin( layer, layerCount );
- }
- // Cleanup the bitmaps.
- for ( U32 i=0; i < bitmaps.size(); i++ )
- delete bitmaps[i];
- }
- U32 matCount = StringUnit::getUnitCount( materialsStr, "\t\n" );
- if( matCount != fileCount)
- {
- Con::errorf("Number of Materials and Layer maps must be equal.");
- return 0;
- }
- Vector<String> materials;
- for ( U32 i = 0; i < matCount; i++ )
- {
- String matStr = StringUnit::getUnit( materialsStr, i, "\t\n" );
- // even if matStr is empty, insert it as a placeholder (will be replaced with warning material later)
- materials.push_back( matStr );
- }
- // Do we have an existing terrain with that name... then update it!
- TerrainBlock *terrain = dynamic_cast<TerrainBlock*>( Sim::findObject( terrainObjectId ) );
- if ( terrain )
- terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials, flipYAxis );
- else
- {
- terrain = new TerrainBlock();
- terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials, flipYAxis );
- terrain->registerObject();
- // Add to scene!
- Scene* scene = Scene::getRootScene();
- if (scene)
- scene->addObject( terrain );
- }
- return terrain->getId();
- }
- bool TerrainBlock::import( const GBitmap &heightMap,
- F32 heightScale,
- F32 metersPerPixel,
- const Vector<U8> &layerMap,
- const Vector<String> &materials,
- bool flipYAxis)
- {
- AssertFatal( isServerObject(), "TerrainBlock::import - This should only be called on the server terrain!" );
- AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainBlock::import - Height map is not square!" );
- AssertFatal( isPow2( heightMap.getWidth() ), "TerrainBlock::import - Height map is not power of two!" );
- // If we don't have a terrain file then add one.
- if ( !mFile )
- {
- // Get a unique file name for the terrain.
- String fileName( getName() );
- if (fileName.isEmpty())
- {
- fileName = Torque::Path(Con::getVariable("$Client::MissionFile")).getFileName();
- if (fileName.isEmpty())
- fileName = "terrain";
- }
- String terrainFileName = FS::MakeUniquePath( "levels", fileName, "ter" );
- if (!TerrainAsset::getAssetByFilename(terrainFileName, &mTerrainAsset))
- {
- return false;
- }
- else
- {
- mFile = mTerrainAsset->getTerrainResource();
- }
- /*// TODO: We have to save and reload the file to get
- // it into the resource system. This creates lots
- // of temporary unused files when the terrain is
- // discarded because of undo or quit.
- TerrainFile *file = new TerrainFile;
- file->save( mTerrFileName );
- delete file;
- mFile = ResourceManager::get().load( mTerrFileName );*/
- }
- // The file does a bunch of the work.
- mFile->import( heightMap, heightScale, layerMap, materials, flipYAxis );
- // Set the square size.
- mSquareSize = metersPerPixel;
- if ( isProperlyAdded() )
- {
- // Update the server bounds.
- _updateBounds();
- // Make sure the client gets updated.
- setMaskBits( HeightMapChangeMask | SizeMask );
- }
- return true;
- }
|