Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,728 @@
//-----------------------------------------------------------------------------
// 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/glsl/terrFeatureGLSL.h"
#include "terrain/terrFeatureTypes.h"
#include "materials/materialFeatureTypes.h"
#include "materials/materialFeatureData.h"
#include "gfx/gfxDevice.h"
#include "shaderGen/langElement.h"
#include "shaderGen/shaderOp.h"
#include "shaderGen/featureMgr.h"
#include "core/module.h"
MODULE_BEGIN( TerrainFeatGLSL )
MODULE_INIT_AFTER( ShaderGenFeatureMgr )
MODULE_INIT
{
FEATUREMGR->registerFeature( MFT_TerrainBaseMap, new TerrainBaseMapFeatGLSL );
FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new TerrainParallaxMapFeatGLSL );
FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatGLSL );
FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatGLSL );
FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatGLSL );
FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureGLSL( "Terrain Side Projection" ) );
FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatGLSL );
}
MODULE_END;
Var* TerrainFeatGLSL::_getUniformVar( const char *name, const char *type, ConstantSortPosition csp )
{
Var *theVar = (Var*)LangElement::find( name );
if ( !theVar )
{
theVar = new Var;
theVar->setType( type );
theVar->setName( name );
theVar->uniform = true;
theVar->constSortPos = csp;
}
return theVar;
}
Var* TerrainFeatGLSL::_getInDetailCoord( Vector<ShaderComponent*> &componentList )
{
String name( String::ToString( "outDetCoord%d", getProcessIndex() ) );
Var *inDet = (Var*)LangElement::find( name );
if ( !inDet )
{
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
inDet = connectComp->getElement( RT_TEXCOORD );
inDet->setName( name );
inDet->setType( "vec4" );
inDet->mapsToSampler = true;
}
return inDet;
}
Var* TerrainFeatGLSL::_getNormalMapTex()
{
String name( String::ToString( "normalMap%d", getProcessIndex() ) );
Var *normalMap = (Var*)LangElement::find( name );
if ( !normalMap )
{
normalMap = new Var;
normalMap->setType( "sampler2D" );
normalMap->setName( name );
normalMap->uniform = true;
normalMap->sampler = true;
normalMap->constNum = Var::getTexUnitNum();
}
return normalMap;
}
Var* TerrainFeatGLSL::_getDetailIdStrengthParallax()
{
String name( String::ToString( "detailIdStrengthParallax%d", getProcessIndex() ) );
Var *detailInfo = (Var*)LangElement::find( name );
if ( !detailInfo )
{
detailInfo = new Var;
detailInfo->setType( "vec3" );
detailInfo->setName( name );
detailInfo->uniform = true;
detailInfo->constSortPos = cspPotentialPrimitive;
}
return detailInfo;
}
void TerrainBaseMapFeatGLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
MultiLine *meta = new MultiLine;
output = meta;
// Generate the incoming texture var.
Var *inTex;
{
Var *inPos = (Var*)LangElement::find( "inPosition" );
if ( !inPos )
inPos = (Var*)LangElement::find( "position" );
inTex = new Var( "texCoord", "vec3" );
Var *oneOverTerrainSize = _getUniformVar( "oneOverTerrainSize", "float", cspPass );
// NOTE: The y coord here should be negative to have
// the texture maps not end up flipped which also caused
// normal and parallax mapping to be incorrect.
//
// This mistake early in development means that the layer
// id bilinear blend depends on it being that way.
//
// So instead i fixed this by flipping the base and detail
// coord y scale to compensate when rendering.
//
meta->addStatement( new GenOp( " @ = @.xyz * vec3( @, @, -@ );\r\n",
new DecOp( inTex ), inPos, oneOverTerrainSize, oneOverTerrainSize, oneOverTerrainSize ) );
}
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
// Pass the texture coord to the pixel shader.
Var *outTex = connectComp->getElement( RT_TEXCOORD );
outTex->setName( "outTexCoord" );
outTex->setType( "vec3" );
outTex->mapsToSampler = true;
meta->addStatement( new GenOp( " @.xy = @.xy;\r\n", outTex, inTex ) );
// If this shader has a side projected layer then we
// pass the dot product between the +Y and the normal
// thru outTexCoord.z for use in blending the textures.
if ( fd.features.hasFeature( MFT_TerrainSideProject ) )
{
Var *inNormal = (Var*)LangElement::find( "normal" );
meta->addStatement(
new GenOp( " @.z = pow( abs( dot( normalize( vec3( @.x, @.y, 0.0 ) ), vec3( 0, 1, 0 ) ) ), 10.0 );\r\n",
outTex, inNormal, inNormal ) );
}
else
meta->addStatement( new GenOp( " @.z = 0;\r\n", outTex ) );
// HACK: This is sort of lazy... we generate the tanget
// vector here so that we're sure it exists in the parallax
// and normal features which will expect "T" to exist.
//
// If this shader doesn't use it the shader compiler will
// optimize away this code.
//
Var *inTangentZ = getVertTexCoord( "tcTangentZ" );
Var *inTanget = new Var( "T", "vec3" );
Var *squareSize = _getUniformVar( "squareSize", "float", cspPass );
meta->addStatement( new GenOp( " @ = normalize( vec3( @, 0.0, @ ) );\r\n",
new DecOp( inTanget ), squareSize, inTangentZ ) );
}
void TerrainBaseMapFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// grab connector texcoord register
Var *texCoord = getInTexCoord( "outTexCoord", "vec3", true, componentList );
// We do nothing more if this is a prepass.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
// create texture var
Var *diffuseMap = new Var;
diffuseMap->setType( "sampler2D" );
diffuseMap->setName( "baseTexMap" );
diffuseMap->uniform = true;
diffuseMap->sampler = true;
diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here
MultiLine *meta = new MultiLine;
Var *baseColor = new Var;
baseColor->setType( "vec4" );
baseColor->setName( "baseColor" );
meta->addStatement( new GenOp( " @ = texture2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) );
meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) );
output = meta;
}
ShaderFeature::Resources TerrainBaseMapFeatGLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
res.numTexReg = 1;
// We only sample from the base map during a diffuse pass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
res.numTex = 1;
return res;
}
TerrainDetailMapFeatGLSL::TerrainDetailMapFeatGLSL()
: mTerrainDep( "shaders/common/terrain/terrain.glsl" )
{
addDependency( &mTerrainDep );
}
void TerrainDetailMapFeatGLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
const U32 detailIndex = getProcessIndex();
// If this is a prepass and we don't have a
// matching normal map... we have nothing to do.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) &&
!fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) )
return;
// Grab incoming texture coords... the base map feature
// made sure this was created.
Var *inTex = (Var*)LangElement::find( "texCoord" );
AssertFatal( inTex, "The texture coord is missing!" );
// Grab the input position.
Var *inPos = (Var*)LangElement::find( "inPosition" );
if ( !inPos )
inPos = (Var*)LangElement::find( "position" );
// Get the object space eye position.
Var *eyePos = _getUniformVar( "eyePos", "vec3", cspPotentialPrimitive );
MultiLine *meta = new MultiLine;
// Get the distance from the eye to this vertex.
Var *dist = (Var*)LangElement::find( "dist" );
if ( !dist )
{
dist = new Var;
dist->setType( "float" );
dist->setName( "dist" );
meta->addStatement( new GenOp( " @ = distance( @.xyz, @ );\r\n",
new DecOp( dist ), inPos, eyePos ) );
}
// grab connector texcoord register
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
Var *outTex = connectComp->getElement( RT_TEXCOORD );
outTex->setName( String::ToString( "outDetCoord%d", detailIndex ) );
outTex->setType( "vec4" );
outTex->mapsToSampler = true;
// Get the detail scale and fade info.
Var *detScaleAndFade = new Var;
detScaleAndFade->setType( "vec4" );
detScaleAndFade->setName( String::ToString( "detailScaleAndFade%d", detailIndex ) );
detScaleAndFade->uniform = true;
detScaleAndFade->constSortPos = cspPotentialPrimitive;
// Setup the detail coord.
//
// NOTE: You see here we scale the texture coord by 'xyx'
// to generate the detail coord. This y is here because
// its scale is flipped to correct for the non negative y
// in texCoord.
//
// See TerrainBaseMapFeatHLSL::processVert().
//
meta->addStatement( new GenOp( " @.xyz = @ * @.xyx;\r\n", outTex, inTex, detScaleAndFade ) );
// And sneak the detail fade thru the w detailCoord.
meta->addStatement( new GenOp( " @.w = clamp( ( @.z - @ ) * @.w, 0.0, 1.0 );\r\n",
outTex, detScaleAndFade, dist, detScaleAndFade ) );
output = meta;
}
void TerrainDetailMapFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
const U32 detailIndex = getProcessIndex();
// If this is a prepass and we don't have a
// matching normal map... we have nothing to do.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) &&
!fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) )
return;
Var *inTex = getVertTexCoord( "outTexCoord" );
MultiLine *meta = new MultiLine;
// Get the layer samples.
Var *layerSample = (Var*)LangElement::find( "layerSample" );
if ( !layerSample )
{
layerSample = new Var;
layerSample->setType( "vec4" );
layerSample->setName( "layerSample" );
// Get the layer texture var
Var *layerTex = new Var;
layerTex->setType( "sampler2D" );
layerTex->setName( "layerTex" );
layerTex->uniform = true;
layerTex->sampler = true;
layerTex->constNum = Var::getTexUnitNum();
// Read the layer texture to get the samples.
meta->addStatement( new GenOp( " @ = round( texture2D( @, @.xy ) * 255.0f );\r\n",
new DecOp( layerSample ), layerTex, inTex ) );
}
Var *layerSize = (Var*)LangElement::find( "layerSize" );
if ( !layerSize )
{
layerSize = new Var;
layerSize->setType( "float" );
layerSize->setName( "layerSize" );
layerSize->uniform = true;
layerSize->constSortPos = cspPass;
}
// Grab the incoming detail coord.
Var *inDet = _getInDetailCoord( componentList );
// Get the detail id.
Var *detailInfo = _getDetailIdStrengthParallax();
// Create the detail blend var.
Var *detailBlend = new Var;
detailBlend->setType( "float" );
detailBlend->setName( String::ToString( "detailBlend%d", detailIndex ) );
// Calculate the blend for this detail texture.
meta->addStatement( new GenOp( " @ = calcBlend( @.x, @.xy, @, @ );\r\n",
new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) );
// Get a var and accumulate the blend amount.
Var *blendTotal = (Var*)LangElement::find( "blendTotal" );
if ( !blendTotal )
{
blendTotal = new Var;
blendTotal->setName( "blendTotal" );
blendTotal->setType( "float" );
meta->addStatement( new GenOp( " @ = 0.0;\r\n", new DecOp( blendTotal ) ) );
}
// Add to the blend total.
meta->addStatement( new GenOp( " @ += @;\r\n", blendTotal, detailBlend ) );
//meta->addStatement( new GenOp( " @ += @ * @.y * @.w;\r\n",
//blendTotal, detailBlend, detailInfo, inDet ) );
// Nothing more to do for a detail texture in prepass.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
{
output = meta;
return;
}
Var *detailColor = (Var*)LangElement::find( "detailColor" );
if ( !detailColor )
{
detailColor = new Var;
detailColor->setType( "vec4" );
detailColor->setName( "detailColor" );
meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailColor ) ) );
}
// Get the detail texture.
Var *detailMap = new Var;
detailMap->setType( "sampler2D" );
detailMap->setName( String::ToString( "detailMap%d", detailIndex ) );
detailMap->uniform = true;
detailMap->sampler = true;
detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here
// If we're using SM 3.0 then take advantage of
// dynamic branching to skip layers per-pixel.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) );
meta->addStatement( new GenOp( " {\r\n" ) );
// Note that we're doing the standard greyscale detail
// map technique here which can darken and lighten the
// diffuse texture.
//
// We take two color samples and lerp between them for
// side projection layers... else a single sample.
//
if ( fd.features.hasFeature( MFT_TerrainSideProject, detailIndex ) )
{
meta->addStatement( new GenOp( " @ = ( mix( texture2D( @, @.yz ), texture2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n",
detailColor, detailMap, inDet, detailMap, inDet, inTex ) );
}
else
{
meta->addStatement( new GenOp( " @ = ( texture2D( @, @.xy ) * 2.0 ) - 1.0;\r\n",
detailColor, detailMap, inDet ) );
}
meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n",
detailColor, detailInfo, inDet ) );
Var *baseColor = (Var*)LangElement::find( "baseColor" );
Var *outColor = (Var*)LangElement::find( "col" );
meta->addStatement( new GenOp( " @ = mix( @, @ + @, @ );\r\n",
outColor, outColor, baseColor, detailColor, detailBlend ) );
meta->addStatement( new GenOp( " }\r\n" ) );
output = meta;
}
ShaderFeature::Resources TerrainDetailMapFeatGLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
{
// If this is a prepass and we don't have a
// matching normal map... we use no resources.
if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() ) )
return res;
// If this is the first matching normal map then
// it also samples from the layer tex.
if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) )
res.numTex += 1;
}
else
{
// If this is the first detail pass then it
// also samples from the layer tex.
if ( !fd.features.hasFeature( MFT_TerrainDetailMap, getProcessIndex() - 1 ) )
res.numTex += 1;
res.numTex += 1;
}
res.numTexReg += 1;
return res;
}
void TerrainNormalMapFeatGLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// We only need to process normals during the prepass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
MultiLine *meta = new MultiLine;
// Make sure the world to tangent transform
// is created and available for the pixel shader.
getOutViewToTangent( componentList, meta, fd );
output = meta;
}
void TerrainNormalMapFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// We only need to process normals during the prepass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
MultiLine *meta = new MultiLine;
Var *viewToTangent = getInViewToTangent( componentList );
// This var is read from GBufferConditionerGLSL and
// used in the prepass output.
Var *gbNormal = (Var*)LangElement::find( "gbNormal" );
if ( !gbNormal )
{
gbNormal = new Var;
gbNormal->setName( "gbNormal" );
gbNormal->setType( "vec3" );
meta->addStatement( new GenOp( " @ = @[2];\r\n", new DecOp( gbNormal ), viewToTangent ) );
}
const U32 normalIndex = getProcessIndex();
Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) );
AssertFatal( detailBlend, "The detail blend is missing!" );
// If we're using SM 3.0 then take advantage of
// dynamic branching to skip layers per-pixel.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) );
meta->addStatement( new GenOp( " {\r\n" ) );
// Get the normal map texture.
Var *normalMap = _getNormalMapTex();
/// Get the texture coord.
Var *inDet = _getInDetailCoord( componentList );
Var *inTex = getVertTexCoord( "outTexCoord" );
// Sample the normal map.
//
// We take two normal samples and lerp between them for
// side projection layers... else a single sample.
LangElement *texOp;
if ( fd.features.hasFeature( MFT_TerrainSideProject, normalIndex ) )
{
texOp = new GenOp( "mix( texture2D( @, @.yz ), texture2D( @, @.xz ), @.z )",
normalMap, inDet, normalMap, inDet, inTex );
}
else
texOp = new GenOp( "texture2D(@, @.xy)", normalMap, inDet );
// create bump normal
Var *bumpNorm = new Var;
bumpNorm->setName( "bumpNormal" );
bumpNorm->setType( "vec4" );
LangElement *bumpNormDecl = new DecOp( bumpNorm );
meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) );
// Normalize is done later...
// Note: The reverse mul order is intentional. Affine matrix.
meta->addStatement( new GenOp( " @ = mix( @, @.xyz * @, min( @, @.w ) );\r\n",
gbNormal, gbNormal, bumpNorm, viewToTangent, detailBlend, inDet ) );
// End the conditional block.
meta->addStatement( new GenOp( " }\r\n" ) );
// If this is the last normal map then we
// can test to see the total blend value
// to see if we should clip the result.
//if ( fd.features.getNextFeatureIndex( MFT_TerrainNormalMap, normalIndex ) == -1 )
//meta->addStatement( new GenOp( " clip( @ - 0.0001f );\r\n", blendTotal ) );
output = meta;
}
ShaderFeature::Resources TerrainNormalMapFeatGLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
// We only need to process normals during the prepass.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
{
// If this is the first normal map then it
// will generate the worldToTanget transform.
if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) )
res.numTexReg = 3;
res.numTex = 1;
}
return res;
}
TerrainParallaxMapFeatGLSL::TerrainParallaxMapFeatGLSL()
: mIncludeDep( "shaders/common/gl/torque.glsl" )
{
addDependency( &mIncludeDep );
}
void TerrainParallaxMapFeatGLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
if ( LangElement::find( "outNegViewTS" ) )
return;
MultiLine *meta = new MultiLine;
// Grab the input position.
Var *inPos = (Var*)LangElement::find( "inPosition" );
if ( !inPos )
inPos = (Var*)LangElement::find( "position" );
// Get the object space eye position and the
// object to tangent transform.
Var *eyePos = _getUniformVar( "eyePos", "vec3" , cspPotentialPrimitive );
Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta,fd );
// Now send the negative view vector in tangent space to the pixel shader.
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
Var *outNegViewTS = connectComp->getElement( RT_TEXCOORD );
outNegViewTS->setName( "outNegViewTS" );
outNegViewTS->setType( "vec3" );
meta->addStatement( new GenOp( " @ = @ * vec3( @ - @.xyz );\r\n",
outNegViewTS, objToTangentSpace, eyePos, inPos ) );
output = meta;
}
void TerrainParallaxMapFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
MultiLine *meta = new MultiLine;
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
// We need the negative tangent space view vector
// as in parallax mapping we step towards the camera.
Var *negViewTS = (Var*)LangElement::find( "negViewTS" );
if ( !negViewTS )
{
Var *inNegViewTS = (Var*)LangElement::find( "outNegViewTS" );
if ( !inNegViewTS )
{
inNegViewTS = connectComp->getElement( RT_TEXCOORD );
inNegViewTS->setName( "outNegViewTS" );
inNegViewTS->setType( "vec3" );
}
negViewTS = new Var( "negViewTS", "vec3" );
meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", new DecOp( negViewTS ), inNegViewTS ) );
}
// Get the rest of our inputs.
Var *detailInfo = _getDetailIdStrengthParallax();
Var *normalMap = _getNormalMapTex();
Var *texCoord = _getInDetailCoord( componentList );
// Call the library function to do the rest.
meta->addStatement( new GenOp( " @.xy += parallaxOffset( @, @.xy, @, @.z );\r\n",
texCoord, normalMap, texCoord, negViewTS, detailInfo ) );
output = meta;
}
ShaderFeature::Resources TerrainParallaxMapFeatGLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
// If this is the first parallax feature then
// it will generate the tangetEye vector and
// the worldToTanget transform.
if ( getProcessIndex() == 0 || !fd.features.hasFeature( MFT_TerrainParallaxMap, getProcessIndex() - 1 ) )
res.numTexReg = 4;
// If this isn't the prepass then we will
// be adding a normal map.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
res.numTex = 1;
return res;
}
void TerrainLightMapFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// grab connector texcoord register
Var *inTex = (Var*)LangElement::find( "outTexCoord" );
if ( !inTex )
return;
// Get the lightmap texture.
Var *lightMap = new Var;
lightMap->setType( "sampler2D" );
lightMap->setName( "lightMapTex" );
lightMap->uniform = true;
lightMap->sampler = true;
lightMap->constNum = Var::getTexUnitNum();
// Create a 'lightMask' value which is read by
// RTLighting to mask out the directional lighting.
Var *lightMask = new Var;
lightMask->setType( "vec3" );
lightMask->setName( "lightMask" );
output = new GenOp( " @ = texture2D( @, @.xy ).rgb;\r\n", new DecOp( lightMask ), lightMap, inTex );
}
ShaderFeature::Resources TerrainLightMapFeatGLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
res.numTex = 1;
return res;
}
void TerrainAdditiveFeatGLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
Var *color = (Var*) LangElement::find( "col" );
Var *blendTotal = (Var*)LangElement::find( "blendTotal" );
if ( !color || !blendTotal )
return;
MultiLine *meta = new MultiLine;
meta->addStatement( new GenOp( " if ( @ - 0.0001 < 0.0 ) discard;\r\n", blendTotal ) );
meta->addStatement( new GenOp( " @.a = @;\r\n", color, blendTotal ) );
output = meta;
}

