Torque3D/Engine/source/terrain/terrRender.cpp
Areloch 4ce558f042 Reworks the terrain loader code to work with the assets.
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.
2020-06-25 23:33:01 -05:00

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();
}