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.
539 lines
16 KiB
C++
539 lines
16 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "terrain/terrRender.h"
|
|
|
|
#include "terrain/terrData.h"
|
|
#include "terrain/terrCell.h"
|
|
#include "terrain/terrMaterial.h"
|
|
#include "terrain/terrCellMaterial.h"
|
|
#include "materials/shaderData.h"
|
|
|
|
#include "platform/profiler.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "math/util/frustum.h"
|
|
#include "renderInstance/renderPassManager.h"
|
|
#include "renderInstance/renderTerrainMgr.h"
|
|
|
|
#include "lighting/lightInfo.h"
|
|
#include "lighting/lightManager.h"
|
|
|
|
#include "materials/matInstance.h"
|
|
#include "materials/materialManager.h"
|
|
#include "materials/matTextureTarget.h"
|
|
#include "shaderGen/conditionerFeature.h"
|
|
|
|
#include "gfx/gfxDrawUtil.h"
|
|
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
#include "afx/arcaneFX.h"
|
|
#include "afx/ce/afxZodiacMgr.h"
|
|
#endif
|
|
|
|
#include "gfx/gfxTransformSaver.h"
|
|
#include "gfx/bitmap/gBitmap.h"
|
|
#include "gfx/bitmap/ddsFile.h"
|
|
#include "gfx/bitmap/imageUtils.h"
|
|
#include "terrain/terrMaterial.h"
|
|
#include "gfx/gfxDebugEvent.h"
|
|
#include "gfx/gfxCardProfile.h"
|
|
#include "core/stream/fileStream.h"
|
|
|
|
|
|
bool TerrainBlock::smDebugRender = false;
|
|
|
|
|
|
GFX_ImplementTextureProfile( TerrainLayerTexProfile,
|
|
GFXTextureProfile::DiffuseMap,
|
|
GFXTextureProfile::PreserveSize |
|
|
GFXTextureProfile::Dynamic,
|
|
GFXTextureProfile::NONE );
|
|
|
|
|
|
void TerrainBlock::_onFlushMaterials()
|
|
{
|
|
if ( mCell )
|
|
mCell->deleteMaterials();
|
|
|
|
SAFE_DELETE( mBaseMaterial );
|
|
}
|
|
|
|
void TerrainBlock::_updateMaterials()
|
|
{
|
|
if (!mFile)
|
|
return;
|
|
|
|
mBaseTextures.setSize( mFile->mMaterials.size() );
|
|
|
|
mMaxDetailDistance = 0.0f;
|
|
|
|
for ( U32 i=0; i < mFile->mMaterials.size(); i++ )
|
|
{
|
|
TerrainMaterial *mat = mFile->mMaterials[i];
|
|
|
|
if (!mat->getDiffuseMap().isEmpty())
|
|
{
|
|
mBaseTextures[i].set(mat->getDiffuseMap(), &GFXStaticTextureSRGBProfile,
|
|
"TerrainBlock::_updateMaterials() - DiffuseMap");
|
|
}
|
|
else
|
|
mBaseTextures[ i ] = GFXTexHandle();
|
|
|
|
// Find the maximum detail distance.
|
|
if ( mat->getDetailMap().isNotEmpty() &&
|
|
mat->getDetailDistance() > mMaxDetailDistance )
|
|
mMaxDetailDistance = mat->getDetailDistance();
|
|
|
|
if ( mat->getMacroMap().isNotEmpty() &&
|
|
mat->getMacroDistance() > mMaxDetailDistance )
|
|
mMaxDetailDistance = mat->getMacroDistance();
|
|
}
|
|
|
|
if ( mCell )
|
|
mCell->deleteMaterials();
|
|
}
|
|
|
|
void TerrainBlock::_updateLayerTexture()
|
|
{
|
|
const U32 layerSize = mFile->mSize;
|
|
const Vector<U8> &layerMap = mFile->mLayerMap;
|
|
const U32 pixelCount = layerMap.size();
|
|
|
|
if ( mLayerTex.isNull() ||
|
|
mLayerTex.getWidth() != layerSize ||
|
|
mLayerTex.getHeight() != layerSize )
|
|
mLayerTex.set( layerSize, layerSize, GFXFormatB8G8R8A8, &TerrainLayerTexProfile, "" );
|
|
|
|
AssertFatal( mLayerTex.getWidth() == layerSize &&
|
|
mLayerTex.getHeight() == layerSize,
|
|
"TerrainBlock::_updateLayerTexture - The texture size doesn't match the requested size!" );
|
|
|
|
// Update the layer texture.
|
|
GFXLockedRect *lock = mLayerTex.lock();
|
|
|
|
for ( U32 i=0; i < pixelCount; i++ )
|
|
{
|
|
lock->bits[0] = layerMap[i];
|
|
|
|
if ( i + 1 >= pixelCount )
|
|
lock->bits[1] = lock->bits[0];
|
|
else
|
|
lock->bits[1] = layerMap[i+1];
|
|
|
|
if ( i + layerSize >= pixelCount )
|
|
lock->bits[2] = lock->bits[0];
|
|
else
|
|
lock->bits[2] = layerMap[i + layerSize];
|
|
|
|
if ( i + layerSize + 1 >= pixelCount )
|
|
lock->bits[3] = lock->bits[0];
|
|
else
|
|
lock->bits[3] = layerMap[i + layerSize + 1];
|
|
|
|
lock->bits += 4;
|
|
}
|
|
|
|
mLayerTex.unlock();
|
|
//mLayerTex->dumpToDisk( "png", "./layerTex.png" );
|
|
}
|
|
|
|
bool TerrainBlock::_initBaseShader()
|
|
{
|
|
ShaderData *shaderData = NULL;
|
|
if ( !Sim::findObject( "TerrainBlendShader", shaderData ) || !shaderData )
|
|
return false;
|
|
|
|
mBaseShader = shaderData->getShader();
|
|
|
|
mBaseShaderConsts = mBaseShader->allocConstBuffer();
|
|
mBaseTexScaleConst = mBaseShader->getShaderConstHandle( "$texScale" );
|
|
mBaseTexIdConst = mBaseShader->getShaderConstHandle( "$texId" );
|
|
mBaseLayerSizeConst = mBaseShader->getShaderConstHandle( "$layerSize" );
|
|
|
|
mBaseTarget = GFX->allocRenderToTextureTarget();
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.samplersDefined = true;
|
|
desc.samplers[0] = GFXSamplerStateDesc::getClampPoint();
|
|
desc.samplers[1] = GFXSamplerStateDesc::getWrapLinear();
|
|
desc.zDefined = true;
|
|
desc.zWriteEnable = false;
|
|
desc.zEnable = false;
|
|
desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendOne );
|
|
desc.cullDefined = true;
|
|
desc.cullMode = GFXCullNone;
|
|
desc.colorWriteAlpha = false;
|
|
mBaseShaderSB = GFX->createStateBlock( desc );
|
|
|
|
return true;
|
|
}
|
|
|
|
void TerrainBlock::_updateBaseTexture(bool writeToCache)
|
|
{
|
|
if ( !mBaseShader && !_initBaseShader() )
|
|
return;
|
|
|
|
// This can sometimes occur outside a begin/end scene.
|
|
const bool sceneBegun = GFX->canCurrentlyRender();
|
|
if ( !sceneBegun )
|
|
GFX->beginScene();
|
|
|
|
GFXDEBUGEVENT_SCOPE( TerrainBlock_UpdateBaseTexture, ColorI::GREEN );
|
|
|
|
PROFILE_SCOPE( TerrainBlock_UpdateBaseTexture );
|
|
|
|
GFXTransformSaver saver;
|
|
|
|
const U32 maxTextureSize = GFX->getCardProfiler()->queryProfile( "maxTextureSize", 1024 );
|
|
|
|
U32 baseTexSize = getNextPow2( mBaseTexSize );
|
|
baseTexSize = getMin( maxTextureSize, baseTexSize );
|
|
Point2I destSize( baseTexSize, baseTexSize );
|
|
|
|
// Setup geometry
|
|
GFXVertexBufferHandle<GFXVertexPT> vb;
|
|
{
|
|
F32 copyOffsetX = 2.0f * GFX->getFillConventionOffset() / (F32)destSize.x;
|
|
F32 copyOffsetY = 2.0f * GFX->getFillConventionOffset() / (F32)destSize.y;
|
|
|
|
GFXVertexPT points[4];
|
|
points[0].point = Point3F(1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0);
|
|
points[0].texCoord = Point2F(1.0, 1.0f);
|
|
points[1].point = Point3F(1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0);
|
|
points[1].texCoord = Point2F(1.0, 0.0f);
|
|
points[2].point = Point3F(-1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0);
|
|
points[2].texCoord = Point2F(0.0, 1.0f);
|
|
points[3].point = Point3F(-1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0);
|
|
points[3].texCoord = Point2F(0.0, 0.0f);
|
|
|
|
vb.set( GFX, 4, GFXBufferTypeVolatile );
|
|
GFXVertexPT *ptr = vb.lock();
|
|
if(ptr)
|
|
{
|
|
dMemcpy( ptr, points, sizeof(GFXVertexPT) * 4 );
|
|
vb.unlock();
|
|
}
|
|
}
|
|
|
|
GFXTexHandle blendTex;
|
|
|
|
// If the base texture is already a valid render target then
|
|
// use it to render to else we create one.
|
|
if ( mBaseTex.isValid() &&
|
|
mBaseTex->isRenderTarget() &&
|
|
mBaseTex->getFormat() == GFXFormatR8G8B8A8_SRGB &&
|
|
mBaseTex->getWidth() == destSize.x &&
|
|
mBaseTex->getHeight() == destSize.y )
|
|
blendTex = mBaseTex;
|
|
else
|
|
blendTex.set( destSize.x, destSize.y, GFXFormatR8G8B8A8_SRGB, &GFXRenderTargetSRGBProfile, "" );
|
|
|
|
GFX->pushActiveRenderTarget();
|
|
|
|
// Set our shader stuff
|
|
GFX->setShader( mBaseShader );
|
|
GFX->setShaderConstBuffer( mBaseShaderConsts );
|
|
GFX->setStateBlock( mBaseShaderSB );
|
|
GFX->setVertexBuffer( vb );
|
|
|
|
mBaseTarget->attachTexture( GFXTextureTarget::Color0, blendTex );
|
|
GFX->setActiveRenderTarget( mBaseTarget );
|
|
|
|
GFX->clear( GFXClearTarget, ColorI(0,0,0,255), 1.0f, 0 );
|
|
|
|
GFX->setTexture( 0, mLayerTex );
|
|
mBaseShaderConsts->setSafe( mBaseLayerSizeConst, (F32)mLayerTex->getWidth() );
|
|
|
|
for ( U32 i=0; i < mBaseTextures.size(); i++ )
|
|
{
|
|
GFXTextureObject *tex = mBaseTextures[i];
|
|
if ( !tex )
|
|
continue;
|
|
|
|
GFX->setTexture( 1, tex );
|
|
|
|
F32 baseSize = mFile->mMaterials[i]->getDiffuseSize();
|
|
F32 scale = 1.0f;
|
|
if ( !mIsZero( baseSize ) )
|
|
scale = getWorldBlockSize() / baseSize;
|
|
|
|
// A mistake early in development means that texture
|
|
// coords are not flipped correctly. To compensate
|
|
// we flip the y scale here.
|
|
mBaseShaderConsts->setSafe( mBaseTexScaleConst, Point2F( scale, -scale ) );
|
|
mBaseShaderConsts->setSafe( mBaseTexIdConst, (F32)i );
|
|
|
|
GFX->drawPrimitive( GFXTriangleStrip, 0, 2 );
|
|
}
|
|
|
|
mBaseTarget->resolve();
|
|
|
|
GFX->setShader( NULL );
|
|
//GFX->setStateBlock( NULL ); // WHY NOT?
|
|
GFX->setShaderConstBuffer( NULL );
|
|
GFX->setVertexBuffer( NULL );
|
|
|
|
GFX->popActiveRenderTarget();
|
|
|
|
// End it if we begun it... Yeehaw!
|
|
if ( !sceneBegun )
|
|
GFX->endScene();
|
|
|
|
/// Do we cache this sucker?
|
|
if (mBaseTexFormat == NONE || !writeToCache)
|
|
{
|
|
// We didn't cache the result, so set the base texture
|
|
// to the render target we updated. This should be good
|
|
// for realtime painting cases.
|
|
mBaseTex = blendTex;
|
|
}
|
|
else if (mBaseTexFormat == DDS)
|
|
{
|
|
String cachePath = _getBaseTexCacheFileName();
|
|
|
|
FileStream fs;
|
|
if ( fs.open( _getBaseTexCacheFileName(), Torque::FS::File::Write ) )
|
|
{
|
|
// Read back the render target, dxt compress it, and write it to disk.
|
|
GBitmap blendBmp( destSize.x, destSize.y, false, GFXFormatR8G8B8A8 );
|
|
blendTex.copyToBmp( &blendBmp );
|
|
|
|
/*
|
|
// Test code for dumping uncompressed bitmap to disk.
|
|
{
|
|
FileStream fs;
|
|
if ( fs.open( "./basetex.png", Torque::FS::File::Write ) )
|
|
{
|
|
blendBmp.writeBitmap( "png", fs );
|
|
fs.close();
|
|
}
|
|
}
|
|
*/
|
|
|
|
blendBmp.extrudeMipLevels();
|
|
|
|
DDSFile *blendDDS = DDSFile::createDDSFileFromGBitmap( &blendBmp );
|
|
ImageUtil::ddsCompress( blendDDS, GFXFormatBC1 );
|
|
|
|
// Write result to file stream
|
|
blendDDS->write( fs );
|
|
|
|
delete blendDDS;
|
|
}
|
|
fs.close();
|
|
}
|
|
else
|
|
{
|
|
FileStream stream;
|
|
if (!stream.open(_getBaseTexCacheFileName(), Torque::FS::File::Write))
|
|
{
|
|
mBaseTex = blendTex;
|
|
return;
|
|
}
|
|
|
|
GBitmap bitmap(blendTex->getWidth(), blendTex->getHeight(), false, GFXFormatR8G8B8A8);
|
|
blendTex->copyToBmp(&bitmap);
|
|
bitmap.writeBitmap(formatToExtension(mBaseTexFormat), stream);
|
|
}
|
|
}
|
|
|
|
void TerrainBlock::_renderBlock( SceneRenderState *state )
|
|
{
|
|
PROFILE_SCOPE( TerrainBlock_RenderBlock );
|
|
|
|
if (!mFile)
|
|
return;
|
|
|
|
// Prevent rendering shadows if feature is disabled
|
|
if ( !mCastShadows && state->isShadowPass() )
|
|
return;
|
|
|
|
MatrixF worldViewXfm = state->getWorldViewMatrix();
|
|
worldViewXfm.mul( getRenderTransform() );
|
|
|
|
MatrixF worldViewProjXfm = state->getProjectionMatrix();
|
|
worldViewProjXfm.mul( worldViewXfm );
|
|
|
|
const MatrixF &objectXfm = getRenderWorldTransform();
|
|
|
|
Point3F objCamPos = state->getDiffuseCameraPosition();
|
|
objectXfm.mulP( objCamPos );
|
|
|
|
// Get the shadow material.
|
|
if ( !mDefaultMatInst )
|
|
mDefaultMatInst = TerrainCellMaterial::getShadowMat();
|
|
|
|
// Make sure we have a base material.
|
|
if ( !mBaseMaterial )
|
|
{
|
|
mBaseMaterial = new TerrainCellMaterial();
|
|
mBaseMaterial->init( this, 0, false, false, true );
|
|
}
|
|
|
|
// Did the detail layers change?
|
|
if ( mDetailsDirty )
|
|
{
|
|
_updateMaterials();
|
|
mDetailsDirty = false;
|
|
}
|
|
|
|
// If the layer texture has been cleared or is
|
|
// dirty then update it.
|
|
if ( mLayerTex.isNull() || mLayerTexDirty )
|
|
_updateLayerTexture();
|
|
|
|
// If the layer texture is dirty or we lost the base
|
|
// texture then regenerate it.
|
|
if ( mLayerTexDirty || mBaseTex.isNull() )
|
|
{
|
|
_updateBaseTexture( false );
|
|
mLayerTexDirty = false;
|
|
}
|
|
|
|
static Vector<TerrCell*> renderCells;
|
|
renderCells.clear();
|
|
|
|
mCell->cullCells( state,
|
|
objCamPos,
|
|
&renderCells );
|
|
|
|
RenderPassManager *renderPass = state->getRenderPass();
|
|
|
|
MatrixF *riObjectToWorldXfm = renderPass->allocUniqueXform( getRenderTransform() );
|
|
|
|
const bool isColorDrawPass = state->isDiffusePass() || state->isReflectPass();
|
|
|
|
// This is here for shadows mostly... it allows the
|
|
// proper shadow material to be generated.
|
|
BaseMatInstance *defaultMatInst = state->getOverrideMaterial( mDefaultMatInst );
|
|
|
|
// Only pass and use the light manager if this is not a shadow pass.
|
|
LightManager *lm = NULL;
|
|
if ( isColorDrawPass )
|
|
lm = LIGHTMGR;
|
|
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
bool has_zodiacs = afxZodiacMgr::doesBlockContainZodiacs(state, this);
|
|
#endif
|
|
for ( U32 i=0; i < renderCells.size(); i++ )
|
|
{
|
|
TerrCell *cell = renderCells[i];
|
|
|
|
// Ok this cell is fit to render.
|
|
TerrainRenderInst *inst = renderPass->allocInst<TerrainRenderInst>();
|
|
|
|
// Setup lights for this cell.
|
|
if ( lm )
|
|
{
|
|
SphereF bounds = cell->getSphereBounds();
|
|
getRenderTransform().mulP( bounds.center );
|
|
|
|
LightQuery query;
|
|
query.init( bounds );
|
|
query.getLights( inst->lights, 8 );
|
|
}
|
|
|
|
GFXVertexBufferHandleBase vertBuff;
|
|
GFXPrimitiveBufferHandle primBuff;
|
|
|
|
cell->getRenderPrimitive( &inst->prim, &vertBuff, &primBuff );
|
|
|
|
inst->mat = defaultMatInst;
|
|
inst->vertBuff = vertBuff.getPointer();
|
|
|
|
if ( primBuff.isValid() )
|
|
{
|
|
// Use the cell's custom primitive buffer
|
|
inst->primBuff = primBuff.getPointer();
|
|
}
|
|
else
|
|
{
|
|
// Use the standard primitive buffer for this cell
|
|
inst->primBuff = mPrimBuffer.getPointer();
|
|
}
|
|
|
|
inst->objectToWorldXfm = riObjectToWorldXfm;
|
|
|
|
// If we're not drawing to the shadow map then we need
|
|
// to include the normal rendering materials.
|
|
if ( isColorDrawPass )
|
|
{
|
|
const SphereF &bounds = cell->getSphereBounds();
|
|
|
|
F32 sqDist = ( bounds.center - objCamPos ).lenSquared();
|
|
|
|
F32 radiusSq = mSquared( ( mMaxDetailDistance + bounds.radius ) * smDetailScale );
|
|
|
|
// If this cell is near enough to get detail textures then
|
|
// use the full detail mapping material. Else we use the
|
|
// simple base only material.
|
|
if ( !state->isReflectPass() && sqDist < radiusSq )
|
|
inst->cellMat = cell->getMaterial();
|
|
else if ( state->isReflectPass() )
|
|
inst->cellMat = mBaseMaterial->getReflectMat();
|
|
else
|
|
inst->cellMat = mBaseMaterial;
|
|
}
|
|
|
|
inst->defaultKey = (U32)cell->getMaterials();
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
if (has_zodiacs)
|
|
afxZodiacMgr::renderTerrainZodiacs(state, this, cell);
|
|
// Submit it for rendering.
|
|
#endif
|
|
renderPass->addInst( inst );
|
|
}
|
|
|
|
// Trigger the debug rendering.
|
|
if ( state->isDiffusePass() &&
|
|
!renderCells.empty() &&
|
|
smDebugRender )
|
|
{
|
|
// Store the render cells for later.
|
|
mDebugCells = renderCells;
|
|
|
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind( this, &TerrainBlock::_renderDebug );
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
}
|
|
|
|
void TerrainBlock::_renderDebug( ObjectRenderInst *ri,
|
|
SceneRenderState *state,
|
|
BaseMatInstance *overrideMat )
|
|
{
|
|
GFXTransformSaver saver;
|
|
GFX->multWorld( getRenderTransform() );
|
|
|
|
for ( U32 i=0; i < mDebugCells.size(); i++ )
|
|
mDebugCells[i]->renderBounds();
|
|
|
|
mDebugCells.clear();
|
|
}
|