View file

@ -0,0 +1,145 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRFEATUREGLSL_H_
#define _TERRFEATUREGLSL_H_
#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_
#include "shaderGen/GLSL/shaderFeatureGLSL.h"
#endif
#ifndef _LANG_ELEMENT_H_
#include "shaderGen/langElement.h"
#endif
/// A shared base class for terrain features which
/// includes some helper functions.
class TerrainFeatGLSL : public ShaderFeatureGLSL
{
protected:
Var* _getInDetailCoord(Vector<ShaderComponent*> &componentList );
Var* _getNormalMapTex();
static Var* _getUniformVar( const char *name, const char *type, ConstantSortPosition csp );
Var* _getDetailIdStrengthParallax();
};
class TerrainBaseMapFeatGLSL : public TerrainFeatGLSL
{
public:
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Base Texture"; }
};
class TerrainDetailMapFeatGLSL : public TerrainFeatGLSL
{
protected:
ShaderIncludeDependency mTerrainDep;
public:
TerrainDetailMapFeatGLSL();
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Detail Texture"; }
};
class TerrainNormalMapFeatGLSL : public TerrainFeatGLSL
{
public:
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Normal Texture"; }
};
class TerrainParallaxMapFeatGLSL : public TerrainFeatGLSL
{
protected:
ShaderIncludeDependency mIncludeDep;
public:
TerrainParallaxMapFeatGLSL();
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Parallax Texture"; }
};
class TerrainLightMapFeatGLSL : public TerrainFeatGLSL
{
public:
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Lightmap Texture"; }
};
class TerrainAdditiveFeatGLSL : public TerrainFeatGLSL
{
public:
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Additive"; }
};
#endif // _TERRFEATUREGLSL_H_

View file

@ -0,0 +1,712 @@
//-----------------------------------------------------------------------------
// 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/hlsl/terrFeatureHLSL.h"
#include "terrain/terrFeatureTypes.h"
#include "materials/materialFeatureTypes.h"
#include "materials/materialFeatureData.h"
#include "gfx/gfxDevice.h"
#include "shaderGen/langElement.h"
#include "shaderGen/shaderOp.h"
#include "shaderGen/featureMgr.h"
#include "core/module.h"
MODULE_BEGIN( TerrainFeatHLSL )
MODULE_INIT_AFTER( ShaderGenFeatureMgr )
MODULE_INIT
{
FEATUREMGR->registerFeature( MFT_TerrainBaseMap, new TerrainBaseMapFeatHLSL );
FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new NamedFeatureHLSL( "Terrain Parallax Texture" ) );
FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatHLSL );
FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatHLSL );
FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatHLSL );
FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureHLSL( "Terrain Side Projection" ) );
FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatHLSL );
}
MODULE_END;
Var* TerrainFeatHLSL::_getUniformVar( const char *name, const char *type, ConstantSortPosition csp )
{
Var *theVar = (Var*)LangElement::find( name );
if ( !theVar )
{
theVar = new Var;
theVar->setType( type );
theVar->setName( name );
theVar->uniform = true;
theVar->constSortPos = csp;
}
return theVar;
}
Var* TerrainFeatHLSL::_getInDetailCoord( Vector<ShaderComponent*> &componentList )
{
String name( String::ToString( "detCoord%d", getProcessIndex() ) );
Var *inDet = (Var*)LangElement::find( name );
if ( !inDet )
{
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
inDet = connectComp->getElement( RT_TEXCOORD );
inDet->setName( name );
inDet->setStructName( "IN" );
inDet->setType( "float4" );
inDet->mapsToSampler = true;
}
return inDet;
}
Var* TerrainFeatHLSL::_getNormalMapTex()
{
String name( String::ToString( "normalMap%d", getProcessIndex() ) );
Var *normalMap = (Var*)LangElement::find( name );
if ( !normalMap )
{
normalMap = new Var;
normalMap->setType( "sampler2D" );
normalMap->setName( name );
normalMap->uniform = true;
normalMap->sampler = true;
normalMap->constNum = Var::getTexUnitNum();
}
return normalMap;
}
Var* TerrainFeatHLSL::_getDetailIdStrengthParallax()
{
String name( String::ToString( "detailIdStrengthParallax%d", getProcessIndex() ) );
Var *detailInfo = (Var*)LangElement::find( name );
if ( !detailInfo )
{
detailInfo = new Var;
detailInfo->setType( "float3" );
detailInfo->setName( name );
detailInfo->uniform = true;
detailInfo->constSortPos = cspPotentialPrimitive;
}
return detailInfo;
}
void TerrainBaseMapFeatHLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
MultiLine *meta = new MultiLine;
output = meta;
// Generate the incoming texture var.
Var *inTex;
{
Var *inPos = (Var*)LangElement::find( "inPosition" );
if ( !inPos )
inPos = (Var*)LangElement::find( "position" );
inTex = new Var( "texCoord", "float3" );
Var *oneOverTerrainSize = _getUniformVar( "oneOverTerrainSize", "float", cspPass );
// NOTE: The y coord here should be negative to have
// the texture maps not end up flipped which also caused
// normal and parallax mapping to be incorrect.
//
// This mistake early in development means that the layer
// id bilinear blend depends on it being that way.
//
// So instead i fixed this by flipping the base and detail
// coord y scale to compensate when rendering.
//
meta->addStatement( new GenOp( " @ = @.xyz * float3( @, @, -@ );\r\n",
new DecOp( inTex ), inPos, oneOverTerrainSize, oneOverTerrainSize, oneOverTerrainSize ) );
}
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
// Pass the texture coord to the pixel shader.
Var *outTex = connectComp->getElement( RT_TEXCOORD );
outTex->setName( "outTexCoord" );
outTex->setStructName( "OUT" );
outTex->setType( "float3" );
outTex->mapsToSampler = true;
meta->addStatement( new GenOp( " @.xy = @.xy;\r\n", outTex, inTex ) );
// If this shader has a side projected layer then we
// pass the dot product between the +Y and the normal
// thru outTexCoord.z for use in blending the textures.
if ( fd.features.hasFeature( MFT_TerrainSideProject ) )
{
Var *inNormal = (Var*)LangElement::find( "normal" );
meta->addStatement(
new GenOp( " @.z = pow( abs( dot( normalize( float3( @.x, @.y, 0 ) ), float3( 0, 1, 0 ) ) ), 10.0 );\r\n",
outTex, inNormal, inNormal ) );
}
else
meta->addStatement( new GenOp( " @.z = 0;\r\n", outTex ) );
// HACK: This is sort of lazy... we generate the tanget
// vector here so that we're sure it exists in the parallax
// and normal features which will expect "T" to exist.
//
// If this shader doesn't use it the shader compiler will
// optimize away this code.
//
Var *inTangentZ = getVertTexCoord( "tcTangentZ" );
Var *inTanget = new Var( "T", "float3" );
Var *squareSize = _getUniformVar( "squareSize", "float", cspPass );
meta->addStatement( new GenOp( " @ = normalize( float3( @, 0, @ ) );\r\n",
new DecOp( inTanget ), squareSize, inTangentZ ) );
}
void TerrainBaseMapFeatHLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// grab connector texcoord register
Var *texCoord = getInTexCoord( "texCoord", "float3", true, componentList );
// We do nothing more if this is a prepass.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
// create texture var
Var *diffuseMap = new Var;
diffuseMap->setType( "sampler2D" );
diffuseMap->setName( "baseTexMap" );
diffuseMap->uniform = true;
diffuseMap->sampler = true;
diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here
MultiLine *meta = new MultiLine;
Var *baseColor = new Var;
baseColor->setType( "float4" );
baseColor->setName( "baseColor" );
meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) );
meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) );
output = meta;
}
ShaderFeature::Resources TerrainBaseMapFeatHLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
res.numTexReg = 1;
// We only sample from the base map during a diffuse pass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
res.numTex = 1;
return res;
}
TerrainDetailMapFeatHLSL::TerrainDetailMapFeatHLSL()
: mTorqueDep( "shaders/common/torque.hlsl" ),
mTerrainDep( "shaders/common/terrain/terrain.hlsl" )
{
addDependency( &mTorqueDep );
addDependency( &mTerrainDep );
}
void TerrainDetailMapFeatHLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
const U32 detailIndex = getProcessIndex();
// Grab incoming texture coords... the base map feature
// made sure this was created.
Var *inTex = (Var*)LangElement::find( "texCoord" );
AssertFatal( inTex, "The texture coord is missing!" );
// Grab the input position.
Var *inPos = (Var*)LangElement::find( "inPosition" );
if ( !inPos )
inPos = (Var*)LangElement::find( "position" );
// Get the object space eye position.
Var *eyePos = _getUniformVar( "eyePos", "float3", cspPotentialPrimitive );
MultiLine *meta = new MultiLine;
// If we have parallax mapping then make sure we've sent
// the negative view vector to the pixel shader.
if ( fd.features.hasFeature( MFT_TerrainParallaxMap ) &&
!LangElement::find( "outNegViewTS" ) )
{
// Get the object to tangent transform which
// will consume 3 output registers.
Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta, fd );
// Now use a single output register to send the negative
// view vector in tangent space to the pixel shader.
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
Var *outNegViewTS = connectComp->getElement( RT_TEXCOORD );
outNegViewTS->setName( "outNegViewTS" );
outNegViewTS->setStructName( "OUT" );
outNegViewTS->setType( "float3" );
meta->addStatement( new GenOp( " @ = mul( @, float3( @ - @.xyz ) );\r\n",
outNegViewTS, objToTangentSpace, eyePos, inPos ) );
}
// Get the distance from the eye to this vertex.
Var *dist = (Var*)LangElement::find( "dist" );
if ( !dist )
{
dist = new Var;
dist->setType( "float" );
dist->setName( "dist" );
meta->addStatement( new GenOp( " @ = distance( @.xyz, @ );\r\n",
new DecOp( dist ), inPos, eyePos ) );
}
// grab connector texcoord register
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
Var *outTex = connectComp->getElement( RT_TEXCOORD );
outTex->setName( String::ToString( "detCoord%d", detailIndex ) );
outTex->setStructName( "OUT" );
outTex->setType( "float4" );
outTex->mapsToSampler = true;
// Get the detail scale and fade info.
Var *detScaleAndFade = new Var;
detScaleAndFade->setType( "float4" );
detScaleAndFade->setName( String::ToString( "detailScaleAndFade%d", detailIndex ) );
detScaleAndFade->uniform = true;
detScaleAndFade->constSortPos = cspPotentialPrimitive;
// Setup the detail coord.
//
// NOTE: You see here we scale the texture coord by 'xyx'
// to generate the detail coord. This y is here because
// its scale is flipped to correct for the non negative y
// in texCoord.
//
// See TerrainBaseMapFeatHLSL::processVert().
//
meta->addStatement( new GenOp( " @.xyz = @ * @.xyx;\r\n", outTex, inTex, detScaleAndFade ) );
// And sneak the detail fade thru the w detailCoord.
meta->addStatement( new GenOp( " @.w = clamp( ( @.z - @ ) * @.w, 0.0, 1.0 );\r\n",
outTex, detScaleAndFade, dist, detScaleAndFade ) );
output = meta;
}
void TerrainDetailMapFeatHLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
const U32 detailIndex = getProcessIndex();
Var *inTex = getVertTexCoord( "texCoord" );
MultiLine *meta = new MultiLine;
// We need the negative tangent space view vector
// as in parallax mapping we step towards the camera.
Var *negViewTS = (Var*)LangElement::find( "negViewTS" );
if ( !negViewTS &&
fd.features.hasFeature( MFT_TerrainParallaxMap ) )
{
Var *inNegViewTS = (Var*)LangElement::find( "outNegViewTS" );
if ( !inNegViewTS )
{
ShaderConnector *connectComp = dynamic_cast<ShaderConnector *>( componentList[C_CONNECTOR] );
inNegViewTS = connectComp->getElement( RT_TEXCOORD );
inNegViewTS->setName( "outNegViewTS" );
inNegViewTS->setStructName( "IN" );
inNegViewTS->setType( "float3" );
}
negViewTS = new Var( "negViewTS", "float3" );
meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", new DecOp( negViewTS ), inNegViewTS ) );
}
// Get the layer samples.
Var *layerSample = (Var*)LangElement::find( "layerSample" );
if ( !layerSample )
{
layerSample = new Var;
layerSample->setType( "float4" );
layerSample->setName( "layerSample" );
// Get the layer texture var
Var *layerTex = new Var;
layerTex->setType( "sampler2D" );
layerTex->setName( "layerTex" );
layerTex->uniform = true;
layerTex->sampler = true;
layerTex->constNum = Var::getTexUnitNum();
// Read the layer texture to get the samples.
meta->addStatement( new GenOp( " @ = round( tex2D( @, @.xy ) * 255.0f );\r\n",
new DecOp( layerSample ), layerTex, inTex ) );
}
Var *layerSize = (Var*)LangElement::find( "layerSize" );
if ( !layerSize )
{
layerSize = new Var;
layerSize->setType( "float" );
layerSize->setName( "layerSize" );
layerSize->uniform = true;
layerSize->constSortPos = cspPass;
}
// Grab the incoming detail coord.
Var *inDet = _getInDetailCoord( componentList );
// Get the detail id.
Var *detailInfo = _getDetailIdStrengthParallax();
// Create the detail blend var.
Var *detailBlend = new Var;
detailBlend->setType( "float" );
detailBlend->setName( String::ToString( "detailBlend%d", detailIndex ) );
// Calculate the blend for this detail texture.
meta->addStatement( new GenOp( " @ = calcBlend( @.x, @.xy, @, @ );\r\n",
new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) );
// Get a var and accumulate the blend amount.
Var *blendTotal = (Var*)LangElement::find( "blendTotal" );
if ( !blendTotal )
{
blendTotal = new Var;
blendTotal->setName( "blendTotal" );
blendTotal->setType( "float" );
meta->addStatement( new GenOp( " @ = 0;\r\n", new DecOp( blendTotal ) ) );
}
// Add to the blend total.
meta->addStatement( new GenOp( " @ = max( @, @ );\r\n", blendTotal, blendTotal, detailBlend ) );
// If we had a parallax feature... then factor in the parallax
// amount so that it fades out with the layer blending.
if ( fd.features.hasFeature( MFT_TerrainParallaxMap, detailIndex ) )
{
// Get the rest of our inputs.
Var *normalMap = _getNormalMapTex();
// Call the library function to do the rest.
meta->addStatement( new GenOp( " @.xy += parallaxOffset( @, @.xy, @, @.z * @ );\r\n",
inDet, normalMap, inDet, negViewTS, detailInfo, detailBlend ) );
}
// If this is a prepass then we skip color.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
{
// Check to see if we have a gbuffer normal.
Var *gbNormal = (Var*)LangElement::find( "gbNormal" );
// If we have a gbuffer normal and we don't have a
// normal map feature then we need to lerp in a
// default normal else the normals below this layer
// will show thru.
if ( gbNormal &&
!fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) )
{
Var *viewToTangent = getInViewToTangent( componentList );
meta->addStatement( new GenOp( " @ = lerp( @, @[2], min( @, @.w ) );\r\n",
gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) );
}
output = meta;
return;
}
Var *detailColor = (Var*)LangElement::find( "detailColor" );
if ( !detailColor )
{
detailColor = new Var;
detailColor->setType( "float4" );
detailColor->setName( "detailColor" );
meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailColor ) ) );
}
// Get the detail texture.
Var *detailMap = new Var;
detailMap->setType( "sampler2D" );
detailMap->setName( String::ToString( "detailMap%d", detailIndex ) );
detailMap->uniform = true;
detailMap->sampler = true;
detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here
// If we're using SM 3.0 then take advantage of
// dynamic branching to skip layers per-pixel.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) );
meta->addStatement( new GenOp( " {\r\n" ) );
// Note that we're doing the standard greyscale detail
// map technique here which can darken and lighten the
// diffuse texture.
//
// We take two color samples and lerp between them for
// side projection layers... else a single sample.
//
if ( fd.features.hasFeature( MFT_TerrainSideProject, detailIndex ) )
{
meta->addStatement( new GenOp( " @ = ( lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n",
detailColor, detailMap, inDet, detailMap, inDet, inTex ) );
}
else
{
meta->addStatement( new GenOp( " @ = ( tex2D( @, @.xy ) * 2.0 ) - 1.0;\r\n",
detailColor, detailMap, inDet ) );
}
meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n",
detailColor, detailInfo, inDet ) );
Var *baseColor = (Var*)LangElement::find( "baseColor" );
Var *outColor = (Var*)LangElement::find( "col" );
meta->addStatement( new GenOp( " @ = lerp( @, @ + @, @ );\r\n",
outColor, outColor, baseColor, detailColor, detailBlend ) );
meta->addStatement( new GenOp( " }\r\n" ) );
output = meta;
}
ShaderFeature::Resources TerrainDetailMapFeatHLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
if ( getProcessIndex() == 0 )
{
// If this is the first detail pass then we
// samples from the layer tex.
res.numTex += 1;
// If this material also does parallax then it
// will generate the negative view vector and the
// worldToTanget transform.
if ( fd.features.hasFeature( MFT_TerrainParallaxMap ) )
res.numTexReg += 4;
}
// If this isn't the prepass then we sample
// from the detail texture for diffuse coloring.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
res.numTex += 1;
// If we have parallax for this layer then we'll also
// be sampling the normal map for the parallax heightmap.
if ( fd.features.hasFeature( MFT_TerrainParallaxMap, getProcessIndex() ) )
res.numTex += 1;
// Finally we always send the detail texture
// coord to the pixel shader.
res.numTexReg += 1;
return res;
}
void TerrainNormalMapFeatHLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// We only need to process normals during the prepass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
MultiLine *meta = new MultiLine;
// Make sure the world to tangent transform
// is created and available for the pixel shader.
getOutViewToTangent( componentList, meta, fd );
output = meta;
}
void TerrainNormalMapFeatHLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// We only need to process normals during the prepass.
if ( !fd.features.hasFeature( MFT_PrePassConditioner ) )
return;
MultiLine *meta = new MultiLine;
Var *viewToTangent = getInViewToTangent( componentList );
// This var is read from GBufferConditionerHLSL and
// used in the prepass output.
Var *gbNormal = (Var*)LangElement::find( "gbNormal" );
if ( !gbNormal )
{
gbNormal = new Var;
gbNormal->setName( "gbNormal" );
gbNormal->setType( "float3" );
meta->addStatement( new GenOp( " @ = @[2];\r\n", new DecOp( gbNormal ), viewToTangent ) );
}
const U32 normalIndex = getProcessIndex();
Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) );
AssertFatal( detailBlend, "The detail blend is missing!" );
// If we're using SM 3.0 then take advantage of
// dynamic branching to skip layers per-pixel.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) );
meta->addStatement( new GenOp( " {\r\n" ) );
// Get the normal map texture.
Var *normalMap = _getNormalMapTex();
/// Get the texture coord.
Var *inDet = _getInDetailCoord( componentList );
Var *inTex = getVertTexCoord( "texCoord" );
// Sample the normal map.
//
// We take two normal samples and lerp between them for
// side projection layers... else a single sample.
LangElement *texOp;
if ( fd.features.hasFeature( MFT_TerrainSideProject, normalIndex ) )
{
texOp = new GenOp( "lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z )",
normalMap, inDet, normalMap, inDet, inTex );
}
else
texOp = new GenOp( "tex2D(@, @.xy)", normalMap, inDet );
// create bump normal
Var *bumpNorm = new Var;
bumpNorm->setName( "bumpNormal" );
bumpNorm->setType( "float4" );
LangElement *bumpNormDecl = new DecOp( bumpNorm );
meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) );
// Normalize is done later...
// Note: The reverse mul order is intentional. Affine matrix.
meta->addStatement( new GenOp( " @ = lerp( @, mul( @.xyz, @ ), min( @, @.w ) );\r\n",
gbNormal, gbNormal, bumpNorm, viewToTangent, detailBlend, inDet ) );
// End the conditional block.
meta->addStatement( new GenOp( " }\r\n" ) );
// If this is the last normal map then we
// can test to see the total blend value
// to see if we should clip the result.
//if ( fd.features.getNextFeatureIndex( MFT_TerrainNormalMap, normalIndex ) == -1 )
//meta->addStatement( new GenOp( " clip( @ - 0.0001f );\r\n", blendTotal ) );
output = meta;
}
ShaderFeature::Resources TerrainNormalMapFeatHLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
// We only need to process normals during the prepass.
if ( fd.features.hasFeature( MFT_PrePassConditioner ) )
{
// If this is the first normal map and there
// are no parallax features then we will
// generate the worldToTanget transform.
if ( !fd.features.hasFeature( MFT_TerrainParallaxMap ) &&
( getProcessIndex() == 0 || !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) ) )
res.numTexReg = 3;
res.numTex = 1;
}
return res;
}
void TerrainLightMapFeatHLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
// grab connector texcoord register
Var *inTex = (Var*)LangElement::find( "texCoord" );
if ( !inTex )
return;
// Get the lightmap texture.
Var *lightMap = new Var;
lightMap->setType( "sampler2D" );
lightMap->setName( "lightMapTex" );
lightMap->uniform = true;
lightMap->sampler = true;
lightMap->constNum = Var::getTexUnitNum();
MultiLine *meta = new MultiLine;
// Find or create the lightMask value which is read by
// RTLighting to mask out the lights.
//
// The first light is always the sunlight so we apply
// the shadow mask to only the first channel.
//
Var *lightMask = (Var*)LangElement::find( "lightMask" );
if ( !lightMask )
{
lightMask = new Var( "lightMask", "float4" );
meta->addStatement( new GenOp( " @ = 1;\r\n", new DecOp( lightMask ) ) );
}
meta->addStatement( new GenOp( " @[0] = tex2D( @, @.xy ).r;\r\n", lightMask, lightMap, inTex ) );
output = meta;
}
ShaderFeature::Resources TerrainLightMapFeatHLSL::getResources( const MaterialFeatureData &fd )
{
Resources res;
res.numTex = 1;
return res;
}
void TerrainAdditiveFeatHLSL::processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
Var *color = (Var*) LangElement::find( "col" );
Var *blendTotal = (Var*)LangElement::find( "blendTotal" );
if ( !color || !blendTotal )
return;
MultiLine *meta = new MultiLine;
meta->addStatement( new GenOp( " clip( @ - 0.0001 );\r\n", blendTotal ) );
meta->addStatement( new GenOp( " @.a = @;\r\n", color, blendTotal ) );
output = meta;
}

