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.
885 lines
26 KiB
C++
885 lines
26 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/terrFile.h"
|
|
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/resourceManager.h"
|
|
#include "terrain/terrMaterial.h"
|
|
#include "gfx/gfxTextureHandle.h"
|
|
#include "gfx/bitmap/gBitmap.h"
|
|
#include "platform/profiler.h"
|
|
#include "math/mPlane.h"
|
|
|
|
|
|
template<>
|
|
void* Resource<TerrainFile>::create( const Torque::Path &path )
|
|
{
|
|
return TerrainFile::load( path );
|
|
}
|
|
|
|
template<> ResourceBase::Signature Resource<TerrainFile>::signature()
|
|
{
|
|
return MakeFourCC('t','e','r','d');
|
|
}
|
|
|
|
|
|
TerrainFile::TerrainFile()
|
|
: mSize( 256 ),
|
|
mGridLevels(0),
|
|
mFileVersion( FILE_VERSION ),
|
|
mNeedsResaving( false )
|
|
{
|
|
mLayerMap.setSize( mSize * mSize );
|
|
dMemset( mLayerMap.address(), 0, mLayerMap.memSize() );
|
|
|
|
mHeightMap.setSize( mSize * mSize );
|
|
dMemset( mHeightMap.address(), 0, mHeightMap.memSize() );
|
|
}
|
|
|
|
TerrainFile::~TerrainFile()
|
|
{
|
|
}
|
|
|
|
static U16 calcDev( const PlaneF &pl, const Point3F &pt )
|
|
{
|
|
F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z;
|
|
F32 diff = z - pt.z;
|
|
if(diff < 0.0f)
|
|
diff = -diff;
|
|
|
|
if(diff > 0xFFFF)
|
|
return 0xFFFF;
|
|
else
|
|
return U16(diff);
|
|
}
|
|
|
|
static U16 Umax( U16 u1, U16 u2 )
|
|
{
|
|
return u1 > u2 ? u1 : u2;
|
|
}
|
|
|
|
|
|
inline U32 getMostSignificantBit( U32 v )
|
|
{
|
|
U32 bit = 0;
|
|
|
|
while ( v >>= 1 )
|
|
bit++;
|
|
|
|
return bit;
|
|
}
|
|
|
|
void TerrainFile::_buildGridMap()
|
|
{
|
|
// The grid level count is the same as the
|
|
// most significant bit of the size. While
|
|
// we loop we take the time to calculate the
|
|
// grid memory pool size.
|
|
mGridLevels = 0;
|
|
U32 size = mSize;
|
|
U32 poolSize = size * size;
|
|
while ( size >>= 1 )
|
|
{
|
|
poolSize += size * size;
|
|
mGridLevels++;
|
|
}
|
|
|
|
mGridMapPool.setSize( poolSize );
|
|
mGridMapPool.compact();
|
|
mGridMap.setSize( mGridLevels + 1 );
|
|
mGridMap.compact();
|
|
|
|
// Assign memory from the pool to each grid level.
|
|
TerrainSquare *grid = mGridMapPool.address();
|
|
for ( S32 i = mGridLevels; i >= 0; i-- )
|
|
{
|
|
mGridMap[i] = grid;
|
|
grid += 1 << ( 2 * ( mGridLevels - i ) );
|
|
}
|
|
|
|
for( S32 i = mGridLevels; i >= 0; i-- )
|
|
{
|
|
S32 squareCount = 1 << ( mGridLevels - i );
|
|
S32 squareSize = mSize / squareCount;
|
|
|
|
for ( S32 squareX = 0; squareX < squareCount; squareX++ )
|
|
{
|
|
for ( S32 squareY = 0; squareY < squareCount; squareY++ )
|
|
{
|
|
U16 min = 0xFFFF;
|
|
U16 max = 0;
|
|
U16 mindev45 = 0;
|
|
U16 mindev135 = 0;
|
|
|
|
// determine max error for both possible splits.
|
|
|
|
const Point3F p1(0, 0, getHeight(squareX * squareSize, squareY * squareSize));
|
|
const Point3F p2(0, (F32)squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize));
|
|
const Point3F p3((F32)squareSize, (F32)squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize));
|
|
const Point3F p4((F32)squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize));
|
|
|
|
// pl1, pl2 = split45, pl3, pl4 = split135
|
|
const PlaneF pl1(p1, p2, p3);
|
|
const PlaneF pl2(p1, p3, p4);
|
|
const PlaneF pl3(p1, p2, p4);
|
|
const PlaneF pl4(p2, p3, p4);
|
|
|
|
bool parentSplit45 = false;
|
|
TerrainSquare *parent = NULL;
|
|
if ( i < mGridLevels )
|
|
{
|
|
parent = findSquare( i+1, squareX * squareSize, squareY * squareSize );
|
|
parentSplit45 = parent->flags & TerrainSquare::Split45;
|
|
}
|
|
|
|
bool empty = true;
|
|
bool hasEmpty = false;
|
|
|
|
for ( S32 sizeX = 0; sizeX <= squareSize; sizeX++ )
|
|
{
|
|
for ( S32 sizeY = 0; sizeY <= squareSize; sizeY++ )
|
|
{
|
|
S32 x = squareX * squareSize + sizeX;
|
|
S32 y = squareY * squareSize + sizeY;
|
|
|
|
if(sizeX != squareSize && sizeY != squareSize)
|
|
{
|
|
if ( !isEmptyAt( x, y ) )
|
|
empty = false;
|
|
else
|
|
hasEmpty = true;
|
|
}
|
|
|
|
U16 ht = getHeight( x, y );
|
|
if ( ht < min )
|
|
min = ht;
|
|
if( ht > max )
|
|
max = ht;
|
|
|
|
Point3F pt( (F32)sizeX, (F32)sizeY, (F32)ht );
|
|
U16 dev;
|
|
|
|
if(sizeX < sizeY)
|
|
dev = calcDev(pl1, pt);
|
|
else if(sizeX > sizeY)
|
|
dev = calcDev(pl2, pt);
|
|
else
|
|
dev = Umax(calcDev(pl1, pt), calcDev(pl2, pt));
|
|
|
|
if(dev > mindev45)
|
|
mindev45 = dev;
|
|
|
|
if(sizeX + sizeY < squareSize)
|
|
dev = calcDev(pl3, pt);
|
|
else if(sizeX + sizeY > squareSize)
|
|
dev = calcDev(pl4, pt);
|
|
else
|
|
dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt));
|
|
|
|
if(dev > mindev135)
|
|
mindev135 = dev;
|
|
}
|
|
}
|
|
|
|
TerrainSquare *sq = findSquare( i, squareX * squareSize, squareY * squareSize );
|
|
sq->minHeight = min;
|
|
sq->maxHeight = max;
|
|
|
|
sq->flags = empty ? TerrainSquare::Empty : 0;
|
|
if ( hasEmpty )
|
|
sq->flags |= TerrainSquare::HasEmpty;
|
|
|
|
bool shouldSplit45 = ((squareX ^ squareY) & 1) == 0;
|
|
bool split45;
|
|
|
|
//split45 = shouldSplit45;
|
|
if ( i == 0 )
|
|
split45 = shouldSplit45;
|
|
else if( i < 4 && shouldSplit45 == parentSplit45 )
|
|
split45 = shouldSplit45;
|
|
else
|
|
split45 = mindev45 < mindev135;
|
|
|
|
//split45 = shouldSplit45;
|
|
if(split45)
|
|
{
|
|
sq->flags |= TerrainSquare::Split45;
|
|
sq->heightDeviance = mindev45;
|
|
}
|
|
else
|
|
sq->heightDeviance = mindev135;
|
|
|
|
if( parent )
|
|
if ( parent->heightDeviance < sq->heightDeviance )
|
|
parent->heightDeviance = sq->heightDeviance;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
for ( S32 y = 0; y < mSize; y += 2 )
|
|
{
|
|
for ( S32 x=0; x < mSize; x += 2 )
|
|
{
|
|
GridSquare *sq = findSquare(1, Point2I(x, y));
|
|
GridSquare *s1 = findSquare(0, Point2I(x, y));
|
|
GridSquare *s2 = findSquare(0, Point2I(x+1, y));
|
|
GridSquare *s3 = findSquare(0, Point2I(x, y+1));
|
|
GridSquare *s4 = findSquare(0, Point2I(x+1, y+1));
|
|
sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
void TerrainFile::_initMaterialInstMapping()
|
|
{
|
|
mMaterialInstMapping.clearMatInstList();
|
|
|
|
for( U32 i = 0; i < mMaterials.size(); ++ i )
|
|
{
|
|
Torque::Path path( mMaterials[ i ]->getDiffuseMap() );
|
|
mMaterialInstMapping.push_back( path.getFileName() );
|
|
}
|
|
|
|
mMaterialInstMapping.mapMaterials();
|
|
}
|
|
|
|
bool TerrainFile::save( const char *filename )
|
|
{
|
|
FileStream stream;
|
|
stream.open( filename, Torque::FS::File::Write );
|
|
if ( stream.getStatus() != Stream::Ok )
|
|
return false;
|
|
|
|
stream.write( (U8)FILE_VERSION );
|
|
|
|
stream.write( mSize );
|
|
|
|
// Write out the height map.
|
|
for ( U32 i=0; i < mHeightMap.size(); i++)
|
|
stream.write( mHeightMap[i] );
|
|
|
|
// Write out the layer map.
|
|
for ( U32 i=0; i < mLayerMap.size(); i++)
|
|
stream.write( mLayerMap[i] );
|
|
|
|
// Write out the material names.
|
|
stream.write( (U32)mMaterials.size() );
|
|
for ( U32 i=0; i < mMaterials.size(); i++ )
|
|
stream.write( String( mMaterials[i]->getInternalName() ) );
|
|
|
|
return stream.getStatus() == FileStream::Ok;
|
|
}
|
|
|
|
TerrainFile* TerrainFile::load( const Torque::Path &path )
|
|
{
|
|
FileStream stream;
|
|
|
|
stream.open( path.getFullPath(), Torque::FS::File::Read );
|
|
if ( stream.getStatus() != Stream::Ok )
|
|
{
|
|
Con::errorf( "Resource<TerrainFile>::create - could not open '%s'", path.getFullPath().c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
U8 version;
|
|
stream.read(&version);
|
|
if (version > TerrainFile::FILE_VERSION)
|
|
{
|
|
Con::errorf( "Resource<TerrainFile>::create - file version '%i' is newer than engine version '%i'", version, TerrainFile::FILE_VERSION );
|
|
return NULL;
|
|
}
|
|
|
|
TerrainFile *ret = new TerrainFile;
|
|
ret->mFileVersion = version;
|
|
ret->mFilePath = path;
|
|
|
|
if ( version >= 7 )
|
|
ret->_load( stream );
|
|
else
|
|
ret->_loadLegacy( stream );
|
|
|
|
// Update the collision structures.
|
|
ret->_buildGridMap();
|
|
|
|
// Do the material mapping.
|
|
ret->_initMaterialInstMapping();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void TerrainFile::_load( FileStream &stream )
|
|
{
|
|
// NOTE: We read using a loop instad of in one large chunk
|
|
// because the stream will do endian conversions for us when
|
|
// reading one type at a time.
|
|
|
|
stream.read( &mSize );
|
|
|
|
// Load the heightmap.
|
|
mHeightMap.setSize( mSize * mSize );
|
|
for ( U32 i=0; i < mHeightMap.size(); i++ )
|
|
stream.read( &mHeightMap[i] );
|
|
|
|
// Load the layer index map.
|
|
mLayerMap.setSize( mSize * mSize );
|
|
for ( U32 i=0; i < mLayerMap.size(); i++ )
|
|
stream.read( &mLayerMap[i] );
|
|
|
|
// Get the material name count.
|
|
U32 materialCount;
|
|
stream.read( &materialCount );
|
|
Vector<String> materials;
|
|
materials.setSize( materialCount );
|
|
|
|
// Load the material names.
|
|
for ( U32 i=0; i < materialCount; i++ )
|
|
stream.read( &materials[i] );
|
|
|
|
// Resolve the TerrainMaterial objects from the names.
|
|
_resolveMaterials( materials );
|
|
}
|
|
|
|
void TerrainFile::_loadLegacy( FileStream &stream )
|
|
{
|
|
// Some legacy constants.
|
|
enum
|
|
{
|
|
MaterialGroups = 8,
|
|
BlockSquareWidth = 256,
|
|
};
|
|
|
|
const U32 sampleCount = BlockSquareWidth * BlockSquareWidth;
|
|
mSize = BlockSquareWidth;
|
|
|
|
// Load the heightmap.
|
|
mHeightMap.setSize( sampleCount );
|
|
for ( U32 i=0; i < mHeightMap.size(); i++ )
|
|
stream.read( &mHeightMap[i] );
|
|
|
|
// Prior to version 7 we stored this weird material struct.
|
|
const U32 MATERIAL_GROUP_MASK = 0x7;
|
|
struct Material
|
|
{
|
|
enum Flags
|
|
{
|
|
Plain = 0,
|
|
Rotate = 1,
|
|
FlipX = 2,
|
|
FlipXRotate = 3,
|
|
FlipY = 4,
|
|
FlipYRotate = 5,
|
|
FlipXY = 6,
|
|
FlipXYRotate = 7,
|
|
RotateMask = 7,
|
|
Empty = 8,
|
|
Modified = BIT(7),
|
|
|
|
// must not clobber TerrainFile::MATERIAL_GROUP_MASK bits!
|
|
PersistMask = BIT(7)
|
|
};
|
|
|
|
U8 flags;
|
|
U8 index;
|
|
};
|
|
|
|
// Temp locals for loading before we convert to the new
|
|
// version 7+ format.
|
|
U8 baseMaterialMap[sampleCount] = { 0 };
|
|
U8 *materialAlphaMap[MaterialGroups] = { 0 };
|
|
Material materialMap[BlockSquareWidth * BlockSquareWidth];
|
|
|
|
// read the material group map and flags...
|
|
dMemset(materialMap, 0, sizeof(materialMap));
|
|
|
|
AssertFatal(!(Material::PersistMask & MATERIAL_GROUP_MASK),
|
|
"Doh! We have flag clobberage...");
|
|
|
|
for (S32 j=0; j < sampleCount; j++)
|
|
{
|
|
U8 val;
|
|
stream.read(&val);
|
|
|
|
//
|
|
baseMaterialMap[j] = val & MATERIAL_GROUP_MASK;
|
|
materialMap[j].flags = val & Material::PersistMask;
|
|
}
|
|
|
|
// Load the material names.
|
|
Vector<String> materials;
|
|
for ( U32 i=0; i < MaterialGroups; i++ )
|
|
{
|
|
String matName;
|
|
stream.read( &matName );
|
|
if ( matName.isEmpty() )
|
|
continue;
|
|
|
|
if ( mFileVersion > 3 && mFileVersion < 6 )
|
|
{
|
|
// Between version 3 and 5 we store the texture file names
|
|
// relative to the terrain file. We restore the full path
|
|
// here so that we can create a TerrainMaterial from it.
|
|
materials.push_back( Torque::Path::CompressPath( mFilePath.getRoot() + mFilePath.getPath() + '/' + matName ) );
|
|
}
|
|
else
|
|
materials.push_back( matName );
|
|
}
|
|
|
|
if ( mFileVersion <= 3 )
|
|
{
|
|
GFXTexHandle terrainMat;
|
|
Torque::Path matRelPath;
|
|
|
|
// Try to automatically fix up our material file names
|
|
for (U32 i = 0; i < materials.size(); i++)
|
|
{
|
|
if ( materials[i].isEmpty() )
|
|
continue;
|
|
|
|
terrainMat.set( materials[i], &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
|
|
if ( terrainMat )
|
|
continue;
|
|
|
|
matRelPath = materials[i];
|
|
|
|
String path = matRelPath.getPath();
|
|
|
|
String::SizeType n = path.find( '/', 0, String::NoCase );
|
|
if ( n != String::NPos )
|
|
{
|
|
matRelPath.setPath( String(Con::getVariable( "$defaultGame" )) + path.substr( n, path.length() - n ) );
|
|
|
|
terrainMat.set( matRelPath, &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
|
|
if ( terrainMat )
|
|
{
|
|
materials[i] = matRelPath.getFullPath();
|
|
mNeedsResaving = true;
|
|
}
|
|
}
|
|
|
|
} // for (U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
|
|
|
|
} // if ( mFileVersion <= 3 )
|
|
|
|
if ( mFileVersion == 1 )
|
|
{
|
|
for( S32 j = 0; j < sampleCount; j++ )
|
|
{
|
|
if ( materialAlphaMap[baseMaterialMap[j]] == NULL )
|
|
{
|
|
materialAlphaMap[baseMaterialMap[j]] = new U8[sampleCount];
|
|
dMemset(materialAlphaMap[baseMaterialMap[j]], 0, sampleCount);
|
|
}
|
|
|
|
materialAlphaMap[baseMaterialMap[j]][j] = 255;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( S32 k=0; k < materials.size(); k++ )
|
|
{
|
|
AssertFatal(materialAlphaMap[k] == NULL, "Bad assumption. There should be no alpha map at this point...");
|
|
materialAlphaMap[k] = new U8[sampleCount];
|
|
stream.read(sampleCount, materialAlphaMap[k]);
|
|
}
|
|
}
|
|
|
|
// Throw away the old texture and heightfield scripts.
|
|
if ( mFileVersion >= 3 )
|
|
{
|
|
U32 len;
|
|
stream.read(&len);
|
|
char *textureScript = (char *)dMalloc(len + 1);
|
|
stream.read(len, textureScript);
|
|
dFree( textureScript );
|
|
|
|
stream.read(&len);
|
|
char *heightfieldScript = (char *)dMalloc(len + 1);
|
|
stream.read(len, heightfieldScript);
|
|
dFree( heightfieldScript );
|
|
}
|
|
|
|
// Load and throw away the old edge terrain paths.
|
|
if ( mFileVersion >= 5 )
|
|
{
|
|
stream.readSTString(true);
|
|
stream.readSTString(true);
|
|
}
|
|
|
|
U32 layerCount = materials.size() - 1;
|
|
|
|
// Ok... time to convert all this mess to the layer index map!
|
|
for ( U32 i=0; i < sampleCount; i++ )
|
|
{
|
|
// Find the greatest layer.
|
|
U32 layer = 0;
|
|
U32 lastValue = 0;
|
|
for ( U32 k=0; k < MaterialGroups; k++ )
|
|
{
|
|
if ( materialAlphaMap[k] && materialAlphaMap[k][i] > lastValue )
|
|
{
|
|
layer = k;
|
|
lastValue = materialAlphaMap[k][i];
|
|
}
|
|
}
|
|
|
|
// Set the layer index.
|
|
mLayerMap[i] = getMin( layer, layerCount );
|
|
}
|
|
|
|
// Cleanup.
|
|
for ( U32 i=0; i < MaterialGroups; i++ )
|
|
delete [] materialAlphaMap[i];
|
|
|
|
// Force resaving on these old file versions.
|
|
//mNeedsResaving = false;
|
|
|
|
// Resolve the TerrainMaterial objects from the names.
|
|
_resolveMaterials( materials );
|
|
}
|
|
|
|
void TerrainFile::_resolveMaterials( const Vector<String> &materials )
|
|
{
|
|
mMaterials.clear();
|
|
|
|
for ( U32 i=0; i < materials.size(); i++ )
|
|
mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) );
|
|
|
|
// If we didn't get any materials then at least
|
|
// add a warning material so we will render.
|
|
if ( mMaterials.empty() )
|
|
mMaterials.push_back( TerrainMaterial::getWarningMaterial() );
|
|
}
|
|
|
|
void TerrainFile::setSize( U32 newSize, bool clear )
|
|
{
|
|
// Make sure the resolution is a power of two.
|
|
newSize = getNextPow2( newSize );
|
|
|
|
//
|
|
if ( clear )
|
|
{
|
|
mLayerMap.setSize( newSize * newSize );
|
|
mLayerMap.compact();
|
|
dMemset( mLayerMap.address(), 0, mLayerMap.memSize() );
|
|
|
|
// Initialize the elevation to something above
|
|
// zero so that we have room to excavate by default.
|
|
U16 elev = floatToFixed( 512.0f );
|
|
|
|
mHeightMap.setSize( newSize * newSize );
|
|
mHeightMap.compact();
|
|
for ( U32 i = 0; i < mHeightMap.size(); i++ )
|
|
mHeightMap[i] = elev;
|
|
}
|
|
else
|
|
{
|
|
// We're resizing here!
|
|
|
|
|
|
|
|
}
|
|
|
|
mSize = newSize;
|
|
|
|
_buildGridMap();
|
|
}
|
|
|
|
void TerrainFile::smooth( F32 factor, U32 steps, bool updateCollision )
|
|
{
|
|
const U32 blockSize = mSize * mSize;
|
|
|
|
// Grab some temp buffers for our smoothing results.
|
|
Vector<F32> h1, h2;
|
|
h1.setSize( blockSize );
|
|
h2.setSize( blockSize );
|
|
|
|
// Fill the first buffer with the current heights.
|
|
for ( U32 i=0; i < blockSize; i++ )
|
|
h1[i] = (F32)mHeightMap[i];
|
|
|
|
// factor of 0.0 = NO Smoothing
|
|
// factor of 1.0 = MAX Smoothing
|
|
const F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor));
|
|
const F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f;
|
|
const F32 matrixC = matrixE * 0.5f;
|
|
|
|
// Now loop for our interations.
|
|
F32 *src = h1.address();
|
|
F32 *dst = h2.address();
|
|
for ( U32 s=0; s < steps; s++ )
|
|
{
|
|
for ( S32 y=0; y < mSize; y++ )
|
|
{
|
|
for ( S32 x=0; x < mSize; x++ )
|
|
{
|
|
F32 samples[9];
|
|
|
|
S32 c = 0;
|
|
for (S32 i = y-1; i < y+2; i++)
|
|
for (S32 j = x-1; j < x+2; j++)
|
|
{
|
|
if ( i < 0 || j < 0 || i >= mSize || j >= mSize )
|
|
samples[c++] = src[ x + ( y * mSize ) ];
|
|
else
|
|
samples[c++] = src[ j + ( i * mSize ) ];
|
|
}
|
|
|
|
// 0 1 2
|
|
// 3 x,y 5
|
|
// 6 7 8
|
|
|
|
dst[ x + ( y * mSize ) ] =
|
|
((samples[0]+samples[2]+samples[6]+samples[8]) * matrixC) +
|
|
((samples[1]+samples[3]+samples[5]+samples[7]) * matrixE) +
|
|
(samples[4] * matrixM);
|
|
}
|
|
}
|
|
|
|
// Swap!
|
|
F32 *tmp = dst;
|
|
dst = src;
|
|
src = tmp;
|
|
}
|
|
|
|
// Copy the results back to the height map.
|
|
for ( U32 i=0; i < blockSize; i++ )
|
|
mHeightMap[i] = (U16)mCeil( (F32)src[i] );
|
|
|
|
if ( updateCollision )
|
|
_buildGridMap();
|
|
}
|
|
|
|
void TerrainFile::setHeightMap( const Vector<U16> &heightmap, bool updateCollision )
|
|
{
|
|
AssertFatal( mHeightMap.size() == heightmap.size(), "TerrainFile::setHeightMap - Incorrect heightmap size!" );
|
|
dMemcpy( mHeightMap.address(), heightmap.address(), mHeightMap.size() );
|
|
|
|
if ( updateCollision )
|
|
_buildGridMap();
|
|
}
|
|
|
|
void TerrainFile::import( const GBitmap &heightMap,
|
|
F32 heightScale,
|
|
const Vector<U8> &layerMap,
|
|
const Vector<String> &materials,
|
|
bool flipYAxis )
|
|
{
|
|
AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainFile::import - Height map is not square!" );
|
|
AssertFatal( isPow2( heightMap.getWidth() ), "TerrainFile::import - Height map is not power of two!" );
|
|
|
|
const U32 newSize = heightMap.getWidth();
|
|
if ( newSize != mSize )
|
|
{
|
|
mHeightMap.setSize( newSize * newSize );
|
|
mHeightMap.compact();
|
|
mSize = newSize;
|
|
}
|
|
|
|
// Convert the height map to heights.
|
|
U16 *oBits = mHeightMap.address();
|
|
if ( heightMap.getFormat() == GFXFormatL16)
|
|
{
|
|
const F32 toFixedPoint = ( 1.0f / (F32)U16_MAX ) * floatToFixed( heightScale );
|
|
const U16 *iBits = (const U16*)heightMap.getBits();
|
|
if ( flipYAxis )
|
|
{
|
|
for ( U32 i = 0; i < mSize * mSize; i++ )
|
|
{
|
|
U16 height = convertBEndianToHost( *iBits );
|
|
*oBits = (U16)mCeil( (F32)height * toFixedPoint );
|
|
++oBits;
|
|
++iBits;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(S32 y = mSize - 1; y >= 0; y--) {
|
|
for(U32 x = 0; x < mSize; x++) {
|
|
U16 height = convertBEndianToHost( *iBits );
|
|
mHeightMap[x + y * mSize] = (U16)mCeil( (F32)height * toFixedPoint );
|
|
++iBits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const F32 toFixedPoint = ( 1.0f / (F32)U8_MAX ) * floatToFixed( heightScale );
|
|
const U8 *iBits = heightMap.getBits();
|
|
if ( flipYAxis )
|
|
{
|
|
for ( U32 i = 0; i < mSize * mSize; i++ )
|
|
{
|
|
*oBits = (U16)mCeil( ((F32)*iBits) * toFixedPoint );
|
|
++oBits;
|
|
iBits += heightMap.getBytesPerPixel();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(S32 y = mSize - 1; y >= 0; y--) {
|
|
for(U32 x = 0; x < mSize; x++) {
|
|
mHeightMap[x + y * mSize] = (U16)mCeil( ((F32)*iBits) * toFixedPoint );
|
|
iBits += heightMap.getBytesPerPixel();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy over the layer map.
|
|
AssertFatal( layerMap.size() == mHeightMap.size(), "TerrainFile::import - Layer map is the wrong size!" );
|
|
mLayerMap = layerMap;
|
|
mLayerMap.compact();
|
|
|
|
// Resolve the materials.
|
|
_resolveMaterials( materials );
|
|
|
|
// Rebuild the collision grid map.
|
|
_buildGridMap();
|
|
}
|
|
|
|
|
|
void TerrainFile::create( String *inOutFilename,
|
|
U32 newSize,
|
|
const Vector<String> &materials )
|
|
{
|
|
// Determine the path and basename
|
|
Torque::Path basePath( *inOutFilename );
|
|
if ( !basePath.getExtension().equal("ter") )
|
|
{
|
|
// Use the default path and filename
|
|
String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
|
|
if ( terrainDirectory.isEmpty() )
|
|
{
|
|
terrainDirectory = "data/terrains";
|
|
}
|
|
basePath.setPath( terrainDirectory );
|
|
basePath.setFileName( "terrain" );
|
|
}
|
|
|
|
// Construct a default file name
|
|
(*inOutFilename) = Torque::FS::MakeUniquePath( basePath.getRootAndPath(), basePath.getFileName(), "ter" );
|
|
|
|
// Create the file
|
|
TerrainFile *file = new TerrainFile;
|
|
|
|
for ( U32 i=0; i < materials.size(); i++ )
|
|
file->mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) );
|
|
|
|
file->setSize( newSize, true );
|
|
file->save( *inOutFilename );
|
|
|
|
delete file;
|
|
}
|
|
|
|
inline void getMinMax( U16 &inMin, U16 &inMax, U16 height )
|
|
{
|
|
if ( height < inMin )
|
|
inMin = height;
|
|
if ( height > inMax )
|
|
inMax = height;
|
|
}
|
|
|
|
inline void checkSquare( TerrainSquare *parent, const TerrainSquare *child )
|
|
{
|
|
if(parent->minHeight > child->minHeight)
|
|
parent->minHeight = child->minHeight;
|
|
if(parent->maxHeight < child->maxHeight)
|
|
parent->maxHeight = child->maxHeight;
|
|
|
|
if ( child->flags & (TerrainSquare::Empty | TerrainSquare::HasEmpty) )
|
|
parent->flags |= TerrainSquare::HasEmpty;
|
|
}
|
|
|
|
void TerrainFile::updateGrid( const Point2I &minPt, const Point2I &maxPt )
|
|
{
|
|
// here's how it works:
|
|
// for the current terrain renderer we only care about
|
|
// the minHeight and maxHeight on the GridSquare
|
|
// so we do one pass through, updating minHeight and maxHeight
|
|
// on the level 0 squares, then we loop up the grid map from 1 to
|
|
// the top, expanding the bounding boxes as necessary.
|
|
// this should end up being way, way, way, way faster for the terrain
|
|
// editor
|
|
|
|
PROFILE_SCOPE( TerrainFile_UpdateGrid );
|
|
|
|
for ( S32 y = minPt.y - 1; y < maxPt.y + 1; y++ )
|
|
{
|
|
for ( S32 x = minPt.x - 1; x < maxPt.x + 1; x++ )
|
|
{
|
|
S32 px = x;
|
|
S32 py = y;
|
|
if ( px < 0 )
|
|
px += mSize;
|
|
if ( py < 0 )
|
|
py += mSize;
|
|
|
|
TerrainSquare *sq = findSquare( 0, px, py );
|
|
sq->minHeight = 0xFFFF;
|
|
sq->maxHeight = 0;
|
|
|
|
// Update the empty state.
|
|
if ( isEmptyAt( x, y ) )
|
|
sq->flags |= TerrainSquare::Empty;
|
|
else
|
|
sq->flags &= ~TerrainSquare::Empty;
|
|
|
|
getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y ) );
|
|
getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y ) );
|
|
getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y+1 ) );
|
|
getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y+1 ) );
|
|
}
|
|
}
|
|
|
|
// ok, all the level 0 grid squares are updated:
|
|
// now update all the parent grid squares that need to be updated:
|
|
for( S32 level = 1; level <= mGridLevels; level++ )
|
|
{
|
|
S32 size = 1 << level;
|
|
S32 halfSize = size >> 1;
|
|
|
|
for( S32 y = (minPt.y - 1) >> level; y < (maxPt.y + size) >> level; y++ )
|
|
{
|
|
for ( S32 x = (minPt.x - 1) >> level; x < (maxPt.x + size) >> level; x++ )
|
|
{
|
|
S32 px = x << level;
|
|
S32 py = y << level;
|
|
|
|
TerrainSquare *sq = findSquare(level, px, py);
|
|
sq->minHeight = 0xFFFF;
|
|
sq->maxHeight = 0;
|
|
sq->flags &= ~( TerrainSquare::Empty | TerrainSquare::HasEmpty );
|
|
|
|
checkSquare( sq, findSquare( level - 1, px, py ) );
|
|
checkSquare( sq, findSquare( level - 1, px + halfSize, py ) );
|
|
checkSquare( sq, findSquare( level - 1, px, py + halfSize ) );
|
|
checkSquare( sq, findSquare( level - 1, px + halfSize, py + halfSize ) );
|
|
}
|
|
}
|
|
}
|
|
}
|