mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
Fixes the terrain asset creation, makes the loading logic go through the asset auto-import behavior when a filename or assetid is bound that is not found. Corrects terrain material binding to properly save and load Makes the terrain asset inspector fields work as expected.
322 lines
11 KiB
C++
322 lines
11 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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, (String terrainName, 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( terrainName.c_str() ) );
|
|
if ( terrain )
|
|
terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials, flipYAxis );
|
|
else
|
|
{
|
|
terrain = new TerrainBlock();
|
|
terrain->assignName( terrainName );
|
|
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;
|
|
}
|
|
|