View file

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRFEATUREHLSL_H_
#define _TERRFEATUREHLSL_H_
#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_
#include "shaderGen/HLSL/shaderFeatureHLSL.h"
#endif
#ifndef _LANG_ELEMENT_H_
#include "shaderGen/langElement.h"
#endif
/// A shared base class for terrain features which
/// includes some helper functions.
class TerrainFeatHLSL : public ShaderFeatureHLSL
{
protected:
Var* _getInDetailCoord(Vector<ShaderComponent*> &componentList );
Var* _getNormalMapTex();
static Var* _getUniformVar( const char *name, const char *type, ConstantSortPosition csp );
Var* _getDetailIdStrengthParallax();
};
class TerrainBaseMapFeatHLSL : public TerrainFeatHLSL
{
public:
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Base Texture"; }
};
class TerrainDetailMapFeatHLSL : public TerrainFeatHLSL
{
protected:
ShaderIncludeDependency mTorqueDep;
ShaderIncludeDependency mTerrainDep;
public:
TerrainDetailMapFeatHLSL();
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Detail Texture"; }
};
class TerrainNormalMapFeatHLSL : public TerrainFeatHLSL
{
public:
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Normal Texture"; }
};
class TerrainLightMapFeatHLSL : public TerrainFeatHLSL
{
public:
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual Resources getResources( const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Lightmap Texture"; }
};
class TerrainAdditiveFeatHLSL : public TerrainFeatHLSL
{
public:
virtual void processPix( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual String getName() { return "Terrain Additive"; }
};
#endif // _TERRFEATUREHLSL_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,248 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRCELL_H_
#define _TERRCELL_H_
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _TDICTIONARY_H_
#include "core/util/tDictionary.h"
#endif
#ifndef _MORIENTEDBOX_H_
#include "math/mOrientedBox.h"
#endif
#ifndef _BITVECTOR_H_
#include "core/bitVector.h"
#endif
class TerrainBlock;
class TerrainCellMaterial;
class Frustum;
class SceneRenderState;
class SceneZoneSpaceManager;
/// The TerrainCell vertex format optimized to
/// 32 bytes for optimal vertex cache performance.
GFXDeclareVertexFormat( TerrVertex )
{
/// The position.
Point3F point;
/// The normal.
Point3F normal;
/// The height for calculating the
/// tangent vector on the GPU.
F32 tangentZ;
/// The empty flag state which is either
/// -1 or 1 so we can do the special
/// interpolation trick.
F32 empty;
};
/// The TerrCell is a single quadrant of the terrain geometry quadtree.
class TerrCell
{
protected:
/// The handle to the static vertex buffer which holds the
/// vertices for this cell.
GFXVertexBufferHandle<TerrVertex> mVertexBuffer;
/// The handle to the static primitive buffer for this cell.
/// It is only used if this cell has any empty squares
GFXPrimitiveBufferHandle mPrimBuffer;
///
Point2I mPoint;
///
U32 mSize;
/// The level of this cell within the quadtree (of cells) where
/// zero is the root and one is a direct child of the root, etc.
U32 mLevel;
/// Statics used in VB and PB generation.
static const U32 smVBStride;
static const U32 smMinCellSize;
static const U32 smVBSize;
static const U32 smPBSize;
static const U32 smTriCount;
/// Triangle count for our own primitive buffer, if any
U32 mTriCount;
/// Indicates if this cell has any empty squares
bool mHasEmpty;
/// A list of all empty vertices for this cell
Vector<U32> mEmptyVertexList;
/// The terrain this cell is based on.
TerrainBlock *mTerrain;
/// The material used to render the cell.
TerrainCellMaterial *mMaterial;
/// The bounding box of this cell in
/// TerrainBlock object space.
Box3F mBounds;
/// The OBB of this cell in world space.
OrientedBox3F mOBB;
/// The bounding radius of this cell.
F32 mRadius;
/// The child cells of this one.
TerrCell *mChildren[4];
/// This bit flag tells us which materials effect
/// this cell and is used for optimizing rendering.
/// @see TerrainFile::mMaterialAlphaMap
U64 mMaterials;
/// Whether this cell is fully contained inside interior zones.
bool mIsInteriorOnly;
/// The zone overlap for this cell.
/// @note The bit for the outdoor zone is never set.
BitVector mZoneOverlap;
///
void _updateBounds();
/// Update #mOBB from the current terrain transform state.
void _updateOBB();
//
void _init( TerrainBlock *terrain,
const Point2I &point,
U32 size,
U32 level );
//
void _updateVertexBuffer();
//
void _updatePrimitiveBuffer();
//
void _updateMaterials();
//
bool _isVertIndexEmpty( U32 index ) const;
public:
TerrCell();
virtual ~TerrCell();
static TerrCell* init( TerrainBlock *terrain );
void getRenderPrimitive( GFXPrimitive *prim,
GFXVertexBufferHandleBase *vertBuff,
GFXPrimitiveBufferHandle *primBuff ) const;
void updateGrid( const RectI &gridRect, bool opacityOnly = false );
/// Update the world-space OBBs used for culling.
void updateOBBs();
///
void updateZoning( const SceneZoneSpaceManager *zoneManager );
void cullCells( const SceneRenderState *state,
const Point3F &objLodPos,
Vector<TerrCell*> *outCells );
const Box3F& getBounds() const { return mBounds; }
/// Returns the object space sphere bounds.
SphereF getSphereBounds() const { return SphereF( mBounds.getCenter(), mRadius ); }
F32 getSqDistanceTo( const Point3F &pt ) const;
F32 getDistanceTo( const Point3F &pt ) const;
U64 getMaterials() const { return mMaterials; }
/// Returns a bit vector of what zones overlap this cell.
const BitVector& getZoneOverlap() const { return mZoneOverlap; }
/// Forces the loading of the materials for this
/// cell and all its child cells.
void preloadMaterials();
TerrainCellMaterial* getMaterial();
/// Return true if this is a leaf cell, i.e. a cell without children.
bool isLeaf() const { return !mChildren[ 0 ]; }
/// Deletes the materials for this cell
/// and all its children. They will be
/// recreate on the next request.
void deleteMaterials();
U32 getSize() const { return mSize; }
Point2I getPoint() const { return mPoint; }
/// Initializes a primitive buffer for rendering any cell.
static void createPrimBuffer( GFXPrimitiveBufferHandle *primBuffer );
/// Debug Rendering
/// @{
/// Renders the debug bounds for this cell.
void renderBounds() const;
/// @}
};
inline F32 TerrCell::getDistanceTo( const Point3F &pt ) const
{
return ( mBounds.getCenter() - pt ).len() - mRadius;
}
inline bool TerrCell::_isVertIndexEmpty( U32 index ) const
{
for ( U32 i = 0; i < mEmptyVertexList.size(); ++i )
{
if ( mEmptyVertexList[i] == index )
{
return true;
}
}
return false;
}
#endif // _TERRCELL_H_

View file

@ -0,0 +1,780 @@
//-----------------------------------------------------------------------------
// 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/terrCellMaterial.h"
#include "terrain/terrData.h"
#include "terrain/terrCell.h"
#include "materials/materialFeatureTypes.h"
#include "materials/materialManager.h"
#include "terrain/terrFeatureTypes.h"
#include "terrain/terrMaterial.h"
#include "renderInstance/renderPrePassMgr.h"
#include "shaderGen/shaderGen.h"
#include "shaderGen/featureMgr.h"
#include "scene/sceneRenderState.h"
#include "materials/sceneData.h"
#include "gfx/util/screenspace.h"
#include "lighting/advanced/advancedLightBinManager.h"
AFTER_MODULE_INIT( MaterialManager )
{
Con::NotifyDelegate callabck( &TerrainCellMaterial::_updateDefaultAnisotropy );
Con::addVariableNotify( "$pref::Video::defaultAnisotropy", callabck );
}
Vector<TerrainCellMaterial*> TerrainCellMaterial::smAllMaterials;
TerrainCellMaterial::TerrainCellMaterial()
: mCurrPass( 0 ),
mTerrain( NULL ),
mPrePassMat( NULL ),
mReflectMat( NULL )
{
smAllMaterials.push_back( this );
}
TerrainCellMaterial::~TerrainCellMaterial()
{
SAFE_DELETE( mPrePassMat );
SAFE_DELETE( mReflectMat );
smAllMaterials.remove( this );
}
void TerrainCellMaterial::_updateDefaultAnisotropy()
{
// TODO: We need to split the stateblock initialization
// from the shader constant lookup and pass setup in a
// future version of terrain materials.
//
// For now use some custom code in a horrible loop to
// change the anisotropy directly and fast.
//
const U32 maxAnisotropy = MATMGR->getDefaultAnisotropy();
Vector<TerrainCellMaterial*>::iterator iter = smAllMaterials.begin();
for ( ; iter != smAllMaterials.end(); iter++ )
{
for ( U32 p=0; p < (*iter)->mPasses.size(); p++ )
{
Pass &pass = (*iter)->mPasses[p];
// Start from the existing state block.
GFXStateBlockDesc desc = pass.stateBlock->getDesc();
for ( U32 m=0; m < pass.materials.size(); m++ )
{
const MaterialInfo *matInfo = pass.materials[m];
if ( matInfo->detailTexConst->isValid() )
{
const S32 sampler = matInfo->detailTexConst->getSamplerRegister();
if ( maxAnisotropy > 1 )
{
desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic;
desc.samplers[sampler].maxAnisotropy = maxAnisotropy;
}
else
desc.samplers[sampler].minFilter = GFXTextureFilterLinear;
}
if ( matInfo->normalTexConst->isValid() )
{
const S32 sampler = matInfo->normalTexConst->getSamplerRegister();
if ( maxAnisotropy > 1 )
{
desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic;
desc.samplers[sampler].maxAnisotropy = maxAnisotropy;
}
else
desc.samplers[sampler].minFilter = GFXTextureFilterLinear;
}
} // for ( U32 m=0; m < pass.materials.size(); m++ )
// Set the updated stateblock.
pass.stateBlock = GFX->createStateBlock( desc );
// Create the wireframe state blocks.
GFXStateBlockDesc wireframe( desc );
wireframe.fillMode = GFXFillWireframe;
pass.wireframeStateBlock = GFX->createStateBlock( wireframe );
} // for ( U32 p=0; i < (*iter)->mPasses.size(); p++ )
}
}
void TerrainCellMaterial::setTransformAndEye( const MatrixF &modelXfm,
const MatrixF &viewXfm,
const MatrixF &projectXfm,
F32 farPlane )
{
PROFILE_SCOPE( TerrainCellMaterial_SetTransformAndEye );
MatrixF modelViewProj = projectXfm * viewXfm * modelXfm;
MatrixF invViewXfm( viewXfm );
invViewXfm.inverse();
Point3F eyePos = invViewXfm.getPosition();
MatrixF invModelXfm( modelXfm );
invModelXfm.inverse();
Point3F objEyePos = eyePos;
invModelXfm.mulP( objEyePos );
VectorF vEye = invViewXfm.getForwardVector();
vEye.normalize( 1.0f / farPlane );
for ( U32 i=0; i < mPasses.size(); i++ )
{
Pass &pass = mPasses[i];
pass.consts->setSafe( pass.modelViewProjConst, modelViewProj );
if( pass.viewToObj->isValid() || pass.worldViewOnly->isValid() )
{
MatrixF worldViewOnly = viewXfm * modelXfm;
pass.consts->setSafe( pass.worldViewOnly, worldViewOnly );
if( pass.viewToObj->isValid() )
{
worldViewOnly.affineInverse();
pass.consts->set( pass.viewToObj, worldViewOnly);
}
}
pass.consts->setSafe( pass.eyePosWorldConst, eyePos );
pass.consts->setSafe( pass.eyePosConst, objEyePos );
pass.consts->setSafe( pass.objTransConst, modelXfm );
pass.consts->setSafe( pass.worldToObjConst, invModelXfm );
pass.consts->setSafe( pass.vEyeConst, vEye );
}
}
TerrainCellMaterial* TerrainCellMaterial::getPrePassMat()
{
if ( !mPrePassMat )
{
mPrePassMat = new TerrainCellMaterial();
mPrePassMat->init( mTerrain, mMaterials, true, false, mMaterials == 0 );
}
return mPrePassMat;
}
TerrainCellMaterial* TerrainCellMaterial::getReflectMat()
{
if ( !mReflectMat )
{
mReflectMat = new TerrainCellMaterial();
mReflectMat->init( mTerrain, mMaterials, false, true, true );
}
return mReflectMat;
}
void TerrainCellMaterial::init( TerrainBlock *block,
U64 activeMaterials,
bool prePassMat,
bool reflectMat,
bool baseOnly )
{
// This isn't allowed for now.
AssertFatal( !( prePassMat && reflectMat ), "TerrainCellMaterial::init - We shouldn't get prepass and reflection in the same material!" );
mTerrain = block;
mMaterials = activeMaterials;
Vector<MaterialInfo*> materials;
for ( U32 i = 0; i < 64; i++ )
{
if ( !( mMaterials & ((U64)1 << i ) ) )
continue;
TerrainMaterial *mat = block->getMaterial( i );
MaterialInfo *info = new MaterialInfo();
info->layerId = i;
info->mat = mat;
materials.push_back( info );
}
mCurrPass = 0;
mPasses.clear();
// Ok... loop till we successfully generate all
// the shader passes for the materials.
while ( materials.size() > 0 || baseOnly )
{
mPasses.increment();
if ( !_createPass( &materials,
&mPasses.last(),
mPasses.size() == 1,
prePassMat,
reflectMat,
baseOnly ) )
{
Con::errorf( "TerrainCellMaterial::init - Failed to create pass!" );
// The pass failed to be generated... give up.
mPasses.last().materials.clear();
mPasses.clear();
for_each( materials.begin(), materials.end(), delete_pointer() );
return;
}
if ( baseOnly )
break;
}
// Cleanup any remaining matinfo.
for_each( materials.begin(), materials.end(), delete_pointer() );
// If we have attached mats then update them too.
if ( mPrePassMat )
mPrePassMat->init( mTerrain, mMaterials, true, false, baseOnly );
if ( mReflectMat )
mReflectMat->init( mTerrain, mMaterials, false, true, baseOnly );
}
bool TerrainCellMaterial::_createPass( Vector<MaterialInfo*> *materials,
Pass *pass,
bool firstPass,
bool prePassMat,
bool reflectMat,
bool baseOnly )
{
if ( GFX->getPixelShaderVersion() < 3.0f )
baseOnly = true;
// NOTE: At maximum we only try to combine 3 materials
// into a single pass. This is sub-optimal for the simplest
// cases, but the most common case results in much fewer
// shader generation failures and permutations leading to
// faster load time and less hiccups during gameplay.
U32 matCount = getMin( 3, materials->size() );
Vector<GFXTexHandle> normalMaps;
// See if we're currently running under the
// basic lighting manager.
//
// TODO: This seems ugly... we should trigger
// features like this differently in the future.
//
bool useBLM = dStrcmp( LIGHTMGR->getId(), "BLM" ) == 0;
// Do we need to disable normal mapping?
const bool disableNormalMaps = MATMGR->getExclusionFeatures().hasFeature( MFT_NormalMap ) || useBLM;
// How about parallax?
const bool disableParallaxMaps = GFX->getPixelShaderVersion() < 3.0f ||
MATMGR->getExclusionFeatures().hasFeature( MFT_Parallax );
// Has advanced lightmap support been enabled for prepass.
bool advancedLightmapSupport = false;
if ( prePassMat )
{
// This sucks... but it works.
AdvancedLightBinManager *lightBin;
if ( Sim::findObject( "AL_LightBinMgr", lightBin ) )
advancedLightmapSupport = lightBin->MRTLightmapsDuringPrePass();
}
// Loop till we create a valid shader!
while( true )
{
FeatureSet features;
features.addFeature( MFT_VertTransform );
features.addFeature( MFT_TerrainBaseMap );
if ( prePassMat )
{
features.addFeature( MFT_EyeSpaceDepthOut );
features.addFeature( MFT_PrePassConditioner );
if ( advancedLightmapSupport )
features.addFeature( MFT_RenderTarget1_Zero );
}
else
{
features.addFeature( MFT_RTLighting );
// The HDR feature is always added... it will compile out
// if HDR is not enabled in the engine.
features.addFeature( MFT_HDROut );
}
// Enable lightmaps and fogging if we're in BL.
if ( reflectMat || useBLM )
{
features.addFeature( MFT_Fog );
features.addFeature( MFT_ForwardShading );
}
if ( useBLM )
features.addFeature( MFT_TerrainLightMap );
// The additional passes need to be lerp blended into the
// target to maintain the results of the previous passes.
if ( !firstPass )
features.addFeature( MFT_TerrainAdditive );
normalMaps.clear();
pass->materials.clear();
// Now add all the material layer features.
for ( U32 i=0; i < matCount && !baseOnly; i++ )
{
TerrainMaterial *mat = (*materials)[i]->mat;
if ( mat == NULL )
continue;
// We only include materials that
// have more than a base texture.
if ( mat->getDetailSize() <= 0 ||
mat->getDetailDistance() <= 0 ||
mat->getDetailMap().isEmpty() )
continue;
S32 featureIndex = pass->materials.size();
features.addFeature( MFT_TerrainDetailMap, featureIndex );
pass->materials.push_back( (*materials)[i] );
normalMaps.increment();
// Skip normal maps if we need to.
if ( !disableNormalMaps && mat->getNormalMap().isNotEmpty() )
{
features.addFeature( MFT_TerrainNormalMap, featureIndex );
normalMaps.last().set( mat->getNormalMap(),
&GFXDefaultStaticNormalMapProfile, "TerrainCellMaterial::_createPass() - NormalMap" );
if ( normalMaps.last().getFormat() == GFXFormatDXT5 )
features.addFeature( MFT_IsDXTnm, featureIndex );
// Do we need and can we do parallax mapping?
if ( !disableParallaxMaps &&
mat->getParallaxScale() > 0.0f &&
!mat->useSideProjection() )
features.addFeature( MFT_TerrainParallaxMap, featureIndex );
}
// Is this layer got side projection?
if ( mat->useSideProjection() )
features.addFeature( MFT_TerrainSideProject, featureIndex );
}
MaterialFeatureData featureData;
featureData.features = features;
featureData.materialFeatures = features;
// Check to see how many vertex shader output
// registers we're gonna need.
U32 numTex = 0;
U32 numTexReg = 0;
for ( U32 i=0; i < features.getCount(); i++ )
{
S32 index;
const FeatureType &type = features.getAt( i, &index );
ShaderFeature* sf = FEATUREMGR->getByType( type );
if ( !sf )
continue;
sf->setProcessIndex( index );
ShaderFeature::Resources res = sf->getResources( featureData );
numTex += res.numTex;
numTexReg += res.numTexReg;
}
// Can we build the shader?
//
// NOTE: The 10 is sort of an abitrary SM 3.0
// limit. Its really supposed to be 11, but that
// always fails to compile so far.
//
if ( numTex < GFX->getNumSamplers() &&
numTexReg <= 10 )
{
// NOTE: We really shouldn't be getting errors building the shaders,
// but we can generate more instructions than the ps_2_x will allow.
//
// There is no way to deal with this case that i know of other than
// letting the compile fail then recovering by trying to build it
// with fewer materials.
//
// We normally disable the shader error logging so that the user
// isn't fooled into thinking there is a real bug. That is until
// we get down to a single material. If a single material case
// fails it means it cannot generate any passes at all!
const bool logErrors = matCount == 1;
GFXShader::setLogging( logErrors, true );
pass->shader = SHADERGEN->getShader( featureData, getGFXVertexFormat<TerrVertex>(), NULL );
}
// If we got a shader then we can continue.
if ( pass->shader )
break;
// If the material count is already 1 then this
// is a real shader error... give up!
if ( matCount <= 1 )
return false;
// If we failed we next try half the input materials
// so that we can more quickly arrive at a valid shader.
matCount -= matCount / 2;
}
// Setup the constant buffer.
pass->consts = pass->shader->allocConstBuffer();
// Prepare the basic constants.
pass->modelViewProjConst = pass->shader->getShaderConstHandle( "$modelview" );
pass->worldViewOnly = pass->shader->getShaderConstHandle( "$worldViewOnly" );
pass->viewToObj = pass->shader->getShaderConstHandle( "$viewToObj" );
pass->eyePosWorldConst = pass->shader->getShaderConstHandle( "$eyePosWorld" );
pass->eyePosConst = pass->shader->getShaderConstHandle( "$eyePos" );
pass->vEyeConst = pass->shader->getShaderConstHandle( "$vEye" );
pass->layerSizeConst = pass->shader->getShaderConstHandle( "$layerSize" );
pass->objTransConst = pass->shader->getShaderConstHandle( "$objTrans" );
pass->worldToObjConst = pass->shader->getShaderConstHandle( "$worldToObj" );
pass->lightInfoBufferConst = pass->shader->getShaderConstHandle( "$lightInfoBuffer" );
pass->baseTexMapConst = pass->shader->getShaderConstHandle( "$baseTexMap" );
pass->layerTexConst = pass->shader->getShaderConstHandle( "$layerTex" );
pass->fogDataConst = pass->shader->getShaderConstHandle( "$fogData" );
pass->fogColorConst = pass->shader->getShaderConstHandle( "$fogColor" );
pass->lightMapTexConst = pass->shader->getShaderConstHandle( "$lightMapTex" );
pass->oneOverTerrainSize = pass->shader->getShaderConstHandle( "$oneOverTerrainSize" );
pass->squareSize = pass->shader->getShaderConstHandle( "$squareSize" );
// NOTE: We're assuming rtParams0 here as we know its the only
// render target we currently get in a terrain material and the
// DeferredRTLightingFeatHLSL will always use 0.
//
// This could change in the future and we would need to fix
// the ShaderFeature API to allow us to do this right.
//
pass->lightParamsConst = pass->shader->getShaderConstHandle( "$rtParams0" );
// Now prepare the basic stateblock.
GFXStateBlockDesc desc;
if ( !firstPass )
{
desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha );
// If this is the prepass then we don't want to
// write to the last two color channels (where
// depth is usually encoded).
//
// This trick works in combination with the
// MFT_TerrainAdditive feature to lerp the
// output normal with the previous pass.
//
if ( prePassMat )
desc.setColorWrites( true, true, false, false );
}
// We write to the zbuffer if this is a prepass
// material or if the prepass is disabled.
// We also write the zbuffer if we're using OpenGL, because in OpenGL the prepass
// cannot share the same zbuffer as the backbuffer.
desc.setZReadWrite( true, !MATMGR->getPrePassEnabled() ||
GFX->getAdapterType() == OpenGL ||
prePassMat ||
reflectMat );
desc.samplersDefined = true;
if ( pass->baseTexMapConst->isValid() )
desc.samplers[pass->baseTexMapConst->getSamplerRegister()] = GFXSamplerStateDesc::getWrapLinear();
if ( pass->layerTexConst->isValid() )
desc.samplers[pass->layerTexConst->getSamplerRegister()] = GFXSamplerStateDesc::getClampPoint();
if ( pass->lightInfoBufferConst->isValid() )
desc.samplers[pass->lightInfoBufferConst->getSamplerRegister()] = GFXSamplerStateDesc::getClampPoint();
if ( pass->lightMapTexConst->isValid() )
desc.samplers[pass->lightMapTexConst->getSamplerRegister()] = GFXSamplerStateDesc::getWrapLinear();
const U32 maxAnisotropy = MATMGR->getDefaultAnisotropy();
// Finally setup the material specific shader
// constants and stateblock state.
//
// NOTE: If this changes be sure to check TerrainCellMaterial::_updateDefaultAnisotropy
// to see if it needs the same changes.
//
for ( U32 i=0; i < pass->materials.size(); i++ )
{
MaterialInfo *matInfo = pass->materials[i];
matInfo->detailInfoVConst = pass->shader->getShaderConstHandle( avar( "$detailScaleAndFade%d", i ) );
matInfo->detailInfoPConst = pass->shader->getShaderConstHandle( avar( "$detailIdStrengthParallax%d", i ) );
matInfo->detailTexConst = pass->shader->getShaderConstHandle( avar( "$detailMap%d", i ) );
if ( matInfo->detailTexConst->isValid() )
{
const S32 sampler = matInfo->detailTexConst->getSamplerRegister();
desc.samplers[sampler] = GFXSamplerStateDesc::getWrapLinear();
desc.samplers[sampler].magFilter = GFXTextureFilterLinear;
desc.samplers[sampler].mipFilter = GFXTextureFilterLinear;
if ( maxAnisotropy > 1 )
{
desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic;
desc.samplers[sampler].maxAnisotropy = maxAnisotropy;
}
else
desc.samplers[sampler].minFilter = GFXTextureFilterLinear;
matInfo->detailTex.set( matInfo->mat->getDetailMap(),
&GFXDefaultStaticDiffuseProfile, "TerrainCellMaterial::_createPass() - DetailMap" );
}
matInfo->normalTexConst = pass->shader->getShaderConstHandle( avar( "$normalMap%d", i ) );
if ( matInfo->normalTexConst->isValid() )
{
const S32 sampler = matInfo->normalTexConst->getSamplerRegister();
desc.samplers[sampler] = GFXSamplerStateDesc::getWrapLinear();
desc.samplers[sampler].magFilter = GFXTextureFilterLinear;
desc.samplers[sampler].mipFilter = GFXTextureFilterLinear;
if ( maxAnisotropy > 1 )
{
desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic;
desc.samplers[sampler].maxAnisotropy = maxAnisotropy;
}
else
desc.samplers[sampler].minFilter = GFXTextureFilterLinear;
matInfo->normalTex = normalMaps[i];
}
}
// Remove the materials we processed and leave the
// ones that remain for the next pass.
for ( U32 i=0; i < matCount; i++ )
{
MaterialInfo *matInfo = materials->first();
if ( baseOnly || pass->materials.find_next( matInfo ) == -1 )
delete matInfo;
materials->pop_front();
}
// If we're doing prepass it requires some
// special stencil settings for it to work.
if ( prePassMat )
desc.addDesc( RenderPrePassMgr::getOpaqueStenciWriteDesc( false ) );
// Flip the cull for reflection materials.
if ( reflectMat )
desc.setCullMode( GFXCullCW );
pass->stateBlock = GFX->createStateBlock( desc );
// Create the wireframe state blocks.
GFXStateBlockDesc wireframe( desc );
wireframe.fillMode = GFXFillWireframe;
pass->wireframeStateBlock = GFX->createStateBlock( wireframe );
return true;
}
void TerrainCellMaterial::_updateMaterialConsts( Pass *pass )
{
PROFILE_SCOPE( TerrainCellMaterial_UpdateMaterialConsts );
for ( U32 j=0; j < pass->materials.size(); j++ )
{
MaterialInfo *matInfo = pass->materials[j];
F32 detailSize = matInfo->mat->getDetailSize();
F32 detailScale = 1.0f;
if ( !mIsZero( detailSize ) )
detailScale = mTerrain->getWorldBlockSize() / detailSize;
// Scale the distance by the global scalar.
const F32 distance = mTerrain->smDetailScale * matInfo->mat->getDetailDistance();
// NOTE: The negation of the y scale is to make up for
// my mistake early in development and passing the wrong
// y texture coord into the system.
//
// This negation fixes detail, normal, and parallax mapping
// without harming the layer id blending code.
//
// Eventually we should rework this to correct this little
// mistake, but there isn't really a hurry to.
//
Point4F detailScaleAndFade( detailScale,
-detailScale,
distance,
0 );
if ( !mIsZero( distance ) )
detailScaleAndFade.w = 1.0f / distance;
Point3F detailIdStrengthParallax( matInfo->layerId,
matInfo->mat->getDetailStrength(),
matInfo->mat->getParallaxScale() );
pass->consts->setSafe( matInfo->detailInfoVConst, detailScaleAndFade );
pass->consts->setSafe( matInfo->detailInfoPConst, detailIdStrengthParallax );
}
}
bool TerrainCellMaterial::setupPass( const SceneRenderState *state,
const SceneData &sceneData )
{
PROFILE_SCOPE( TerrainCellMaterial_SetupPass );
if ( mCurrPass >= mPasses.size() )
{
mCurrPass = 0;
return false;
}
Pass &pass = mPasses[mCurrPass];
_updateMaterialConsts( &pass );
if ( pass.baseTexMapConst->isValid() )
GFX->setTexture( pass.baseTexMapConst->getSamplerRegister(), mTerrain->mBaseTex.getPointer() );
if ( pass.layerTexConst->isValid() )
GFX->setTexture( pass.layerTexConst->getSamplerRegister(), mTerrain->mLayerTex.getPointer() );
if ( pass.lightMapTexConst->isValid() )
GFX->setTexture( pass.lightMapTexConst->getSamplerRegister(), mTerrain->getLightMapTex() );
if ( sceneData.wireframe )
GFX->setStateBlock( pass.wireframeStateBlock );
else
GFX->setStateBlock( pass.stateBlock );
GFX->setShader( pass.shader );
GFX->setShaderConstBuffer( pass.consts );
// Let the light manager prepare any light stuff it needs.
LIGHTMGR->setLightInfo( NULL,
NULL,
sceneData,
state,
mCurrPass,
pass.consts );
for ( U32 i=0; i < pass.materials.size(); i++ )
{
MaterialInfo *matInfo = pass.materials[i];
if ( matInfo->detailTexConst->isValid() )
GFX->setTexture( matInfo->detailTexConst->getSamplerRegister(), matInfo->detailTex );
if ( matInfo->normalTexConst->isValid() )
GFX->setTexture( matInfo->normalTexConst->getSamplerRegister(), matInfo->normalTex );
}
pass.consts->setSafe( pass.layerSizeConst, (F32)mTerrain->mLayerTex.getWidth() );
if ( pass.oneOverTerrainSize->isValid() )
{
F32 oneOverTerrainSize = 1.0f / mTerrain->getWorldBlockSize();
pass.consts->set( pass.oneOverTerrainSize, oneOverTerrainSize );
}
pass.consts->setSafe( pass.squareSize, mTerrain->getSquareSize() );
if ( pass.fogDataConst->isValid() )
{
Point3F fogData;
fogData.x = sceneData.fogDensity;
fogData.y = sceneData.fogDensityOffset;
fogData.z = sceneData.fogHeightFalloff;
pass.consts->set( pass.fogDataConst, fogData );
}
pass.consts->setSafe( pass.fogColorConst, sceneData.fogColor );
if ( pass.lightInfoBufferConst->isValid() &&
pass.lightParamsConst->isValid() )
{
if ( !mLightInfoTarget )
mLightInfoTarget = NamedTexTarget::find( "lightinfo" );
GFXTextureObject *texObject = mLightInfoTarget->getTexture();
// TODO: Sometimes during reset of the light manager we get a
// NULL texture here. This is corrected on the next frame, but
// we should still investigate why that happens.
if ( texObject )
{
GFX->setTexture( pass.lightInfoBufferConst->getSamplerRegister(), texObject );
const Point3I &targetSz = texObject->getSize();
const RectI &targetVp = mLightInfoTarget->getViewport();
Point4F rtParams;
ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams);
pass.consts->setSafe( pass.lightParamsConst, rtParams );
}
}
++mCurrPass;
return true;
}
BaseMatInstance* TerrainCellMaterial::getShadowMat()
{
// Find our material which has some settings
// defined on it in script.
Material *mat = MATMGR->getMaterialDefinitionByName( "AL_DefaultShadowMaterial" );
// Create the material instance adding the feature which
// handles rendering terrain cut outs.
FeatureSet features = MATMGR->getDefaultFeatures();
BaseMatInstance *matInst = mat->createMatInstance();
if ( !matInst->init( features, getGFXVertexFormat<TerrVertex>() ) )
{
delete matInst;
matInst = NULL;
}
return matInst;
}

