//----------------------------------------------------------------------------- // 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 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 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 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 layerMap; layerMap.setSize( terrSize * terrSize ); { Vector 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 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 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( 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 &layerMap, const Vector &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; }