1067 lines
34 KiB
C++
Executable File
1067 lines
34 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "terrain/waterBlock.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "sceneGraph/sceneGraph.h"
|
|
#include "sceneGraph/sceneState.h"
|
|
#include "core/bitStream.h"
|
|
#include "math/mBox.h"
|
|
#include "dgl/dgl.h"
|
|
#include "core/color.h"
|
|
#include "terrain/terrData.h"
|
|
#include "terrain/terrRender.h"
|
|
#include "math/mathIO.h"
|
|
#include "sceneGraph/sgUtil.h"
|
|
#include "audio/audioDataBlock.h"
|
|
#include "platform/profiler.h"
|
|
|
|
//==============================================================================
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(WaterBlock);
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::mCameraSubmerged = false;
|
|
U32 WaterBlock::mSubmergedType = 0;
|
|
TextureHandle WaterBlock::mSubmergeTexture[WC_NUM_SUBMERGE_TEX];
|
|
|
|
//==============================================================================
|
|
// I know this is a bit of a hack. I've done this in order to avoid a coupling
|
|
// between the fluid and the rest of the system.
|
|
//
|
|
static SceneState* pSceneState = NULL;
|
|
|
|
static F32 FogFunction( F32 Distance, F32 DeltaZ )
|
|
{
|
|
return( pSceneState->getHazeAndFog( Distance, DeltaZ ) );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
WaterBlock::WaterBlock()
|
|
{
|
|
mNetFlags.set(Ghostable | ScopeAlways);
|
|
mTypeMask = WaterObjectType;
|
|
|
|
mObjBox.min.set( 0, 0, 0 );
|
|
mObjBox.max.set( 1, 1, 1 );
|
|
|
|
mTile = true;
|
|
mLiquidType = eOceanWater;
|
|
mDensity = 1;
|
|
mViscosity = 15;
|
|
mWaveMagnitude = 1.0f;
|
|
mSurfaceTexture = TextureHandle();
|
|
mSpecMaskTex = TextureHandle();
|
|
mSurfaceOpacity = 0.75f;
|
|
mEnvMapOverTexture = TextureHandle();
|
|
mEnvMapUnderTexture = TextureHandle();
|
|
mEnvMapIntensity = 0.4f;
|
|
mShoreTexture = TextureHandle();
|
|
mRemoveWetEdges = false;
|
|
mAudioEnvironment = 0;
|
|
|
|
// lets be good little programmers and initialize our data!
|
|
mSurfaceName = NULL;
|
|
mSpecMaskName = NULL;
|
|
mEnvMapOverName = NULL;
|
|
mEnvMapUnderName = NULL;
|
|
mShoreName = NULL;
|
|
|
|
dMemset( mSubmergeName, 0, sizeof( mSubmergeName ) );
|
|
|
|
mpTerrain = NULL;
|
|
mSurfaceZ = 0.0f;
|
|
|
|
mEditorApplied = false;
|
|
mShoreDepth = 20.0f;
|
|
mMinAlpha = 0.03f;
|
|
mMaxAlpha = 1.0f;
|
|
mDepthGradient = 1.0f;
|
|
mUseDepthMap = true;
|
|
mTessellationSurface = 50;
|
|
mTessellationShore = 60;
|
|
mSurfaceParallax = 0.5f;
|
|
mFlowAngle = 0.0f;
|
|
mFlowRate = 0.0f;
|
|
mDistortGridScale = 0.1f;
|
|
mDistortMagnitude = 0.05f;
|
|
mDistortTime = 0.5f;
|
|
|
|
mFluid.SetFogFn( FogFunction );
|
|
mSpecColor.set( 1.0, 1.0, 1.0, 1.0 );
|
|
mSpecPower = 6;
|
|
|
|
mTerrainHalfSize = 1024;
|
|
mTerrainSquareSize = 8;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
WaterBlock::~WaterBlock()
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::SnagTerrain( SceneObject* sceneObj, void * key )
|
|
{
|
|
WaterBlock* pWater = (WaterBlock*)key;
|
|
pWater->mpTerrain = dynamic_cast<TerrainBlock*>(sceneObj);
|
|
|
|
if(pWater->mpTerrain)
|
|
{
|
|
pWater->mTerrainHalfSize = pWater->mpTerrain->getSquareSize() * 128.0;
|
|
pWater->mTerrainSquareSize = pWater->mpTerrain->getSquareSize();
|
|
|
|
pWater->UpdateFluidRegion();
|
|
}
|
|
else
|
|
{
|
|
pWater->mTerrainHalfSize = 1024;
|
|
pWater->mTerrainSquareSize = 8;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::UpdateFluidRegion( void )
|
|
{
|
|
MatrixF M;
|
|
Point3F P;
|
|
|
|
P = mObjToWorld.getPosition();
|
|
P.x += F32(mTerrainHalfSize);
|
|
P.y += F32(mTerrainHalfSize);
|
|
|
|
mSurfaceZ = P.z + mObjScale.z;
|
|
Point2F s(mObjScale.x, mObjScale.y);
|
|
|
|
mFluid.SetInfo( P.x,
|
|
P.y,
|
|
s.x, s.y,
|
|
mSurfaceZ,
|
|
mWaveMagnitude,
|
|
mSurfaceOpacity,
|
|
mEnvMapIntensity,
|
|
mRemoveWetEdges,
|
|
mUseDepthMap,
|
|
mTessellationSurface,
|
|
mTessellationShore,
|
|
mSurfaceParallax,
|
|
mFlowAngle,
|
|
mFlowRate,
|
|
mDistortGridScale,
|
|
mDistortMagnitude,
|
|
mDistortTime,
|
|
mSpecColor,
|
|
mSpecPower,
|
|
mTile,
|
|
mTerrainHalfSize * 2,
|
|
mTerrainSquareSize);
|
|
|
|
P.x -= F32(mTerrainHalfSize);
|
|
P.y -= F32(mTerrainHalfSize);
|
|
|
|
M.identity();
|
|
M.setPosition( P );
|
|
|
|
Parent::setTransform( M );
|
|
|
|
resetWorldBox();
|
|
|
|
if( isServerObject() )
|
|
setMaskBits(1);
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::CalculateDepthMaps(void)
|
|
{
|
|
// Generate Depth Textures.
|
|
GenerateDepthTextures(mDepthBitmap, mDepthTexture, false);
|
|
GenerateDepthTextures(mShoreDepthBitmap, mShoreDepthTexture, true);
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
/// Generate depth textures for water interaction with terrain.
|
|
///
|
|
/// What we are doing here is sample the terrain height at it's own resolution over the area of the
|
|
/// defined waterblock. With these height points we can calculate an appropriate alpha value for the
|
|
/// depth-map texture texel. This results in a *very* coarse alpha-map but this is resolved by passing
|
|
/// the texture through an iterative blur-convolution.
|
|
///
|
|
/// The resultant texture is used to alpha-blend in the surfaces and environment map using multi-texturing.
|
|
/// @author Melv May
|
|
void WaterBlock::GenerateDepthTextures(GBitmap* pBitmap, TextureHandle& mTexture, bool ShoreFlag)
|
|
{
|
|
// Is this a client object?
|
|
if (isClientObject())
|
|
{
|
|
// Yes, so we should have a depth-map bitmap.
|
|
AssertWarn(pBitmap != NULL, "Waterblock: We should have a depth-map bitmap!");
|
|
|
|
// Do we have the terrain yet?
|
|
if (mpTerrain)
|
|
{
|
|
F32 DepthPoint;
|
|
U32 FluidPointColour;
|
|
U32* pTexelBlockOutput;
|
|
|
|
// Fetch the Bitmaps Data.
|
|
U32* pDepthBits = (U32*)pBitmap->getAddress(0,0,0);
|
|
|
|
// Calculate Fluid Min/Max Colour-Alpha.
|
|
const U32 mFluidColour = 0xffffff;
|
|
const U32 mMinColour = (mFluidColour + (((U32)(mMinAlpha * 255)) << 24));
|
|
const U32 mMaxColour = (mFluidColour + (((U32)(mMaxAlpha * 255)) << 24));
|
|
|
|
// Calculate the Depth Bitmap Resolutions.
|
|
F32 DepthSqrResX = pBitmap->getWidth(0) / mFluid.m_SquaresInX;
|
|
F32 DepthSqrResY = pBitmap->getHeight(0) / mFluid.m_SquaresInY;
|
|
|
|
// Calculate the Inverse Depth Bitmap Resolutions.
|
|
F32 InvDepthSqrResX = 1.0f / DepthSqrResX;
|
|
F32 InvDepthSqrResY = 1.0f / DepthSqrResY;
|
|
|
|
// Calculate the Depth Bitmap Texel Resolutions.
|
|
F32 DepthTexResX = mObjScale.x / eDepthMapResolution;
|
|
F32 DepthTexResY = mObjScale.y / eDepthMapResolution;
|
|
|
|
|
|
// Calculate the Depth Bitmap Strides.
|
|
U32 XStride = pBitmap->getWidth(0);
|
|
U32 YStride = pBitmap->getHeight(0);
|
|
|
|
// Calculate the FluidLevel.
|
|
F32 FluidLevel = mSurfaceZ;
|
|
F32 Height;
|
|
|
|
// Fetch the Waterblock Position.
|
|
Point3F TerrainPos = mObjToWorld.getPosition();
|
|
// Change Spaces.
|
|
TerrainPos.x += F32(mTerrainHalfSize);
|
|
TerrainPos.y += F32(mTerrainHalfSize);
|
|
|
|
// Terrain Height Offsets.
|
|
F32 TerrainOffsetX;
|
|
F32 TerrainOffsetY = 0;
|
|
|
|
// Calculate Alpha Range.
|
|
F32 AlphaRange = mMaxAlpha - mMinAlpha;
|
|
|
|
// Calculate depth Alpha-Texel Map.
|
|
for (s32 t = 0; t < eDepthMapResolution; t++)
|
|
{
|
|
// Reset Terrain Height Offset X.
|
|
TerrainOffsetX = 0;
|
|
|
|
for (s32 s = 0; s < eDepthMapResolution; s++)
|
|
{
|
|
// Fetch Terrain Height at current position.
|
|
if ( mpTerrain->getHeight( Point2F(TerrainPos.x + TerrainOffsetX , TerrainPos.y + TerrainOffsetY),
|
|
&Height) )
|
|
{
|
|
// Got a valid height so ...
|
|
|
|
// Calculate Depth Point.
|
|
DepthPoint = FluidLevel - Height;
|
|
|
|
// Are we below the minimum depth?
|
|
if (DepthPoint <= 0)
|
|
{
|
|
// Yes, so set to minimum colour/alpha.
|
|
FluidPointColour = mMinColour;
|
|
}
|
|
// ... so are we below the maximum depth?
|
|
else if (DepthPoint < mShoreDepth)
|
|
{
|
|
// Normalise Range.
|
|
F32 NormRange = mClampF(DepthPoint / mShoreDepth, 0.0f, 1.0f);
|
|
|
|
// Calculate Depth Alpha.
|
|
F32 DepthAlpha = (AlphaRange * mPow(NormRange, 1.0f / mDepthGradient)) + mMinAlpha;
|
|
|
|
// Calculate Resultant Fluid Colour/Alpha.
|
|
FluidPointColour = ((U32)(DepthAlpha*255)) << 24 | mFluidColour;
|
|
}
|
|
// ... out of our depth range ...
|
|
else
|
|
{
|
|
// Are we doing a shoreline depth-map?
|
|
if (ShoreFlag)
|
|
{
|
|
// Yesm so normalise Range.
|
|
F32 NormRange = mClampF((DepthPoint-mShoreDepth) / mShoreDepth, 0.0f, 1.0f);
|
|
|
|
// Calculate Depth Alpha.
|
|
F32 DepthAlpha = mMaxAlpha - ((AlphaRange * mPow(NormRange, 1.0f / mDepthGradient)) + mMinAlpha);
|
|
|
|
// Calculate Resultant Fluid Colour/Alpha.
|
|
FluidPointColour = ((U32)(255*DepthAlpha)) << 24 | mFluidColour;
|
|
}
|
|
else
|
|
{
|
|
// No so clip to maximum colour/alpha.
|
|
FluidPointColour = mMaxColour;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Eeek, empty grid square ...
|
|
FluidPointColour = mFluidColour;
|
|
}
|
|
|
|
// Calculate Position of Texel Block Origin.
|
|
pTexelBlockOutput = pDepthBits + (U32)(s + (t * YStride));
|
|
|
|
// Write Point Height to Texel.
|
|
*pTexelBlockOutput = convertLEndianToHost(FluidPointColour);
|
|
|
|
// Move to next Terrain Height Point.
|
|
TerrainOffsetX += DepthTexResX;
|
|
}
|
|
|
|
// Move to next Terrain Height Point.
|
|
TerrainOffsetY += DepthTexResY;
|
|
}
|
|
|
|
// Update the texture.
|
|
mTexture.refresh();
|
|
#if 0
|
|
// --------------------------------------------------------------------------------------
|
|
// DEBUG CODE: Dump the Depth Alpha-Map as a PNG file.
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// Output file.
|
|
FileStream fStream;
|
|
if(!fStream.open("depthmap.png", FileStream::Write))
|
|
{
|
|
Con::printf("Waterblock: Failed to open debug depth-map file!");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
mDepthTexture.getBitmap()->writePNG(fStream);
|
|
fStream.close();
|
|
}
|
|
#endif
|
|
// --------------------------------------------------------------------------------------
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::onAdd()
|
|
{
|
|
if( !isClientObject() )
|
|
{
|
|
// Make sure that the Fluid has had a chance to tweak the values.
|
|
UpdateFluidRegion();
|
|
}
|
|
|
|
if( !Parent::onAdd() )
|
|
return false;
|
|
|
|
if( isClientObject() )
|
|
{
|
|
// load textures
|
|
bool success = true;
|
|
|
|
mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture );
|
|
mSpecMaskTex = TextureHandle( mSpecMaskName, MeshTexture );
|
|
mEnvMapOverTexture = TextureHandle( mEnvMapOverName, MeshTexture );
|
|
mEnvMapUnderTexture = TextureHandle( mEnvMapUnderName, MeshTexture );
|
|
mShoreTexture = TextureHandle( mShoreName, MeshTexture );
|
|
|
|
for( int i=0; i<WC_NUM_SUBMERGE_TEX; i++ )
|
|
{
|
|
if( mSubmergeName[i] && mSubmergeName[i][0] )
|
|
{
|
|
mLocalSubmergeTexture[i] = TextureHandle( mSubmergeName[i], MeshTexture );
|
|
mSubmergeTexture[i] = mLocalSubmergeTexture[i];
|
|
if(!mLocalSubmergeTexture[i])
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
if(!success && !bool(mServerObject))
|
|
return false;
|
|
|
|
mDepthBitmap = new GBitmap(eDepthMapResolution, eDepthMapResolution, false, GBitmap::RGBA);
|
|
if(!mDepthBitmap)
|
|
return false;
|
|
|
|
mDepthTexture = TextureHandle(NULL, mDepthBitmap, false);
|
|
|
|
mShoreDepthBitmap = new GBitmap(eDepthMapResolution, eDepthMapResolution, false, GBitmap::RGBA);
|
|
if(!mShoreDepthBitmap)
|
|
return false;
|
|
|
|
mShoreDepthTexture = TextureHandle(NULL, mShoreDepthBitmap, false);
|
|
|
|
mFluid.SetTextures( mSurfaceTexture,
|
|
mEnvMapOverTexture,
|
|
mEnvMapUnderTexture,
|
|
mShoreTexture,
|
|
mDepthTexture,
|
|
mShoreDepthTexture,
|
|
mSpecMaskTex);
|
|
}
|
|
|
|
resetWorldBox();
|
|
// MDF: why?
|
|
//if(!mRemoveWetEdges)
|
|
//{
|
|
// setGlobalBounds();
|
|
//}
|
|
addToScene();
|
|
|
|
return( true );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::onRemove()
|
|
{
|
|
// clear static texture handles
|
|
for( int i=0; i<WC_NUM_SUBMERGE_TEX; i++ )
|
|
mSubmergeTexture[i] = NULL;
|
|
|
|
removeFromScene();
|
|
Parent::onRemove();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::onSceneAdd( SceneGraph* pGraph )
|
|
{
|
|
if( Parent::onSceneAdd(pGraph) )
|
|
{
|
|
// Attempt to get the terrain.
|
|
if( (mpTerrain == NULL) && (mContainer != NULL) )
|
|
{
|
|
mContainer->findObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, this );
|
|
if( mpTerrain )
|
|
{
|
|
mFluid.SetTerrainData( mpTerrain->heightMap );
|
|
|
|
if (isClientObject())
|
|
CalculateDepthMaps();
|
|
}
|
|
}
|
|
|
|
return( true );
|
|
}
|
|
return( false );
|
|
}
|
|
|
|
//==============================================================================
|
|
// The incoming matrix transforms the water into the WORLD. This includes any
|
|
// offset in the terrain, so the translation can be negative. We need to get
|
|
// the translation to be expressed in "terrain square" terms. The terrain
|
|
// offset is always -1024,-1024.
|
|
|
|
void WaterBlock::setTransform( const MatrixF &mat )
|
|
{
|
|
mObjToWorld = mat;
|
|
UpdateFluidRegion();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::setScale( const VectorF & scale )
|
|
{
|
|
mObjScale = scale;
|
|
UpdateFluidRegion();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::prepRenderImage( SceneState* state,
|
|
const U32 stateKey,
|
|
const U32,
|
|
const bool )
|
|
{
|
|
// Attempt to get the terrain.
|
|
if( (mpTerrain == NULL) && (mContainer != NULL) )
|
|
{
|
|
mContainer->findObjects( mWorldBox, (U32)TerrainObjectType, SnagTerrain, this );
|
|
if( mpTerrain )
|
|
{
|
|
mFluid.SetTerrainData( mpTerrain->heightMap );
|
|
|
|
// MM: Calculate Depth Maps.
|
|
if (isClientObject())
|
|
CalculateDepthMaps();
|
|
}
|
|
}
|
|
|
|
if (isLastState(state, stateKey))
|
|
return false;
|
|
setLastState(state, stateKey);
|
|
|
|
// This should be sufficient for most objects that don't manage zones, and
|
|
// don't need to return a specialized RenderImage...
|
|
if (state->isObjectRendered(this))
|
|
{
|
|
SceneRenderImage* image = new SceneRenderImage;
|
|
image->obj = this;
|
|
image->isTranslucent = true;
|
|
|
|
image->sortType = SceneRenderImage::Plane;
|
|
image->plane = PlaneF(0, 0, 1, -mSurfaceZ);
|
|
image->poly[0] = Point3F(mObjBox.min.x, mObjBox.min.y, 1);
|
|
image->poly[1] = Point3F(mObjBox.min.x, mObjBox.max.y, 1);
|
|
image->poly[2] = Point3F(mObjBox.max.x, mObjBox.max.y, 1);
|
|
image->poly[3] = Point3F(mObjBox.max.x, mObjBox.min.y, 1);
|
|
|
|
for (U32 i = 0; i < 4; i++)
|
|
{
|
|
image->poly[i].convolve(mObjScale);
|
|
getTransform().mulP(image->poly[i]);
|
|
}
|
|
|
|
// Calc the area of this poly
|
|
Point3F intermed;
|
|
mCross(image->poly[2] - image->poly[0], image->poly[3] - image->poly[1], &intermed);
|
|
image->polyArea = intermed.len() * 0.5;
|
|
|
|
state->insertRenderImage(image);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::renderObject( SceneState* state, SceneRenderImage* )
|
|
{
|
|
PROFILE_START(WaterBlock_render);
|
|
|
|
AssertFatal( dglIsInCanonicalState(),
|
|
"Error, GL not in canonical state on entry" );
|
|
|
|
RectI viewport;
|
|
Point3F Eye; // Camera in water space.
|
|
bool CameraSubmergedFlag = false;
|
|
|
|
dglGetViewport ( &viewport );
|
|
glMatrixMode ( GL_PROJECTION );
|
|
glPushMatrix ();
|
|
state->setupObjectProjection( this );
|
|
|
|
/****
|
|
// Debug assist.
|
|
// Render a wire outline around the base of the water block.
|
|
if( 0 )
|
|
{
|
|
glMatrixMode ( GL_MODELVIEW );
|
|
glPushMatrix ();
|
|
dglMultMatrix ( &mObjToWorld );
|
|
|
|
F32 X0 = 0;
|
|
F32 Y0 = 0;
|
|
F32 X1 = mObjScale.x;
|
|
F32 Y1 = mObjScale.y;
|
|
F32 Z = 0;
|
|
|
|
glDisable ( GL_TEXTURE_2D );
|
|
glDisable ( GL_DEPTH_TEST );
|
|
glEnable ( GL_BLEND );
|
|
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE );
|
|
glColor4f ( 0.6f, 0.6f, 0.0f, 0.5f );
|
|
glBegin ( GL_QUADS );
|
|
|
|
glVertex3f ( X0, Y0, Z );
|
|
glVertex3f ( X1, Y0, Z );
|
|
glVertex3f ( X1, Y1, Z );
|
|
glVertex3f ( X0, Y1, Z );
|
|
|
|
glEnd ();
|
|
glEnable ( GL_DEPTH_TEST );
|
|
glColor4f ( 0.8f, 0.8f, 0.8f, 0.8f );
|
|
glBegin ( GL_QUADS );
|
|
|
|
glVertex3f ( X0, Y0, Z );
|
|
glVertex3f ( X1, Y0, Z );
|
|
glVertex3f ( X1, Y1, Z );
|
|
glVertex3f ( X0, Y1, Z );
|
|
|
|
glEnd ();
|
|
glPolygonMode ( GL_FRONT_AND_BACK, GL_FILL );
|
|
|
|
glPopMatrix ();
|
|
glMatrixMode ( GL_MODELVIEW );
|
|
}
|
|
****/
|
|
|
|
// Handle the fluid.
|
|
{
|
|
// The water block lives in terrain space which is -1024,-1024.
|
|
// To get into world space, we just add 1024,1024.
|
|
// The fluid lives in world space.
|
|
|
|
F32 halfTerrainSize = mTerrainHalfSize;
|
|
|
|
Point3F W2Lv( halfTerrainSize, halfTerrainSize, 0.0f ); // World to Local vector
|
|
Point3F L2Wv( -halfTerrainSize, -halfTerrainSize, 0.0f ); // Local to World vector
|
|
MatrixF L2Wm; // Local to World matrix
|
|
|
|
L2Wm.identity();
|
|
L2Wm.setPosition( L2Wv );
|
|
|
|
glMatrixMode ( GL_MODELVIEW );
|
|
glPushMatrix ();
|
|
dglMultMatrix ( &L2Wm );
|
|
|
|
|
|
// We need the eye in water space.
|
|
{
|
|
Eye = state->getCameraPosition() + W2Lv;
|
|
mFluid.SetEyePosition( Eye.x, Eye.y, Eye.z );
|
|
}
|
|
|
|
// We need the frustrum in water space.
|
|
{
|
|
MatrixF L2Cm;
|
|
dglGetModelview( &L2Cm );
|
|
L2Cm.inverse();
|
|
|
|
Point3F Dummy;
|
|
Dummy = L2Cm.getPosition();
|
|
|
|
F64 frustumParam[6];
|
|
dglGetFrustum(&frustumParam[0], &frustumParam[1],
|
|
&frustumParam[2], &frustumParam[3],
|
|
&frustumParam[4], &frustumParam[5]);
|
|
|
|
sgComputeOSFrustumPlanes(frustumParam,
|
|
L2Cm,
|
|
Dummy,
|
|
mClipPlane[1],
|
|
mClipPlane[2],
|
|
mClipPlane[3],
|
|
mClipPlane[4],
|
|
mClipPlane[5]);
|
|
|
|
// near plane is needed as well...
|
|
PlaneF p(0, 1, 0, -frustumParam[4]);
|
|
mTransformPlane(L2Cm, Point3F(1,1,1), p, &mClipPlane[0]);
|
|
mFluid.SetFrustrumPlanes( (F32*)mClipPlane );
|
|
}
|
|
|
|
// Fog stuff.
|
|
{
|
|
pSceneState = state;
|
|
ColorF FogColor = state->getFogColor();
|
|
mFluid.SetFogParameters( FogColor.red,
|
|
FogColor.green,
|
|
FogColor.blue,
|
|
state->getVisibleDistance() );
|
|
}
|
|
|
|
// And RENDER!
|
|
{
|
|
mFluid.Render( CameraSubmergedFlag );
|
|
}
|
|
|
|
// Clean up.
|
|
glPopMatrix ();
|
|
glMatrixMode ( GL_MODELVIEW );
|
|
}
|
|
|
|
//
|
|
// And now the closing ceremonies...
|
|
//
|
|
glMatrixMode ( GL_PROJECTION );
|
|
glPopMatrix ();
|
|
glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
|
|
dglSetViewport ( viewport );
|
|
|
|
//
|
|
// Oh yes. We have to set some state information for the scene...
|
|
//
|
|
|
|
if( CameraSubmergedFlag )
|
|
{
|
|
mCameraSubmerged = true;
|
|
mSubmergedType = mLiquidType;
|
|
}
|
|
|
|
AssertFatal( dglIsInCanonicalState(),
|
|
"Error, GL not in canonical state on exit" );
|
|
|
|
PROFILE_END();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::inspectPostApply()
|
|
{
|
|
// MM: Flag Editor Applied.
|
|
mEditorApplied = true;
|
|
|
|
resetWorldBox();
|
|
setMaskBits(1);
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
static EnumTable::Enums gLiquidTypeEnums[] =
|
|
{
|
|
{ WaterBlock::eWater, "Water" },
|
|
{ WaterBlock::eOceanWater, "OceanWater" },
|
|
{ WaterBlock::eRiverWater, "RiverWater" },
|
|
{ WaterBlock::eStagnantWater, "StagnantWater" },
|
|
{ WaterBlock::eLava, "Lava" },
|
|
{ WaterBlock::eHotLava, "HotLava" },
|
|
{ WaterBlock::eCrustyLava, "CrustyLava" },
|
|
{ WaterBlock::eQuicksand, "Quicksand" }
|
|
};
|
|
static EnumTable gLiquidTypeTable( 8, gLiquidTypeEnums );
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void WaterBlock::initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addGroup( "Debugging" );
|
|
addField( "UseDepthMask", TypeBool, Offset( mUseDepthMap, WaterBlock ) );
|
|
endGroup( "Debugging" );
|
|
|
|
addGroup("Media");
|
|
addField( "surfaceTexture", TypeFilename, Offset( mSurfaceName, WaterBlock ) );
|
|
addField( "ShoreTexture", TypeFilename, Offset( mShoreName, WaterBlock ) );
|
|
addField( "envMapOverTexture",TypeFilename, Offset( mEnvMapOverName, WaterBlock ) );
|
|
addField( "envMapUnderTexture",TypeFilename, Offset( mEnvMapUnderName, WaterBlock ) );
|
|
addField( "submergeTexture", TypeFilename, Offset( mSubmergeName, WaterBlock ), WC_NUM_SUBMERGE_TEX );
|
|
addField( "specularMaskTex", TypeFilename, Offset( mSpecMaskName, WaterBlock ) );
|
|
endGroup("Media");
|
|
|
|
addGroup("Fluid");
|
|
addField( "liquidType", TypeEnum, Offset( mLiquidType, WaterBlock ), 1, &gLiquidTypeTable );
|
|
addField( "density", TypeF32, Offset( mDensity, WaterBlock ) );
|
|
addField( "viscosity", TypeF32, Offset( mViscosity, WaterBlock ) );
|
|
endGroup("Fluid");
|
|
|
|
addGroup("Surface");
|
|
addField( "waveMagnitude", TypeF32, Offset( mWaveMagnitude, WaterBlock ) );
|
|
addField( "surfaceOpacity", TypeF32, Offset( mSurfaceOpacity, WaterBlock ) );
|
|
addField( "envMapIntensity", TypeF32, Offset( mEnvMapIntensity, WaterBlock ) );
|
|
addField( "TessSurface", TypeF32, Offset( mTessellationSurface, WaterBlock ) );
|
|
addField( "TessShore", TypeF32, Offset( mTessellationShore,WaterBlock ) );
|
|
addField( "SurfaceParallax", TypeF32, Offset( mSurfaceParallax, WaterBlock ) );
|
|
endGroup("Surface");
|
|
|
|
addGroup("Movement");
|
|
addField( "FlowAngle", TypeF32, Offset( mFlowAngle, WaterBlock ) );
|
|
addField( "FlowRate", TypeF32, Offset( mFlowRate, WaterBlock ) );
|
|
addField( "DistortGridScale", TypeF32, Offset( mDistortGridScale, WaterBlock ) );
|
|
addField( "DistortMag", TypeF32, Offset( mDistortMagnitude, WaterBlock ) );
|
|
addField( "DistortTime", TypeF32, Offset( mDistortTime, WaterBlock ) );
|
|
endGroup("Movement");
|
|
|
|
addGroup("Depth Fx");
|
|
addField( "ShoreDepth", TypeF32, Offset( mShoreDepth, WaterBlock ) );
|
|
addField( "DepthGradient", TypeF32, Offset( mDepthGradient, WaterBlock ) );
|
|
addField( "MinAlpha", TypeF32, Offset( mMinAlpha, WaterBlock ) );
|
|
addField( "MaxAlpha", TypeF32, Offset( mMaxAlpha, WaterBlock ) );
|
|
endGroup("Depth Fx");
|
|
|
|
addGroup("Misc");
|
|
addField( "tile", TypeBool, Offset(mTile, WaterBlock));
|
|
addField( "audioEnvironment", TypeAudioEnvironmentPtr, Offset( mAudioEnvironment, WaterBlock ) );
|
|
addField( "removeWetEdges", TypeBool, Offset( mRemoveWetEdges, WaterBlock ) );
|
|
addField( "specularColor", TypeColorF, Offset( mSpecColor, WaterBlock ) );
|
|
addField( "specularPower", TypeF32, Offset( mSpecPower, WaterBlock ) );
|
|
endGroup("Misc");
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::toggleWireFrame()
|
|
{
|
|
mFluid.m_ShowWire = !(mFluid.m_ShowWire);
|
|
if( mFluid.m_ShowWire )
|
|
Con::printf( "WaterBlock wire frame ENABLED" );
|
|
else
|
|
Con::printf( "WaterBlock wire frame DISABLED" );
|
|
}
|
|
|
|
ConsoleMethod( WaterBlock, toggleWireFrame, void, 2, 2, "()")
|
|
{
|
|
object->toggleWireFrame();
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
U32 WaterBlock::packUpdate( NetConnection* c, U32 mask, BitStream* stream )
|
|
{
|
|
U32 retMask = Parent::packUpdate( c, mask, stream );
|
|
|
|
// No masking in here now.
|
|
// There's not too much data, and it doesn't change during normal game play.
|
|
|
|
stream->writeFlag(mTile);
|
|
|
|
stream->writeAffineTransform( mObjToWorld );
|
|
mathWrite( *stream, mObjScale );
|
|
|
|
stream->writeString(mSurfaceName);
|
|
stream->writeString(mSpecMaskName);
|
|
stream->writeString(mEnvMapOverName);
|
|
stream->writeString(mEnvMapUnderName);
|
|
stream->writeString(mShoreName);
|
|
|
|
for( int i=0; i<WC_NUM_SUBMERGE_TEX; i++ )
|
|
{
|
|
stream->writeString( mSubmergeName[i] );
|
|
}
|
|
|
|
stream->write( (S32)mLiquidType );
|
|
stream->write( mDensity );
|
|
stream->write( mViscosity );
|
|
stream->write( mWaveMagnitude );
|
|
stream->write( mSurfaceOpacity );
|
|
stream->write( mEnvMapIntensity );
|
|
stream->write( mRemoveWetEdges );
|
|
|
|
// audio environment:
|
|
if(stream->writeFlag(mAudioEnvironment))
|
|
stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
|
|
// MM: Write Depth-Map Controls.
|
|
stream->write( mUseDepthMap );
|
|
stream->write( mShoreDepth );
|
|
stream->write( mMinAlpha );
|
|
stream->write( mMaxAlpha );
|
|
stream->write( mDepthGradient );
|
|
stream->write( mTessellationSurface );
|
|
stream->write( mTessellationShore );
|
|
stream->write( mSurfaceParallax );
|
|
stream->write( mFlowAngle );
|
|
stream->write( mFlowRate );
|
|
stream->write( mDistortGridScale );
|
|
stream->write( mDistortMagnitude );
|
|
stream->write( mDistortTime );
|
|
|
|
|
|
stream->write( mSpecColor );
|
|
stream->write( mSpecPower );
|
|
|
|
stream->writeFlag(mEditorApplied);
|
|
mEditorApplied = false;
|
|
|
|
return( retMask );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void WaterBlock::unpackUpdate( NetConnection* c, BitStream* stream )
|
|
{
|
|
Parent::unpackUpdate( c, stream );
|
|
|
|
U32 LiquidType;
|
|
|
|
// No masking in here now.
|
|
// There's not too much data, and it doesn't change during normal game play.
|
|
|
|
mTile = stream->readFlag();
|
|
|
|
stream->readAffineTransform( &mObjToWorld );
|
|
mathRead( *stream, &mObjScale );
|
|
|
|
mSurfaceName = stream->readSTString();
|
|
mSpecMaskName = stream->readSTString();
|
|
mEnvMapOverName = stream->readSTString();
|
|
mEnvMapUnderName = stream->readSTString();
|
|
mShoreName = stream->readSTString();
|
|
|
|
for( int i=0; i<WC_NUM_SUBMERGE_TEX; i++ )
|
|
{
|
|
mSubmergeName[i] = stream->readSTString();
|
|
}
|
|
|
|
mSurfaceTexture = TextureHandle( mSurfaceName, MeshTexture );
|
|
mSpecMaskTex = TextureHandle( mSpecMaskName, MeshTexture );
|
|
mEnvMapOverTexture = TextureHandle( mEnvMapOverName, MeshTexture );
|
|
mEnvMapUnderTexture = TextureHandle( mEnvMapUnderName, MeshTexture );
|
|
mShoreTexture = TextureHandle( mShoreName, MeshTexture );
|
|
|
|
mFluid.SetTextures( mSurfaceTexture,
|
|
mEnvMapOverTexture,
|
|
mEnvMapUnderTexture,
|
|
mShoreTexture,
|
|
mDepthTexture,
|
|
mShoreDepthTexture,
|
|
mSpecMaskTex);
|
|
|
|
for( int i=0; i<WC_NUM_SUBMERGE_TEX; i++ )
|
|
{
|
|
if( mSubmergeName[i] && mSubmergeName[i][0] )
|
|
{
|
|
mLocalSubmergeTexture[i] = TextureHandle( mSubmergeName[i], MeshTexture );
|
|
mSubmergeTexture[i] = mLocalSubmergeTexture[i];
|
|
}
|
|
}
|
|
|
|
stream->read( &LiquidType );
|
|
stream->read( &mDensity );
|
|
stream->read( &mViscosity );
|
|
stream->read( &mWaveMagnitude );
|
|
stream->read( &mSurfaceOpacity );
|
|
stream->read( &mEnvMapIntensity );
|
|
stream->read( &mRemoveWetEdges );
|
|
|
|
// audio environment:
|
|
if(stream->readFlag())
|
|
{
|
|
U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
mAudioEnvironment = dynamic_cast<AudioEnvironment*>(Sim::findObject(profileId));
|
|
}
|
|
else
|
|
mAudioEnvironment = 0;
|
|
|
|
mLiquidType = (EWaterType)LiquidType;
|
|
|
|
stream->read( &mUseDepthMap );
|
|
stream->read( &mShoreDepth );
|
|
stream->read( &mMinAlpha );
|
|
stream->read( &mMaxAlpha );
|
|
stream->read( &mDepthGradient );
|
|
stream->read( &mTessellationSurface );
|
|
stream->read( &mTessellationShore );
|
|
stream->read( &mSurfaceParallax );
|
|
stream->read( &mFlowAngle );
|
|
stream->read( &mFlowRate );
|
|
stream->read( &mDistortGridScale );
|
|
stream->read( &mDistortMagnitude );
|
|
stream->read( &mDistortTime );
|
|
stream->read( &mSpecColor );
|
|
stream->read( &mSpecPower );
|
|
|
|
if (stream->readFlag())
|
|
CalculateDepthMaps();
|
|
|
|
// Keep this *after* depth-map reads.
|
|
UpdateFluidRegion();
|
|
|
|
if( !isProperlyAdded() )
|
|
return;
|
|
|
|
resetWorldBox();
|
|
}
|
|
|
|
//==============================================================================
|
|
// This method can take a point in world space, or water block space. The default
|
|
// assumes pos is in world space, and therefore must transform it to waterblock
|
|
// space.
|
|
|
|
bool WaterBlock::isPointSubmerged(const Point3F &pos, bool worldSpace) const
|
|
{
|
|
return( isPointSubmergedSimple( pos, worldSpace ) );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::isPointSubmergedSimple(const Point3F &pos, bool worldSpace) const
|
|
{
|
|
Point3F Pos = pos;
|
|
|
|
if( Pos.z > mSurfaceZ )
|
|
return( false );
|
|
|
|
if( worldSpace )
|
|
{
|
|
Pos.x += F32(mTerrainHalfSize);
|
|
Pos.y += F32(mTerrainHalfSize);
|
|
}
|
|
|
|
return( mFluid.IsFluidAtXY( Pos.x, Pos.y ) );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::castRay( const Point3F& start, const Point3F& end, RayInfo* info )
|
|
{
|
|
F32 t, x, y, X, Y;
|
|
Point3F Pos;
|
|
|
|
//
|
|
// Looks like the incoming points are in parametric object space. Great.
|
|
//
|
|
|
|
// The water surface is 1.0. Bail if the ray does not cross the surface.
|
|
|
|
if( (start.z > 1.0f) && (end.z > 1.0f) ) return( false );
|
|
if( (start.z < 1.0f) && (end.z < 1.0f) ) return( false );
|
|
|
|
// The ray crosses the surface plane. Find out where.
|
|
|
|
t = (start.z - 1.0f) / (start.z - end.z);
|
|
x = start.x + (end.x - start.x) * t;
|
|
y = start.y + (end.y - start.y) * t;
|
|
|
|
Pos = mObjToWorld.getPosition();
|
|
|
|
X = (x * mObjScale.x) + Pos.x + F32(mTerrainHalfSize);
|
|
Y = (y * mObjScale.y) + Pos.y + F32(mTerrainHalfSize);
|
|
|
|
if( mFluid.IsFluidAtXY( X, Y ) )
|
|
{
|
|
info->t = t;
|
|
info->point.x = x;
|
|
info->point.y = y;
|
|
info->point.z = 1.0f;
|
|
info->normal.x = 0.0f;
|
|
info->normal.y = 0.0f;
|
|
info->normal.z = 1.0f;
|
|
info->object = this;
|
|
info->material = 0;
|
|
return( true );
|
|
}
|
|
|
|
// Hmm. Guess we missed!
|
|
return( false );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::isWater( U32 liquidType )
|
|
{
|
|
EWaterType wType = EWaterType( liquidType );
|
|
return( wType == eWater ||
|
|
wType == eOceanWater ||
|
|
wType == eRiverWater ||
|
|
wType == eStagnantWater );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::isLava( U32 liquidType )
|
|
{
|
|
EWaterType wType = EWaterType( liquidType );
|
|
return( wType == eLava ||
|
|
wType == eHotLava ||
|
|
wType == eCrustyLava );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
bool WaterBlock::isQuicksand( U32 liquidType )
|
|
{
|
|
EWaterType wType = EWaterType( liquidType );
|
|
return( wType == eQuicksand );
|
|
}
|
|
|
|
//==============================================================================
|