View file

@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRCELLMATERIAL_H_
#define _TERRCELLMATERIAL_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MATTEXTURETARGET_H_
#include "materials/matTextureTarget.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _GFXSHADER_H_
#include "gfx/gfxShader.h"
#endif
#ifndef _GFXSTATEBLOCK_H_
#include "gfx/gfxStateBlock.h"
#endif
class SceneRenderState;
struct SceneData;
class TerrainMaterial;
class TerrainBlock;
class BaseMatInstance;
/// This is a complex material which holds one or more
/// optimized shaders for rendering a single cell.
class TerrainCellMaterial
{
protected:
class MaterialInfo
{
public:
MaterialInfo()
{
}
~MaterialInfo()
{
}
TerrainMaterial *mat;
U32 layerId;
GFXShaderConstHandle *detailTexConst;
GFXTexHandle detailTex;
GFXShaderConstHandle *normalTexConst;
GFXTexHandle normalTex;
GFXShaderConstHandle *detailInfoVConst;
GFXShaderConstHandle *detailInfoPConst;
};
class Pass
{
public:
Pass()
: shader( NULL )
{
}
~Pass()
{
for ( U32 i=0; i < materials.size(); i++ )
delete materials[i];
}
Vector<MaterialInfo*> materials;
///
GFXShader *shader;
GFXShaderConstBufferRef consts;
GFXStateBlockRef stateBlock;
GFXStateBlockRef wireframeStateBlock;
GFXShaderConstHandle *modelViewProjConst;
GFXShaderConstHandle *worldViewOnly;
GFXShaderConstHandle *viewToObj;
GFXShaderConstHandle *eyePosWorldConst;
GFXShaderConstHandle *eyePosConst;
GFXShaderConstHandle *objTransConst;
GFXShaderConstHandle *worldToObjConst;
GFXShaderConstHandle *vEyeConst;
GFXShaderConstHandle *layerSizeConst;
GFXShaderConstHandle *lightParamsConst;
GFXShaderConstHandle *lightInfoBufferConst;
GFXShaderConstHandle *baseTexMapConst;
GFXShaderConstHandle *layerTexConst;
GFXShaderConstHandle *lightMapTexConst;
GFXShaderConstHandle *squareSize;
GFXShaderConstHandle *oneOverTerrainSize;
GFXShaderConstHandle *fogDataConst;
GFXShaderConstHandle *fogColorConst;
};
TerrainBlock *mTerrain;
U64 mMaterials;
Vector<Pass> mPasses;
U32 mCurrPass;
GFXTexHandle mBaseMapTexture;
GFXTexHandle mLayerMapTexture;
NamedTexTargetRef mLightInfoTarget;
/// The prepass material for this material.
TerrainCellMaterial *mPrePassMat;
/// The reflection material for this material.
TerrainCellMaterial *mReflectMat;
/// A vector of all terrain cell materials loaded in the system.
static Vector<TerrainCellMaterial*> smAllMaterials;
bool _createPass( Vector<MaterialInfo*> *materials,
Pass *pass,
bool firstPass,
bool prePassMat,
bool reflectMat,
bool baseOnly );
void _updateMaterialConsts( Pass *pass );
public:
TerrainCellMaterial();
~TerrainCellMaterial();
void init( TerrainBlock *block,
U64 activeMaterials,
bool prePassMat = false,
bool reflectMat = false,
bool baseOnly = false );
/// Returns a prepass material from this material.
TerrainCellMaterial* getPrePassMat();
/// Returns the reflection material from this material.
TerrainCellMaterial* getReflectMat();
void setTransformAndEye( const MatrixF &modelXfm,
const MatrixF &viewXfm,
const MatrixF &projectXfm,
F32 farPlane );
///
bool setupPass( const SceneRenderState *state,
const SceneData &sceneData );
///
static BaseMatInstance* getShadowMat();
///
static void _updateDefaultAnisotropy();
};
#endif // _TERRCELLMATERIAL_H_

View file

@ -0,0 +1,983 @@
//-----------------------------------------------------------------------------
// 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/terrCollision.h"
#include "terrain/terrData.h"
#include "collision/abstractPolyList.h"
#include "collision/collision.h"
const F32 TerrainThickness = 0.5f;
static const U32 MaxExtent = 256;
#define MAX_FLOAT 1e20f
//----------------------------------------------------------------------------
Convex sTerrainConvexList;
// Number of vertices followed by point index
S32 sVertexList[5][5] = {
{ 3, 1,2,3 }, // 135 B
{ 3, 0,1,3 }, // 135 A
{ 3, 0,2,3 }, // 45 B
{ 3, 0,1,2 }, // 45 A
{ 4, 0,1,2,3 } // Convex square
};
// Number of edges followed by edge index pairs
S32 sEdgeList45[16][11] = {
{ 0 }, //
{ 0 },
{ 0 },
{ 1, 0,1 }, // 0-1
{ 0 },
{ 1, 0,1 }, // 0-2
{ 1, 0,1 }, // 1-2
{ 3, 0,1,1,2,2,0 }, // 0-1,1-2,2-0
{ 0 },
{ 0,}, //
{ 0 },
{ 1, 0,1 }, // 0-1,
{ 0, }, //
{ 1, 0,1 }, // 0-2,
{ 1, 0,1 }, // 1-2
{ 3, 0,1,1,2,0,2 },
};
S32 sEdgeList135[16][11] = {
{ 0 },
{ 0 },
{ 0 },
{ 1, 0,1 }, // 0-1
{ 0 },
{ 0 },
{ 1, 0,1 }, // 1-2
{ 2, 0,1,1,2 }, // 0-1,1-2
{ 0 },
{ 0, }, //
{ 1, 0,1 }, // 1-3
{ 2, 0,1,1,2 }, // 0-1,1-3,
{ 0 }, //
{ 0 }, //
{ 2, 0,1,2,0 }, // 1-2,3-1
{ 3, 0,1,1,2,1,3 },
};
// On split squares, the FaceA diagnal is also removed
S32 sEdgeList45A[16][11] = {
{ 0 }, //
{ 0 },
{ 0 },
{ 1, 0,1 }, // 0-1
{ 0 },
{ 0 }, //
{ 1, 0,1 }, // 1-2
{ 2, 0,1,1,2 }, // 0-1,1-2
{ 0 },
{ 0,}, //
{ 0 },
{ 1, 0,1 }, // 0-1
{ 0, }, //
{ 0, 0,1 }, //
{ 1, 0,1 }, // 1-2
{ 3, 0,1,1,2 },
};
S32 sEdgeList135A[16][11] = {
{ 0 },
{ 0 },
{ 0 },
{ 1, 0,1 }, // 0-1
{ 0 },
{ 0 },
{ 1, 0,1 }, // 1-2
{ 2, 0,1,1,2 }, // 0-1,1-2
{ 0 },
{ 0 }, //
{ 0 }, //
{ 1, 0,1 }, // 0-1
{ 0 }, //
{ 0 }, //
{ 1, 0,1 }, // 1-2
{ 3, 0,1,1,2 },
};
// Number of faces followed by normal index and vertices
S32 sFaceList45[16][9] = {
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 1, 0,0,1,2 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 1, 1,0,1,2 },
{ 0 },
{ 2, 0,0,1,2, 1,0,2,3 },
};
S32 sFaceList135[16][9] = {
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 0 },
{ 1, 0,0,1,2 },
{ 0 },
{ 0 },
{ 1, 1,0,1,2 },
{ 2, 0,0,1,3, 1,1,2,3 },
};
TerrainConvex::TerrainConvex()
{
mType = TerrainConvexType;
}
TerrainConvex::TerrainConvex( const TerrainConvex &cv )
{
mType = TerrainConvexType;
// Only a partial copy...
mObject = cv.mObject;
split45 = cv.split45;
squareId = cv.squareId;
material = cv.material;
point[0] = cv.point[0];
point[1] = cv.point[1];
point[2] = cv.point[2];
point[3] = cv.point[3];
normal[0] = cv.normal[0];
normal[1] = cv.normal[1];
box = cv.box;
}
Box3F TerrainConvex::getBoundingBox() const
{
return box;
}
Box3F TerrainConvex::getBoundingBox(const MatrixF&, const Point3F& ) const
{
// Function should not be called....
return box;
}
Point3F TerrainConvex::support(const VectorF& v) const
{
S32 *vp;
if (halfA)
vp = square ? sVertexList[(split45 << 1) | 1]: sVertexList[4];
else
vp = square ? sVertexList[(split45 << 1)] : sVertexList[4];
S32 *ve = vp + vp[0] + 1;
const Point3F *bp = &point[vp[1]];
F32 bd = mDot(*bp,v);
for (vp += 2; vp < ve; vp++) {
const Point3F* cp = &point[*vp];
F32 dd = mDot(*cp,v);
if (dd > bd) {
bd = dd;
bp = cp;
}
}
return *bp;
}
inline bool isOnPlane(Point3F& p,PlaneF& plane)
{
F32 dist = mDot(plane,p) + plane.d;
return dist < 0.1 && dist > -0.1;
}
void TerrainConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf)
{
U32 i;
cf->material = 0;
cf->object = mObject;
// Plane is normal n + support point
PlaneF plane;
plane.set(support(n),n);
S32 vertexCount = cf->mVertexList.size();
// Emit vertices on the plane
S32* vertexListPointer;
if (halfA)
vertexListPointer = square ? sVertexList[(split45 << 1) | 1]: sVertexList[4];
else
vertexListPointer = square ? sVertexList[(split45 << 1)] : sVertexList[4];
S32 pm = 0;
S32 numVerts = *vertexListPointer;
vertexListPointer += 1;
for (i = 0; i < numVerts; i++)
{
Point3F& cp = point[vertexListPointer[i]];
cf->mVertexList.increment();
mat.mulP(cp,&cf->mVertexList.last());
pm |= 1 << vertexListPointer[i];
}
// Emit Edges
S32* ep = (square && halfA)?
(split45 ? sEdgeList45A[pm]: sEdgeList135A[pm]):
(split45 ? sEdgeList45[pm]: sEdgeList135[pm]);
S32 numEdges = *ep;
S32 edgeListStart = cf->mEdgeList.size();
cf->mEdgeList.increment(numEdges);
ep += 1;
for (i = 0; i < numEdges; i++)
{
cf->mEdgeList[edgeListStart + i].vertex[0] = vertexCount + ep[i * 2 + 0];
cf->mEdgeList[edgeListStart + i].vertex[1] = vertexCount + ep[i * 2 + 1];
}
// Emit faces
S32* fp = split45 ? sFaceList45[pm]: sFaceList135[pm];
S32 numFaces = *fp;
fp += 1;
S32 faceListStart = cf->mFaceList.size();
cf->mFaceList.increment(numFaces);
for (i = 0; i < numFaces; i++)
{
cf->mFaceList[faceListStart + i].normal = normal[fp[i * 4 + 0]];
cf->mFaceList[faceListStart + i].vertex[0] = vertexCount + fp[i * 4 + 1];
cf->mFaceList[faceListStart + i].vertex[1] = vertexCount + fp[i * 4 + 2];
cf->mFaceList[faceListStart + i].vertex[2] = vertexCount + fp[i * 4 + 3];
}
}
void TerrainConvex::getPolyList(AbstractPolyList* list)
{
list->setTransform(&mObject->getTransform(), mObject->getScale());
list->setObject(mObject);
// Emit vertices
U32 array[4];
U32 curr = 0;
S32 numVerts;
S32* vertsStart;
if (halfA)
{
numVerts = square ? sVertexList[(split45 << 1) | 1][0] : sVertexList[4][0];
vertsStart = square ? &sVertexList[(split45 << 1) | 1][1] : &sVertexList[4][1];
}
else
{
numVerts = square ? sVertexList[(split45 << 1)][0] : sVertexList[4][0];
vertsStart = square ? &sVertexList[(split45 << 1)][1] : &sVertexList[4][1];
}
S32 pointMask = 0;
for (U32 i = 0; i < numVerts; i++) {
const Point3F& cp = point[vertsStart[i]];
array[curr++] = list->addPoint(cp);
pointMask |= (1 << vertsStart[i]);
}
S32 numFaces = split45 ? sFaceList45[pointMask][0] : sFaceList135[pointMask][0];
S32* faceStart = split45 ? &sFaceList45[pointMask][1] : &sFaceList135[pointMask][1];
for (U32 j = 0; j < numFaces; j++) {
S32 plane = faceStart[0];
S32 v0 = faceStart[1];
S32 v1 = faceStart[2];
S32 v2 = faceStart[3];
list->begin(0, plane);
list->vertex(array[v0]);
list->vertex(array[v1]);
list->vertex(array[v2]);
list->plane(array[v0], array[v1], array[v2]);
list->end();
faceStart += 4;
}
}
//----------------------------------------------------------------------------
void TerrainBlock::buildConvex(const Box3F& box,Convex* convex)
{
PROFILE_SCOPE( TerrainBlock_buildConvex );
sTerrainConvexList.collectGarbage();
// First check to see if the query misses the
// terrain elevation range.
const Point3F &terrainPos = getPosition();
if ( box.maxExtents.z - terrainPos.z < -TerrainThickness ||
box.minExtents.z - terrainPos.z > fixedToFloat( mFile->getMaxHeight() ) )
return;
// Transform the bounding sphere into the object's coord space. Note that this
// not really optimal.
Box3F osBox = box;
mWorldToObj.mul(osBox);
AssertWarn(mObjScale == Point3F(1, 1, 1), "Error, handle the scale transform on the terrain");
S32 xStart = (S32)mFloor( osBox.minExtents.x / mSquareSize );
S32 xEnd = (S32)mCeil ( osBox.maxExtents.x / mSquareSize );
S32 yStart = (S32)mFloor( osBox.minExtents.y / mSquareSize );
S32 yEnd = (S32)mCeil ( osBox.maxExtents.y / mSquareSize );
S32 xExt = xEnd - xStart;
if (xExt > MaxExtent)
xExt = MaxExtent;
U16 heightMax = floatToFixed(osBox.maxExtents.z);
U16 heightMin = (osBox.minExtents.z < 0)? 0: floatToFixed(osBox.minExtents.z);
const U32 BlockMask = mFile->mSize - 1;
for ( S32 y = yStart; y < yEnd; y++ )
{
S32 yi = y & BlockMask;
//
for ( S32 x = xStart; x < xEnd; x++ )
{
S32 xi = x & BlockMask;
const TerrainSquare *sq = mFile->findSquare( 0, xi, yi );
if ( x != xi || y != yi )
continue;
// holes only in the primary terrain block
if ( ( ( sq->flags & TerrainSquare::Empty ) && x == xi && y == yi ) ||
sq->minHeight > heightMax ||
sq->maxHeight < heightMin )
continue;
U32 sid = (x << 16) + (y & ((1 << 16) - 1));
Convex *cc = 0;
// See if the square already exists as part of the working set.
CollisionWorkingList& wl = convex->getWorkingList();
for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
if (itr->mConvex->getType() == TerrainConvexType &&
static_cast<TerrainConvex*>(itr->mConvex)->squareId == sid) {
cc = itr->mConvex;
break;
}
if (cc)
continue;
// Create a new convex.
TerrainConvex* cp = new TerrainConvex;
sTerrainConvexList.registerObject(cp);
convex->addToWorkingList(cp);
cp->halfA = true;
cp->square = 0;
cp->mObject = this;
cp->squareId = sid;
cp->material = mFile->getLayerIndex( xi, yi );
cp->box.minExtents.set((F32)(x * mSquareSize), (F32)(y * mSquareSize), fixedToFloat( sq->minHeight ));
cp->box.maxExtents.x = cp->box.minExtents.x + mSquareSize;
cp->box.maxExtents.y = cp->box.minExtents.y + mSquareSize;
cp->box.maxExtents.z = fixedToFloat( sq->maxHeight );
mObjToWorld.mul(cp->box);
// Build points
Point3F* pos = cp->point;
for (int i = 0; i < 4 ; i++,pos++) {
S32 dx = i >> 1;
S32 dy = dx ^ (i & 1);
pos->x = (F32)((x + dx) * mSquareSize);
pos->y = (F32)((y + dy) * mSquareSize);
pos->z = fixedToFloat( mFile->getHeight(xi + dx, yi + dy) );
}
// Build normals, then split into two Convex objects if the
// square is concave
if ((cp->split45 = sq->flags & TerrainSquare::Split45) == true) {
VectorF *vp = cp->point;
mCross(vp[0] - vp[1],vp[2] - vp[1],&cp->normal[0]);
cp->normal[0].normalize();
mCross(vp[2] - vp[3],vp[0] - vp[3],&cp->normal[1]);
cp->normal[1].normalize();
if (mDot(vp[3] - vp[1],cp->normal[0]) > 0) {
TerrainConvex* nc = new TerrainConvex(*cp);
sTerrainConvexList.registerObject(nc);
convex->addToWorkingList(nc);
nc->halfA = false;
nc->square = cp;
cp->square = nc;
}
}
else {
VectorF *vp = cp->point;
mCross(vp[3] - vp[0],vp[1] - vp[0],&cp->normal[0]);
cp->normal[0].normalize();
mCross(vp[1] - vp[2],vp[3] - vp[2],&cp->normal[1]);
cp->normal[1].normalize();
if (mDot(vp[2] - vp[0],cp->normal[0]) > 0) {
TerrainConvex* nc = new TerrainConvex(*cp);
sTerrainConvexList.registerObject(nc);
convex->addToWorkingList(nc);
nc->halfA = false;
nc->square = cp;
cp->square = nc;
}
}
}
}
}
static inline void swap(U32*& a,U32*& b)
{
U32* t = b;
b = a;
a = t;
}
static void clrbuf(U32* p, U32 s)
{
U32* e = p + s;
while (p != e)
*p++ = U32_MAX;
}
bool TerrainBlock::buildPolyList(PolyListContext, AbstractPolyList* polyList, const Box3F &box, const SphereF&)
{
PROFILE_SCOPE( TerrainBlock_buildPolyList );
// First check to see if the query misses the
// terrain elevation range.
const Point3F &terrainPos = getPosition();
if ( box.maxExtents.z - terrainPos.z < -TerrainThickness ||
box.minExtents.z - terrainPos.z > fixedToFloat( mFile->getMaxHeight() ) )
return false;
// Transform the bounding sphere into the object's coord
// space. Note that this is really optimal.
Box3F osBox = box;
mWorldToObj.mul(osBox);
AssertWarn(mObjScale == Point3F::One, "Error, handle the scale transform on the terrain");
// Setup collision state data
polyList->setTransform(&getTransform(), getScale());
polyList->setObject(this);
S32 xStart = (S32)mFloor( osBox.minExtents.x / mSquareSize );
S32 xEnd = (S32)mCeil ( osBox.maxExtents.x / mSquareSize );
S32 yStart = (S32)mFloor( osBox.minExtents.y / mSquareSize );
S32 yEnd = (S32)mCeil ( osBox.maxExtents.y / mSquareSize );
if ( xStart < 0 )
xStart = 0;
S32 xExt = xEnd - xStart;
if ( xExt > MaxExtent )
xExt = MaxExtent;
xEnd = xStart + xExt;
U32 heightMax = floatToFixed(osBox.maxExtents.z);
U32 heightMin = (osBox.minExtents.z < 0.0f)? 0.0f: floatToFixed(osBox.minExtents.z);
// Index of shared points
U32 bp[(MaxExtent + 1) * 2],*vb[2];
vb[0] = &bp[0];
vb[1] = &bp[xExt + 1];
clrbuf(vb[1],xExt + 1);
const U32 BlockMask = mFile->mSize - 1;
bool emitted = false;
for (S32 y = yStart; y < yEnd; y++)
{
S32 yi = y & BlockMask;
swap(vb[0],vb[1]);
clrbuf(vb[1],xExt + 1);
//
for (S32 x = xStart; x < xEnd; x++)
{
S32 xi = x & BlockMask;
const TerrainSquare *sq = mFile->findSquare( 0, xi, yi );
if ( x != xi || y != yi )
continue;
// holes only in the primary terrain block
if ( ( ( sq->flags & TerrainSquare::Empty ) && x == xi && y == yi ) ||
sq->minHeight > heightMax ||
sq->maxHeight < heightMin )
continue;
emitted = true;
// Add the missing points
U32 vi[5];
for (int i = 0; i < 4 ; i++)
{
S32 dx = i >> 1;
S32 dy = dx ^ (i & 1);
U32* vp = &vb[dy][x - xStart + dx];
if (*vp == U32_MAX)
{
Point3F pos;
pos.x = (F32)((x + dx) * mSquareSize);
pos.y = (F32)((y + dy) * mSquareSize);
pos.z = fixedToFloat( mFile->getHeight(xi + dx, yi + dy) );
*vp = polyList->addPoint(pos);
}
vi[i] = *vp;
}
U32* vp = &vi[0];
if ( !( sq->flags & TerrainSquare::Split45 ) )
vi[4] = vi[0], vp++;
BaseMatInstance *material = NULL; //getMaterialInst( xi, yi );
U32 surfaceKey = ((xi << 16) + yi) << 1;
polyList->begin(material,surfaceKey);
polyList->vertex(vp[0]);
polyList->vertex(vp[1]);
polyList->vertex(vp[2]);
polyList->plane(vp[0],vp[1],vp[2]);
polyList->end();
polyList->begin(material,surfaceKey + 1);
polyList->vertex(vp[0]);
polyList->vertex(vp[2]);
polyList->vertex(vp[3]);
polyList->plane(vp[0],vp[2],vp[3]);
polyList->end();
}
}
return emitted;
}
//----------------------------------------------------------------------------
static F32 calcInterceptV(F32 vStart, F32 invDeltaV, F32 intercept)
{
return (intercept - vStart) * invDeltaV;
}
static F32 calcInterceptNone(F32, F32, F32)
{
return MAX_FLOAT;
}
static F32 (*calcInterceptX)(F32, F32, F32);
static F32 (*calcInterceptY)(F32, F32, F32);
static U32 lineCount;
static Point3F lineStart, lineEnd;
bool TerrainBlock::castRay(const Point3F &start, const Point3F &end, RayInfo *info)
{
PROFILE_SCOPE( TerrainBlock_castRay );
if ( !castRayI(start, end, info, false) )
return false;
// Set intersection point.
info->setContactPoint( start, end );
// Set material at contact point.
Point2I gridPos = getGridPos( info->point );
U8 layer = mFile->getLayerIndex( gridPos.x, gridPos.y );
info->material = mFile->getMaterialMapping( layer );
return true;
}
bool TerrainBlock::castRayI(const Point3F &start, const Point3F &end, RayInfo *info, bool collideEmpty)
{
lineCount = 0;
lineStart = start;
lineEnd = end;
info->object = this;
if(start.x == end.x && start.y == end.y)
{
if (end.z == start.z)
return false;
F32 height;
if(!getNormalAndHeight(Point2F(start.x, start.y), &info->normal, &height, true))
return false;
F32 t = (height - start.z) / (end.z - start.z);
if(t < 0 || t > 1)
return false;
info->t = t;
return true;
}
F32 invBlockWorldSize = 1 / getWorldBlockSize();
Point3F pStart(start.x * invBlockWorldSize, start.y * invBlockWorldSize, start.z);
Point3F pEnd(end.x * invBlockWorldSize, end.y * invBlockWorldSize, end.z);
int blockX = (S32)mFloor(pStart.x);
int blockY = (S32)mFloor(pStart.y);
int dx, dy;
F32 invDeltaX;
if(pEnd.x == pStart.x)
{
calcInterceptX = calcInterceptNone;
invDeltaX = 0;
dx = 0;
}
else
{
invDeltaX = 1 / (pEnd.x - pStart.x);
calcInterceptX = calcInterceptV;
if(pEnd.x < pStart.x)
dx = -1;
else
dx = 1;
}
F32 invDeltaY;
if(pEnd.y == pStart.y)
{
calcInterceptY = calcInterceptNone;
invDeltaY = 0;
dy = 0;
}
else
{
invDeltaY = 1 / (pEnd.y - pStart.y);
calcInterceptY = calcInterceptV;
if(pEnd.y < pStart.y)
dy = -1;
else
dy = 1;
}
const U32 BlockSquareWidth = mFile->mSize;
const U32 GridLevels = mFile->mGridLevels;
F32 startT = 0;
for(;;)
{
F32 nextXInt = calcInterceptX(pStart.x, invDeltaX, (F32)(blockX + (dx == 1)));
F32 nextYInt = calcInterceptY(pStart.y, invDeltaY, (F32)(blockY + (dy == 1)));
F32 intersectT = 1;
if(nextXInt < intersectT)
intersectT = nextXInt;
if(nextYInt < intersectT)
intersectT = nextYInt;
if ( castRayBlock( pStart,
pEnd,
Point2I( blockX * BlockSquareWidth,
blockY * BlockSquareWidth ),
GridLevels,
invDeltaX,
invDeltaY,
startT,
intersectT,
info,
collideEmpty ) )
{
info->normal.z *= BlockSquareWidth * mSquareSize;
info->normal.normalize();
return true;
}
startT = intersectT;
if(intersectT >= 1)
break;
if(nextXInt < nextYInt)
blockX += dx;
else if(nextYInt < nextXInt)
blockY += dy;
else
{
blockX += dx;
blockY += dy;
}
}
return false;
}
struct TerrLOSStackNode
{
F32 startT;
F32 endT;
Point2I blockPos;
U32 level;
};
bool TerrainBlock::castRayBlock( const Point3F &pStart,
const Point3F &pEnd,
const Point2I &aBlockPos,
U32 aLevel,
F32 invDeltaX,
F32 invDeltaY,
F32 aStartT,
F32 aEndT,
RayInfo *info,
bool collideEmpty )
{
const U32 BlockSquareWidth = mFile->mSize;
const U32 GridLevels = mFile->mGridLevels;
const U32 BlockMask = mFile->mSize - 1;
F32 invBlockSize = 1 / F32( BlockSquareWidth );
static Vector<TerrLOSStackNode> stack;
stack.setSize( GridLevels * 3 + 1 );
U32 stackSize = 1;
stack[0].startT = aStartT;
stack[0].endT = aEndT;
stack[0].blockPos = aBlockPos;
stack[0].level = aLevel;
if( !aBlockPos.isZero() )
return false;
while(stackSize--)
{
TerrLOSStackNode *sn = stack.address() + stackSize;
U32 level = sn->level;
F32 startT = sn->startT;
F32 endT = sn->endT;
Point2I blockPos = sn->blockPos;
const TerrainSquare *sq = mFile->findSquare( level, blockPos.x, blockPos.y );
F32 startZ = startT * (pEnd.z - pStart.z) + pStart.z;
F32 endZ = endT * (pEnd.z - pStart.z) + pStart.z;
F32 minHeight = fixedToFloat(sq->minHeight);
if(startZ <= minHeight && endZ <= minHeight)
continue;
F32 maxHeight = fixedToFloat(sq->maxHeight);
if(startZ >= maxHeight && endZ >= maxHeight)
continue;
if ( !collideEmpty && ( sq->flags & TerrainSquare::Empty ) &&
blockPos.x == ( blockPos.x & BlockMask ) && blockPos.y == ( blockPos.y & BlockMask ))
continue;
if(level == 0)
{
F32 xs = blockPos.x * invBlockSize;
F32 ys = blockPos.y * invBlockSize;
F32 zBottomLeft = fixedToFloat( mFile->getHeight(blockPos.x, blockPos.y) );
F32 zBottomRight= fixedToFloat( mFile->getHeight(blockPos.x + 1, blockPos.y) );
F32 zTopLeft = fixedToFloat( mFile->getHeight(blockPos.x, blockPos.y + 1) );
F32 zTopRight = fixedToFloat( mFile->getHeight(blockPos.x + 1, blockPos.y + 1) );
PlaneF p1, p2;
PlaneF divider;
Point3F planePoint;
if(sq->flags & TerrainSquare::Split45)
{
p1.set(zBottomLeft - zBottomRight, zBottomRight - zTopRight, invBlockSize);
p2.set(zTopLeft - zTopRight, zBottomLeft - zTopLeft, invBlockSize);
planePoint.set(xs, ys, zBottomLeft);
divider.x = 1;
divider.y = -1;
divider.z = 0;
}
else
{
p1.set(zTopLeft - zTopRight, zBottomRight - zTopRight, invBlockSize);
p2.set(zBottomLeft - zBottomRight, zBottomLeft - zTopLeft, invBlockSize);
planePoint.set(xs + invBlockSize, ys, zBottomRight);
divider.x = 1;
divider.y = 1;
divider.z = 0;
}
p1.setPoint(planePoint);
p2.setPoint(planePoint);
divider.setPoint(planePoint);
F32 t1 = p1.intersect(pStart, pEnd);
F32 t2 = p2.intersect(pStart, pEnd);
F32 td = divider.intersect(pStart, pEnd);
F32 dStart = divider.distToPlane(pStart);
F32 dEnd = divider.distToPlane(pEnd);
// see if the line crosses the divider
if((dStart >= 0 && dEnd < 0) || (dStart < 0 && dEnd >= 0))
{
if(dStart < 0)
{
F32 temp = t1;
t1 = t2;
t2 = temp;
}
if(t1 >= startT && t1 && t1 <= td && t1 <= endT)
{
info->t = t1;
info->normal = p1;
return true;
}
if(t2 >= td && t2 >= startT && t2 <= endT)
{
info->t = t2;
info->normal = p2;
return true;
}
}
else
{
F32 t;
if(dStart >= 0) {
t = t1;
info->normal = p1;
}
else {
t = t2;
info->normal = p2;
}
if(t >= startT && t <= endT)
{
info->t = t;
return true;
}
}
continue;
}
int subSqWidth = 1 << (level - 1);
F32 xIntercept = (blockPos.x + subSqWidth) * invBlockSize;
F32 xInt = calcInterceptX(pStart.x, invDeltaX, xIntercept);
F32 yIntercept = (blockPos.y + subSqWidth) * invBlockSize;
F32 yInt = calcInterceptY(pStart.y, invDeltaY, yIntercept);
F32 startX = startT * (pEnd.x - pStart.x) + pStart.x;
F32 startY = startT * (pEnd.y - pStart.y) + pStart.y;
if(xInt < startT)
xInt = MAX_FLOAT;
if(yInt < startT)
yInt = MAX_FLOAT;
U32 x0 = (startX > xIntercept) * subSqWidth;
U32 y0 = (startY > yIntercept) * subSqWidth;
U32 x1 = subSqWidth - x0;
U32 y1 = subSqWidth - y0;
U32 nextLevel = level - 1;
// push the items on the stack in reverse order of processing
if(xInt > endT && yInt > endT)
{
// only test the square the point started in:
stack[stackSize].blockPos.set(blockPos.x + x0, blockPos.y + y0);
stack[stackSize].level = nextLevel;
stackSize++;
}
else if(xInt < yInt)
{
F32 nextIntersect = endT;
if(yInt <= endT)
{
stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1);
stack[stackSize].startT = yInt;
stack[stackSize].endT = endT;
stack[stackSize].level = nextLevel;
nextIntersect = yInt;
stackSize++;
}
stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y0);
stack[stackSize].startT = xInt;
stack[stackSize].endT = nextIntersect;
stack[stackSize].level = nextLevel;
stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0);
stack[stackSize+1].startT = startT;
stack[stackSize+1].endT = xInt;
stack[stackSize+1].level = nextLevel;
stackSize += 2;
}
else if(yInt < xInt)
{
F32 nextIntersect = endT;
if(xInt <= endT)
{
stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1);
stack[stackSize].startT = xInt;
stack[stackSize].endT = endT;
stack[stackSize].level = nextLevel;
nextIntersect = xInt;
stackSize++;
}
stack[stackSize].blockPos.set(blockPos.x + x0, blockPos.y + y1);
stack[stackSize].startT = yInt;
stack[stackSize].endT = nextIntersect;
stack[stackSize].level = nextLevel;
stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0);
stack[stackSize+1].startT = startT;
stack[stackSize+1].endT = yInt;
stack[stackSize+1].level = nextLevel;
stackSize += 2;
}
else
{
stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1);
stack[stackSize].startT = xInt;
stack[stackSize].endT = endT;
stack[stackSize].level = nextLevel;
stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0);
stack[stackSize+1].startT = startT;
stack[stackSize+1].endT = xInt;
stack[stackSize+1].level = nextLevel;
stackSize += 2;
}
}
return false;
}

View file

@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRCOLL_H_
#define _TERRCOLL_H_
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
class TerrainConvex : public Convex
{
friend class TerrainBlock;
TerrainConvex *square; ///< Alternate convex if square is concave
bool halfA; ///< Which half of square
bool split45; ///< Square split pattern
U32 squareId; ///< Used to match squares
U32 material;
Point3F point[4]; ///< 3-4 vertices
VectorF normal[2];
Box3F box; ///< Bounding box
public:
TerrainConvex();
TerrainConvex( const TerrainConvex& cv );
// Convex
Box3F getBoundingBox() const;
Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const;
Point3F support(const VectorF& v) const;
void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
void getPolyList(AbstractPolyList* list);
};
#endif // _TERRCOLL_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRDATA_H_
#define _TERRDATA_H_
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _TERRFILE_H_
#include "terrain/terrFile.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
class GBitmap;
class TerrainBlock;
class TerrCell;
class PhysicsBody;
class TerrainCellMaterial;
class TerrainBlock : public SceneObject
{
typedef SceneObject Parent;
friend class TerrainEditor;
friend class TerrainCellMaterial;
protected:
enum
{
TransformMask = Parent::NextFreeMask,
FileMask = Parent::NextFreeMask << 1,
SizeMask = Parent::NextFreeMask << 2,
MaterialMask = Parent::NextFreeMask << 3,
HeightMapChangeMask = Parent::NextFreeMask << 4,
MiscMask = Parent::NextFreeMask << 5,
NextFreeMask = Parent::NextFreeMask << 6,
};
Box3F mBounds;
///
GBitmap *mLightMap;
/// The lightmap dimensions in pixels.
U32 mLightMapSize;
/// The lightmap texture.
GFXTexHandle mLightMapTex;
/// The terrain data file.
Resource<TerrainFile> mFile;
/// The TerrainFile CRC sent from the server.
U32 mCRC;
///
FileName mTerrFileName;
/// The maximum detail distance found in the material list.
F32 mMaxDetailDistance;
///
Vector<GFXTexHandle> mBaseTextures;
///
GFXTexHandle mLayerTex;
/// The shader used to generate the base texture map.
GFXShaderRef mBaseShader;
///
GFXStateBlockRef mBaseShaderSB;
///
GFXShaderConstBufferRef mBaseShaderConsts;
///
GFXShaderConstHandle *mBaseTexScaleConst;
GFXShaderConstHandle *mBaseTexIdConst;
GFXShaderConstHandle *mBaseLayerSizeConst;
///
GFXTextureTargetRef mBaseTarget;
/// The base texture.
GFXTexHandle mBaseTex;
///
bool mDetailsDirty;
///
bool mLayerTexDirty;
/// The desired size for the base texture.
U32 mBaseTexSize;
///
TerrCell *mCell;
/// The shared base material which is used to render
/// cells that are outside the detail map range.
TerrainCellMaterial *mBaseMaterial;
/// A dummy material only used for shadow
/// material generation.
BaseMatInstance *mDefaultMatInst;
F32 mSquareSize;
PhysicsBody *mPhysicsRep;
U32 mScreenError;
/// The shared primitive buffer used in rendering.
GFXPrimitiveBufferHandle mPrimBuffer;
/// The cells used in the last render pass
/// when doing debug rendering.
/// @see _renderDebug
Vector<TerrCell*> mDebugCells;
/// Set to enable debug rendering of the terrain. It
/// is exposed to the console via $terrain::debugRender.
static bool smDebugRender;
/// Allows the terrain to cast shadows onto itself and other objects.
bool mCastShadows;
/// A global LOD scale used to tweak the default
/// terrain screen error value.
static F32 smLODScale;
/// A global detail scale used to tweak the
/// material detail distances.
static F32 smDetailScale;
/// True if the zoning needs to be recalculated for the terrain.
bool mZoningDirty;
String _getBaseTexCacheFileName() const;
void _rebuildQuadtree();
void _updatePhysics();
void _renderBlock( SceneRenderState *state );
void _renderDebug( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
/// The callback used to get texture events.
/// @see GFXTextureManager::addEventDelegate
void _onTextureEvent( GFXTexCallbackCode code );
/// Used to release terrain materials when
/// the material manager flushes them.
/// @see MaterialManager::getFlushSignal
void _onFlushMaterials();
///
bool _initBaseShader();
///
void _updateMaterials();
///
void _updateBaseTexture( bool writeToCache );
void _updateLayerTexture();
void _updateBounds();
void _onZoningChanged( SceneZoneSpaceManager *zoneManager );
void _updateZoning();
// Protected fields
static bool _setTerrainFile( void *obj, const char *index, const char *data );
static bool _setSquareSize( void *obj, const char *index, const char *data );
static bool _setBaseTexSize( void *obj, const char *index, const char *data );
static bool _setLightMapSize( void *obj, const char *index, const char *data );
public:
enum
{
LightmapUpdate = BIT(0),
HeightmapUpdate = BIT(1),
LayersUpdate = BIT(2),
EmptyUpdate = BIT(3)
};
static Signal<void(U32,TerrainBlock*,const Point2I& ,const Point2I&)> smUpdateSignal;
///
bool import( const GBitmap &heightMap,
F32 heightScale,
F32 metersPerPixel,
const Vector<U8> &layerMap,
const Vector<String> &materials );
#ifdef TORQUE_TOOLS
bool exportHeightMap( const UTF8 *filePath, const String &format ) const;
bool exportLayerMaps( const UTF8 *filePrefix, const String &format ) const;
#endif
public:
TerrainBlock();
virtual ~TerrainBlock();
U32 getCRC() const { return(mCRC); }
Resource<TerrainFile> getFile() const { return mFile; };
bool onAdd();
void onRemove();
void onEditorEnable();
void onEditorDisable();
/// Adds a new material as the top layer or
/// inserts it at the specified index.
void addMaterial( const String &name, U32 insertAt = -1 );
/// Removes the material at the index.
void removeMaterial( U32 index );
/// Updates the material at the index.
void updateMaterial( U32 index, const String &name );
/// Deletes all the materials on the terrain.
void deleteAllMaterials();
//void setMaterialName( U32 index, const String &name );
/// Accessors and mutators for TerrainMaterialUndoAction.
/// @{
const Vector<TerrainMaterial*>& getMaterials() const { return mFile->mMaterials; }
const Vector<U8>& getLayerMap() const { return mFile->mLayerMap; }
void setMaterials( const Vector<TerrainMaterial*> &materials ) { mFile->mMaterials = materials; }
void setLayerMap( const Vector<U8> &layers ) { mFile->mLayerMap = layers; }
/// @}
TerrainMaterial* getMaterial( U32 index ) const;
const char* getMaterialName( U32 index ) const;
U32 getMaterialCount() const;
//BaseMatInstance* getMaterialInst( U32 x, U32 y );
void setHeight( const Point2I &pos, F32 height );
F32 getHeight( const Point2I &pos );
// Performs an update to the selected range of the terrain
// grid including the collision and rendering structures.
void updateGrid( const Point2I &minPt,
const Point2I &maxPt,
bool updateClient = false );
void updateGridMaterials( const Point2I &minPt, const Point2I &maxPt );
Point2I getGridPos( const Point3F &worldPos ) const;
/// This returns true and the terrain z height for
/// a 2d position in the terrains object space.
///
/// If the terrain at that point is within an empty block
/// or the 2d position is outside of the terrain area then
/// it returns false.
///
bool getHeight( const Point2F &pos, F32 *height ) const;
void getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const;
/// This returns true and the terrain normal for a
/// 2d position in the terrains object space.
///
/// If the terrain at that point is within an empty block
/// or the 2d position is outside of the terrain area then
/// it returns false.
///
bool getNormal( const Point2F &pos,
Point3F *normal,
bool normalize = true,
bool skipEmpty = true ) const;
/// This returns true and the smoothed terrain normal
// for a 2d position in the terrains object space.
///
/// If the terrain at that point is within an empty block
/// or the 2d position is outside of the terrain area then
/// it returns false.
///
bool getSmoothNormal( const Point2F &pos,
Point3F *normal,
bool normalize = true,
bool skipEmpty = true ) const;
/// This returns true and the terrain normal and z height
/// for a 2d position in the terrains object space.
///
/// If the terrain at that point is within an empty block
/// or the 2d position is outside of the terrain area then
/// it returns false.
///
bool getNormalAndHeight( const Point2F &pos,
Point3F *normal,
F32 *height,
bool normalize = true ) const;
/// This returns true and the terrain normal, z height, and
/// material name for a 2d position in the terrains object
/// space.
///
/// If the terrain at that point is within an empty block
/// or the 2d position is outside of the terrain area then
/// it returns false.
///
bool getNormalHeightMaterial( const Point2F &pos,
Point3F *normal,
F32 *height,
StringTableEntry &matName ) const;
// only the editor currently uses this method - should always be using a ray to collide with
bool collideBox( const Point3F &start, const Point3F &end, RayInfo* info )
{
return castRay( start, end, info );
}
///
void setLightMap( GBitmap *newLightMap );
/// Fills the lightmap with white.
void clearLightMap();
/// Retuns the dimensions of the light map.
U32 getLightMapSize() const { return mLightMapSize; }
const GBitmap* getLightMap() const { return mLightMap; }
GBitmap* getLightMap() { return mLightMap; }
///
GFXTextureObject* getLightMapTex();
public:
bool setFile( const FileName& terrFileName );
void setFile( Resource<TerrainFile> file );
bool save(const char* filename);
F32 getSquareSize() const { return mSquareSize; }
/// Returns the dimensions of the terrain in world space.
F32 getWorldBlockSize() const { return mSquareSize * (F32)mFile->mSize; }
/// Retuns the dimensions of the terrain in samples.
U32 getBlockSize() const { return mFile->mSize; }
U32 getScreenError() const { return smLODScale * mScreenError; }
// SceneObject
void setTransform( const MatrixF &mat );
void setScale( const VectorF &scale );
void prepRenderImage ( SceneRenderState* state );
void buildConvex(const Box3F& box,Convex* convex);
bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere);
bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
bool castRayI(const Point3F &start, const Point3F &end, RayInfo* info, bool emptyCollide);
bool castRayBlock( const Point3F &pStart,
const Point3F &pEnd,
const Point2I &blockPos,
U32 level,
F32 invDeltaX,
F32 invDeltaY,
F32 startT,
F32 endT,
RayInfo *info,
bool collideEmpty );
const FileName& getTerrainFile() const { return mTerrFileName; }
void postLight(Vector<TerrainBlock *> &terrBlocks) {};
DECLARE_CONOBJECT(TerrainBlock);
static void initPersistFields();
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
void inspectPostApply();
};
#endif // _TERRDATA_H_

View file

@ -0,0 +1,162 @@
//-----------------------------------------------------------------------------
// 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 "terrain/terrMaterial.h"
#include "core/stream/fileStream.h"
#ifdef TORQUE_TOOLS
bool TerrainBlock::exportHeightMap( const UTF8 *filePath, const String &format ) const
{
GBitmap output( mFile->mSize,
mFile->mSize,
false,
GFXFormatR5G6B5 );
// First capture the max height... we'll normalize
// everything to this value.
U16 maxHeight = 0;
Vector<const U16>::iterator iBits = mFile->mHeightMap.begin();
for ( S32 y = 0; y < mFile->mSize; y++ )
{
for ( S32 x = 0; x < mFile->mSize; x++ )
{
if ( *iBits > maxHeight )
maxHeight = *iBits;
++iBits;
}
}
// Now write out the map.
iBits = mFile->mHeightMap.begin();
U16 *oBits = (U16*)output.getWritableBits();
for ( S32 y = 0; y < mFile->mSize; y++ )
{
for ( S32 x = 0; x < mFile->mSize; x++ )
{
// PNG expects big endian.
U16 height = (U16)( ( (F32)(*iBits) / (F32)maxHeight ) * (F32)U16_MAX );
*oBits = convertHostToBEndian( height );
++oBits;
++iBits;
}
}
FileStream stream;
if ( !stream.open( filePath, Torque::FS::File::Write ) )
{
Con::errorf( "TerrainBlock::exportHeightMap() - Error opening file for writing: %s !", filePath );
return false;
}
if ( !output.writeBitmap( format, stream ) )
{
Con::errorf( "TerrainBlock::exportHeightMap() - Error writing %s: %s !", format.c_str(), filePath );
return false;
}
// Print out the map size in meters, so that the user
// knows what values to use when importing it into
// another terrain tool.
S32 dim = mSquareSize * mFile->mSize;
S32 height = fixedToFloat( maxHeight );
Con::printf( "Saved heightmap with dimensions %d x %d x %d.", dim, dim, height );
return true;
}
bool TerrainBlock::exportLayerMaps( const UTF8 *filePrefix, const String &format ) const
{
for(S32 i = 0; i < mFile->mMaterials.size(); i++)
{
Vector<const U8>::iterator iBits = mFile->mLayerMap.begin();
GBitmap output( mFile->mSize,
mFile->mSize,
false,
GFXFormatA8 );
// Copy the layer data.
U8 *oBits = (U8*)output.getWritableBits();
dMemset( oBits, 0, mFile->mSize * mFile->mSize );
for ( S32 y = 0; y < mFile->mSize; y++ )
{
for ( S32 x = 0; x < mFile->mSize; x++ )
{
if(*iBits == i)
*oBits = 0xFF;
++iBits;
++oBits;
}
}
// Whats the full file name for this layer.
UTF8 filePath[1024];
dSprintf( filePath, 1024, "%s_%d_%s.%s", filePrefix, i, mFile->mMaterials[i]->getInternalName(), format.c_str() );
FileStream stream;
if ( !stream.open( filePath, Torque::FS::File::Write ) )
{
Con::errorf( "TerrainBlock::exportLayerMaps() - Error opening file for writing: %s !", filePath );
return false;
}
if ( !output.writeBitmap( format, stream ) )
{
Con::errorf( "TerrainBlock::exportLayerMaps() - Error writing %s: %s !", format.c_str(), filePath );
return false;
}
}
return true;
}
ConsoleMethod( TerrainBlock, exportHeightMap, bool, 3, 4, "(string filename, [string format]) - export the terrain block's heightmap to a bitmap file (default: png)" )
{
UTF8 fileName[1024];
String format = "png";
if( argc > 3 )
format = argv[ 3 ];
Con::expandScriptFilename( fileName, sizeof( fileName ), argv[2] );
return object->exportHeightMap( fileName, format );
}
ConsoleMethod( TerrainBlock, exportLayerMaps, bool, 3, 4, "(string filePrefix, [string format]) - export the terrain block's layer maps to bitmap files (default: png)" )
{
UTF8 filePrefix[1024];
String format = "png";
if( argc > 3 )
format = argv[3];
Con::expandScriptFilename( filePrefix, sizeof( filePrefix ), argv[2] );
return object->exportLayerMaps( filePrefix, format );
}
#endif

View file

@ -0,0 +1,36 @@
//-----------------------------------------------------------------------------
// 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/terrFeatureTypes.h"
#include "materials/materialFeatureTypes.h"
ImplementFeatureType( MFT_TerrainBaseMap, MFG_Texture, 100.0f, false );
ImplementFeatureType( MFT_TerrainParallaxMap, MFG_Texture, 101.0f, false );
ImplementFeatureType( MFT_TerrainDetailMap, MFG_Texture, 102.0f, false );
ImplementFeatureType( MFT_TerrainNormalMap, MFG_Texture, 103.0f, false );
ImplementFeatureType( MFT_TerrainLightMap, MFG_Texture, 104.0f, false );
ImplementFeatureType( MFT_TerrainSideProject, MFG_Texture, 105.0f, false );
ImplementFeatureType( MFT_TerrainAdditive, MFG_PostProcess, 999.0f, false );

View file

@ -0,0 +1,39 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRFEATURETYPES_H_
#define _TERRFEATURETYPES_H_
#ifndef _FEATURETYPE_H_
#include "shaderGen/featureType.h"
#endif
DeclareFeatureType( MFT_TerrainBaseMap );
DeclareFeatureType( MFT_TerrainDetailMap );
DeclareFeatureType( MFT_TerrainNormalMap );
DeclareFeatureType( MFT_TerrainParallaxMap );
DeclareFeatureType( MFT_TerrainLightMap );
DeclareFeatureType( MFT_TerrainSideProject );
DeclareFeatureType( MFT_TerrainAdditive );
#endif // _TERRFEATURETYPES_H_

View file

@ -0,0 +1,852 @@
//-----------------------------------------------------------------------------
// 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()
: mNeedsResaving( false ),
mFileVersion( FILE_VERSION ),
mSize( 256 )
{
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 *sq = mGridMapPool.address();
for ( S32 i = mGridLevels; i >= 0; i-- )
{
mGridMap[i] = sq;
sq += 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], &GFXDefaultPersistentProfile, 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, &GFXDefaultPersistentProfile, 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 )
{
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() == GFXFormatR5G6B5 )
{
const F32 toFixedPoint = ( 1.0f / (F32)U16_MAX ) * floatToFixed( heightScale );
const U16 *iBits = (const U16*)heightMap.getBits();
for ( U32 i = 0; i < mSize * mSize; i++ )
{
U16 height = convertBEndianToHost( *iBits );
*oBits = (U16)mCeil( (F32)height * toFixedPoint );
++oBits;
++iBits;
}
}
else
{
const F32 toFixedPoint = ( 1.0f / (F32)U8_MAX ) * floatToFixed( heightScale );
const U8 *iBits = heightMap.getBits();
for ( U32 i = 0; i < mSize * mSize; i++ )
{
*oBits = (U16)mCeil( ((F32)*iBits) * toFixedPoint );
++oBits;
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 - first try using the input filename (mission name)
Torque::Path basePath( *inOutFilename );
if ( !basePath.getExtension().equal("mis") )
{
// Use the default path and filename
basePath.setPath( "art/terrains" );
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 ) );
}
}
}
}

View file

@ -0,0 +1,282 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRFILE_H_
#define _TERRFILE_H_
#ifndef _TVECTOR_H_
#include <core/util/tVector.h>
#endif
#ifndef _PATH_H_
#include <core/util/path.h>
#endif
#ifndef _MATERIALLIST_H_
#include "materials/materialList.h"
#endif
#ifndef _TERRMATERIAL_H_
#include "terrain/terrMaterial.h"
#endif
class TerrainMaterial;
class FileStream;
class GBitmap;
///
struct TerrainSquare
{
U16 minHeight;
U16 maxHeight;
U16 heightDeviance;
U16 flags;
enum
{
Split45 = BIT(0),
Empty = BIT(1),
HasEmpty = BIT(2),
};
};
/// NOTE: The terrain uses 11.5 fixed point which gives
/// us a height range from 0->2048 in 1/32 increments.
typedef U16 TerrainHeight;
///
class TerrainFile
{
protected:
friend class TerrainBlock;
/// The materials used to render the terrain.
Vector<TerrainMaterial*> mMaterials;
/// The dimensions of the layer and height maps.
U32 mSize;
/// The layer index at each height map sample.
Vector<U8> mLayerMap;
/// The fixed point height map.
/// @see fixedToFloat
Vector<U16> mHeightMap;
/// The memory pool used by the grid map layers.
Vector<TerrainSquare> mGridMapPool;
///
U32 mGridLevels;
/// The grid map layers used to accelerate collision
/// queries for the height map data.
Vector<TerrainSquare*> mGridMap;
/// MaterialList used to map terrain materials to material instances for the
/// sake of collision (physics, etc.).
MaterialList mMaterialInstMapping;
/// The file version.
U32 mFileVersion;
/// The dirty flag.
bool mNeedsResaving;
/// The full path and name of the TerrainFile
Torque::Path mFilePath;
/// The internal loading function.
void _load( FileStream &stream );
/// The legacy file loading code.
void _loadLegacy( FileStream &stream );
/// Used to populate the materail vector by finding the
/// TerrainMaterial objects by name.
void _resolveMaterials( const Vector<String> &materials );
///
void _buildGridMap();
///
void _initMaterialInstMapping();
public:
enum Constants
{
FILE_VERSION = 7
};
TerrainFile();
virtual ~TerrainFile();
///
static void create( String *inOutFilename,
U32 newSize,
const Vector<String> &materials );
///
static TerrainFile* load( const Torque::Path &path );
bool save( const char *filename );
///
void import( const GBitmap &heightMap,
F32 heightScale,
const Vector<U8> &layerMap,
const Vector<String> &materials );
/// Updates the terrain grid for the specified area.
void updateGrid( const Point2I &minPt, const Point2I &maxPt );
/// Performs multiple smoothing steps on the heightmap.
void smooth( F32 factor, U32 steps, bool updateCollision );
void setSize( U32 newResolution, bool clear );
TerrainSquare* findSquare( U32 level, U32 x, U32 y ) const;
BaseMatInstance* getMaterialMapping( U32 index ) const;
StringTableEntry getMaterialName( U32 x, U32 y) const;
void setLayerIndex( U32 x, U32 y, U8 index );
U8 getLayerIndex( U32 x, U32 y ) const;
bool isEmptyAt( U32 x, U32 y ) const { return getLayerIndex( x, y ) == U8_MAX; }
void setHeight( U32 x, U32 y, U16 height );
const U16* getHeightAddress( U32 x, U32 y ) const;
U16 getHeight( U32 x, U32 y ) const;
U16 getMaxHeight() const { return mGridMap[mGridLevels]->maxHeight; }
/// Returns the constant heightmap vector.
const Vector<U16>& getHeightMap() const { return mHeightMap; }
/// Sets a new heightmap state.
void setHeightMap( const Vector<U16> &heightmap, bool updateCollision );
/// Check if the given point is valid within the (non-tiled) terrain file.
bool isPointInTerrain( U32 x, U32 y ) const;
};
inline TerrainSquare* TerrainFile::findSquare( U32 level, U32 x, U32 y ) const
{
x %= mSize;
y %= mSize;
x >>= level;
y >>= level;
return mGridMap[level] + x + ( y << ( mGridLevels - level ) );
}
inline void TerrainFile::setHeight( U32 x, U32 y, U16 height )
{
x %= mSize;
y %= mSize;
mHeightMap[ x + ( y * mSize ) ] = height;
}
inline const U16* TerrainFile::getHeightAddress( U32 x, U32 y ) const
{
x %= mSize;
y %= mSize;
return &mHeightMap[ x + ( y * mSize ) ];
}
inline U16 TerrainFile::getHeight( U32 x, U32 y ) const
{
x %= mSize;
y %= mSize;
return mHeightMap[ x + ( y * mSize ) ];
}
inline U8 TerrainFile::getLayerIndex( U32 x, U32 y ) const
{
x %= mSize;
y %= mSize;
return mLayerMap[ x + ( y * mSize ) ];
}
inline void TerrainFile::setLayerIndex( U32 x, U32 y, U8 index )
{
x %= mSize;
y %= mSize;
mLayerMap[ x + ( y * mSize ) ] = index;
}
inline BaseMatInstance* TerrainFile::getMaterialMapping( U32 index ) const
{
if ( index < mMaterialInstMapping.size() )
return mMaterialInstMapping.getMaterialInst( index );
else
return NULL;
}
inline StringTableEntry TerrainFile::getMaterialName( U32 x, U32 y) const
{
x %= mSize;
y %= mSize;
const U8 &index = mLayerMap[ x + ( y * mSize ) ];
if ( index < mMaterials.size() )
return mMaterials[ index ]->getInternalName();
return StringTable->EmptyString();
}
/// Conversion from 11.5 fixed point to floating point.
inline F32 fixedToFloat( U16 val )
{
return F32(val) * 0.03125f;
}
/// Conversion from floating point to 11.5 fixed point.
inline U16 floatToFixed( F32 val )
{
return U16(val * 32.0);
}
inline bool TerrainFile::isPointInTerrain( U32 x, U32 y ) const
{
if ( x < mSize && y < mSize)
return true;
return false;
}
#endif // _TERRFILE_H_

View file

@ -0,0 +1,313 @@
//-----------------------------------------------------------------------------
// 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"
ConsoleStaticMethod( TerrainBlock, createNew, S32, 5, 5,
"TerrainBlock.create( String terrainName, U32 resolution, String materialName, bool genNoise )\n"
"" )
{
const UTF8 *terrainName = argv[1];
U32 resolution = dAtoi( argv[2] );
const UTF8 *materialName = argv[3];
bool genNoise = dAtob( argv[4] );
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.
FileName terrFileName( Con::getVariable("$Client::MissionFile") );
terrFileName.replace("tools/levels/", "art/terrains/");
terrFileName.replace("levels/", "art/terrains/");
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.
F32 octaves = 3.0f;
U32 freq = 4;
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 );
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 );
// Add to mission group!
SimGroup *missionGroup;
if( Sim::findObject( "MissionGroup", missionGroup ) )
missionGroup->addObject( terrain );
return terrain->getId();
}
ConsoleStaticMethod( TerrainBlock, import, S32, 7, 7,
"( String terrainName, String heightMap, F32 metersPerPixel, F32 heightScale, String materials, String opacityLayers )\n"
"" )
{
// Get the parameters.
const UTF8 *terrainName = argv[1];
const UTF8 *hmap = argv[2];
F32 metersPerPixel = dAtof(argv[3]);
F32 heightScale = dAtof(argv[4]);
const UTF8 *opacityFiles = argv[5];
const UTF8 *materialsStr = argv[6];
// First load the height map and validate it.
Resource<GBitmap> heightmap = GBitmap::load( hmap );
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( opacityFiles, "\n" );
Vector<U8> layerMap;
layerMap.setSize( terrSize * terrSize );
{
Vector<GBitmap*> bitmaps;
for ( U32 i = 0; i < fileCount; i++ )
{
String fileNameWithChannel = StringUnit::getUnit( opacityFiles, 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 ) );
if ( terrain )
terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials );
else
{
terrain = new TerrainBlock();
terrain->assignName( terrainName );
terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials );
terrain->registerObject();
// Add to mission group!
SimGroup *missionGroup;
if ( Sim::findObject( "MissionGroup", missionGroup ) )
missionGroup->addObject( terrain );
}
return terrain->getId();
}
bool TerrainBlock::import( const GBitmap &heightMap,
F32 heightScale,
F32 metersPerPixel,
const Vector<U8> &layerMap,
const Vector<String> &materials )
{
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 = "terrain";
mTerrFileName = FS::MakeUniquePath( "levels", fileName, "ter" );
// 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 );
// Set the square size.
mSquareSize = metersPerPixel;
if ( isProperlyAdded() )
{
// Update the server bounds.
_updateBounds();
// Make sure the client gets updated.
setMaskBits( HeightMapChangeMask | SizeMask );
}
return true;
}

View file

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// 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 "terrain/terrRender.h"
#include "lighting/lightInfo.h"
#include "scene/sceneRenderState.h"
/*
U32 TerrainRender::testSquareLights(GridSquare *sq, S32 level, const Point2I &pos, U32 lightMask)
{
// Calculate our Box3F for this GridSquare
Point3F boxMin(pos.x * mSquareSize + mBlockPos.x, pos.y * mSquareSize + mBlockPos.y, fixedToFloat(sq->minHeight));
F32 blockSize = F32(mSquareSize * (1 << level));
F32 blockHeight = fixedToFloat(sq->maxHeight - sq->minHeight);
Point3F boxMax(boxMin);
boxMax += Point3F(blockSize, blockSize, blockHeight);
Box3F gridBox(boxMin, boxMax);
U32 retMask = 0;
for(S32 i = 0; (lightMask >> i) != 0; i++)
{
if(lightMask & (1 << i))
{
if (mTerrainLights[i].light->mType != LightInfo::Vector)
{
// test the visibility of this light to box
F32 dist = gridBox.getDistanceFromPoint(mTerrainLights[i].pos);
static F32 minDist = 1e14f;
minDist = getMin(minDist, dist);
if(dist < mTerrainLights[i].radius)
retMask |= (1 << i);
} else {
retMask |= (1 << i);
}
}
}
return retMask;
}
void TerrainRender::buildLightArray(SceneState * state)
{
PROFILE_SCOPE(TerrainRender_buildLightArray);
mDynamicLightCount = 0;
if ((mTerrainLighting == NULL) || (!TerrainRender::mEnableTerrainDynLights))
return;
static LightInfoList lights;
lights.clear();
LIGHTMGR->getBestLights(lights);
// create terrain lights from these...
U32 curIndex = 0;
for(U32 i = 0; i < lights.size(); i++)
{
LightInfo* light = lights[i];
if((light->mType != LightInfo::Point) && (light->mType != LightInfo::Spot))
continue;
// set the 'fo
TerrLightInfo & info = mTerrainLights[curIndex++];
mCurrentBlock->getWorldTransform().mulP(light->mPos, &info.pos);
info.radius = light->getRadius();
info.light = light;
}
mDynamicLightCount = curIndex;
}
*/

View file

@ -0,0 +1,165 @@
//-----------------------------------------------------------------------------
// 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/terrMaterial.h"
#include "console/consoleTypes.h"
#include "gfx/bitmap/gBitmap.h"
IMPLEMENT_CONOBJECT( TerrainMaterial );
ConsoleDocClass( TerrainMaterial,
"@brief The TerrainMaterial class orginizes the material settings "
"for a single terrain material layer.\n\n"
"@note You should not be creating TerrainMaterials by hand in code. "
"All TerrainMaterials should be created in the editors, as intended "
"by the system.\n\n"
"@tsexample\n"
"// Created by the Terrain Painter tool in the World Editor\n"
"new TerrainMaterial()\n"
"{\n"
" internalName = \"grass1\";\n"
" diffuseMap = \"art/terrains/Test/grass1\";\n"
" detailMap = \"art/terrains/Test/grass1_d\";\n"
" detailSize = \"10\";\n"
" isManaged = \"1\";\n"
" detailBrightness = \"1\";\n"
" Enabled = \"1\";\n"
" diffuseSize = \"200\";\n"
"};\n"
"@endtsexample\n\n"
"@see Materials\n"
"@ingroup enviroMisc\n");
TerrainMaterial::TerrainMaterial()
: mSideProjection( false ),
mDiffuseSize( 500.0f ),
mDetailSize( 5.0f ),
mDetailStrength( 1.0f ),
mDetailDistance( 50.0f ),
mParallaxScale( 0.0f )
{
}
TerrainMaterial::~TerrainMaterial()
{
}
void TerrainMaterial::initPersistFields()
{
addField( "diffuseMap", TypeStringFilename, Offset( mDiffuseMap, TerrainMaterial ), "Base texture for the material" );
addField( "diffuseSize", TypeF32, Offset( mDiffuseSize, TerrainMaterial ), "Used to scale the diffuse map to the material square" );
addField( "normalMap", TypeStringFilename, Offset( mNormalMap, TerrainMaterial ), "Bump map for the material" );
addField( "detailMap", TypeStringFilename, Offset( mDetailMap, TerrainMaterial ), "Detail map for the material" );
addField( "detailSize", TypeF32, Offset( mDetailSize, TerrainMaterial ), "Used to scale the detail map to the material square" );
addField( "detailStrength", TypeF32, Offset( mDetailStrength, TerrainMaterial ), "Exponentially sharpens or lightens the detail map rendering on the material" );
addField( "detailDistance", TypeF32, Offset( mDetailDistance, TerrainMaterial ), "Changes how far camera can see the detail map rendering on the material" );
addField( "useSideProjection", TypeBool, Offset( mSideProjection, TerrainMaterial ),"Makes that terrain material project along the sides of steep "
"slopes instead of projected downwards");
addField( "parallaxScale", TypeF32, Offset( mParallaxScale, TerrainMaterial ), "Used to scale the height from the normal map to give some self "
"occlusion effect (aka parallax) to the terrain material" );
Parent::initPersistFields();
// Gotta call this at least once or it won't get created!
Sim::getTerrainMaterialSet();
}
bool TerrainMaterial::onAdd()
{
if ( !Parent::onAdd() )
return false;
SimSet *set = Sim::getTerrainMaterialSet();
// Make sure we have an internal name set.
if ( !mInternalName || !mInternalName[0] )
Con::warnf( "TerrainMaterial::onAdd() - No internal name set!" );
else
{
SimObject *object = set->findObjectByInternalName( mInternalName );
if ( object )
Con::warnf( "TerrainMaterial::onAdd() - Internal name collision; '%s' already exists!", mInternalName );
}
set->addObject( this );
return true;
}
TerrainMaterial* TerrainMaterial::getWarningMaterial()
{
return findOrCreate( NULL );
}
TerrainMaterial* TerrainMaterial::findOrCreate( const char *nameOrPath )
{
SimSet *set = Sim::getTerrainMaterialSet();
if ( !nameOrPath || !nameOrPath[0] )
nameOrPath = "warning_material";
// See if we can just find it.
TerrainMaterial *mat = dynamic_cast<TerrainMaterial*>( set->findObjectByInternalName( StringTable->insert( nameOrPath ) ) );
if ( mat )
return mat;
// We didn't find it... so see if its a path to a
// file. If it is lets assume its the texture.
if ( GBitmap::sFindFiles( nameOrPath, NULL ) )
{
mat = new TerrainMaterial();
mat->setInternalName( nameOrPath );
mat->mDiffuseMap = nameOrPath;
mat->registerObject();
Sim::getRootGroup()->addObject( mat );
return mat;
}
// Ok... return a debug material then.
mat = dynamic_cast<TerrainMaterial*>( set->findObjectByInternalName( StringTable->insert( "warning_material" ) ) );
if ( !mat )
{
// This shouldn't happen.... the warning_texture should
// have already been defined in script, but we put this
// fallback here just in case it gets "lost".
mat = new TerrainMaterial();
mat->setInternalName( "warning_material" );
mat->mDiffuseMap = "core/art/warnMat.png";
mat->mDiffuseSize = 500;
mat->mDetailMap = "core/art/warnMat.png";
mat->mDetailSize = 5;
mat->registerObject();
Sim::getRootGroup()->addObject( mat );
}
return mat;
}

View file

@ -0,0 +1,111 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRMATERIAL_H_
#define _TERRMATERIAL_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
/// The TerrainMaterial class orginizes the material settings
/// for a single terrain material layer.
class TerrainMaterial : public SimObject
{
typedef SimObject Parent;
protected:
///
FileName mDiffuseMap;
/// The size of the diffuse base map in meters
/// used to generate its texture coordinates.
F32 mDiffuseSize;
///
FileName mNormalMap;
///
FileName mDetailMap;
/// The size of the detail map in meters used
/// to generate the texture coordinates for the
/// detail and normal maps.
F32 mDetailSize;
///
F32 mDetailStrength;
///
F32 mDetailDistance;
/// Normally the detail is projected on to the xy
/// coordinates of the terrain. If this flag is true
/// then this detail is projected along the xz and yz
/// planes.
bool mSideProjection;
///
F32 mParallaxScale;
public:
TerrainMaterial();
virtual ~TerrainMaterial();
bool onAdd();
static void initPersistFields();
DECLARE_CONOBJECT( TerrainMaterial );
/// This method locates the TerrainMaterial if it exists, tries
/// to create a new one if a valid texture path was passed, or
/// returns a debug material if all else fails.
static TerrainMaterial* findOrCreate( const char *nameOrPath );
/// Returns the default warning terrain material used when
/// a material is not found or defined.
static TerrainMaterial* getWarningMaterial();
const String& getDiffuseMap() const { return mDiffuseMap; }
F32 getDiffuseSize() const { return mDiffuseSize; }
const String& getNormalMap() const { return mNormalMap; }
const String& getDetailMap() const { return mDetailMap; }
F32 getDetailSize() const { return mDetailSize; }
F32 getDetailStrength() const { return mDetailStrength; }
F32 getDetailDistance() const { return mDetailDistance; }
bool useSideProjection() const { return mSideProjection; }
F32 getParallaxScale() const { return mParallaxScale; }
};
#endif // _TERRMATERIAL_H_

View file

@ -0,0 +1,493 @@
//-----------------------------------------------------------------------------
// 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/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"
#include "gfx/gfxTransformSaver.h"
#include "gfx/bitmap/gBitmap.h"
#include "gfx/bitmap/ddsFile.h"
#include "gfx/bitmap/ddsUtils.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()
{
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(),
&GFXDefaultStaticDiffuseProfile,
"TerrainBlock::_updateMaterials() - DiffuseMap" );
else
mBaseTextures[ i ] = GFXTexHandle();
// Find the maximum detail distance.
if ( mat->getDetailMap().isNotEmpty() &&
mat->getDetailDistance() > mMaxDetailDistance )
mMaxDetailDistance = mat->getDetailDistance();
}
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, GFXFormatR8G8B8A8, &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, GFXBlendInvSrcAlpha );
desc.cullDefined = true;
desc.cullMode = GFXCullNone;
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;
const bool needsYFlip = GFX->getAdapterType() == OpenGL;
GFXVertexPT points[4];
points[0].point = Point3F( -1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 );
points[0].texCoord = Point2F( 0.0, needsYFlip ? 0.0f : 1.0f );
points[1].point = Point3F( -1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 );
points[1].texCoord = Point2F( 0.0, needsYFlip ? 1.0f : 0.0f );
points[2].point = Point3F( 1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 );
points[2].texCoord = Point2F( 1.0, needsYFlip ? 1.0f : 0.0f );
points[3].point = Point3F( 1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 );
points[3].texCoord = Point2F( 1.0, needsYFlip ? 0.0f : 1.0f );
vb.set( GFX, 4, GFXBufferTypeVolatile );
dMemcpy( vb.lock(), 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 &&
mBaseTex->getWidth() == destSize.x &&
mBaseTex->getHeight() == destSize.y )
blendTex = mBaseTex;
else
blendTex.set( destSize.x, destSize.y, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, "" );
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->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( GFXTriangleFan, 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 ( writeToCache )
{
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 );
DDSUtil::squishDDS( blendDDS, GFXFormatDXT1 );
// Write result to file stream
blendDDS->write( fs );
delete blendDDS;
}
fs.close();
}
else
{
// 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;
}
}
void TerrainBlock::_renderBlock( SceneRenderState *state )
{
PROFILE_SCOPE( TerrainBlock_RenderBlock );
// 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;
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();
// Submit it for rendering.
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();
}

View file

@ -0,0 +1,59 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TERRRENDER_H_
#define _TERRRENDER_H_
#ifndef _TERRDATA_H_
#include "terrain/terrData.h"
#endif
enum TerrConstants
{
MaxClipPlanes = 8, ///< left, right, top, bottom - don't need far tho...
//MaxTerrainMaterials = 256,
MaxTerrainLights = 64,
MaxVisibleLights = 31,
ClipPlaneMask = (1 << MaxClipPlanes) - 1,
FarSphereMask = 0x80000000,
FogPlaneBoxMask = 0x40000000,
};
class SceneRenderState;
// Allows a lighting system to plug into terrain rendering
class TerrainLightingPlugin
{
public:
virtual ~TerrainLightingPlugin() {}
virtual void setupLightStage(LightManager * lm, LightInfo* light, SceneData& sgData, BaseMatInstance* basemat, BaseMatInstance** dmat) = 0;
virtual void cleanupLights(LightManager * lm) {}
};
/// A special texture profile used for the terrain layer id map.
GFX_DeclareTextureProfile( TerrainLayerTexProfile );
#endif // _TERRRENDER_H_