tge/engine/terrain/fluidSupport.cc
2017-04-17 06:17:10 -06:00

679 lines
21 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "terrain/fluid.h"
#include "dgl/dgl.h"
#include "console/simBase.h"
//==============================================================================
// VARIABLES
//==============================================================================
s32 fluid::m_Instances = 0;
s32 fluid::m_MaskOffset[6] = { 0, 1, 2, 4, 12, 44 };
//==============================================================================
// FUNCTIONS
//==============================================================================
fluid::fluid( void )
{
m_Instances += 1;
// Fill out fields with a stable, if useless, state.
m_SquareX0 = 0;
m_SquareY0 = 0;
m_SquaresInX = 4;
m_SquaresInY = 4;
m_BlocksInX = 1;
m_BlocksInY = 1;
m_HighResMode = 1;
m_RemoveWetEdges = 0;
m_SurfaceZ = 0.0f;
m_WaveAmplitude = 0.0f;
m_Opacity = 0.0f;
m_EnvMapIntensity = 1.0f;
m_BaseSeconds = SECONDS;
m_pTerrain = NULL;
// Set values on the debug flags.
m_ShowWire = 0;
m_ShowBlocks = 0;
m_ShowNodes = 0;
m_ShowBaseA = 1;
m_ShowBaseB = 1;
m_ShowLightMap = 1;
m_ShowEnvMap = 1;
m_ShowFog = 1;
m_TerrainSize = 2048;
m_TerrainBlockSize = 8;
m_TerrainBlockShift = 3;
}
//==============================================================================
fluid::~fluid()
{
m_Instances -= 1;
if( m_Instances == 0 )
{
ReleaseVertexMemory();
}
}
//==============================================================================
s32 fluid::GetRejectBit( s32 Level, s32 IndexX, s32 IndexY ) const
{
s32 BitNumber = (IndexY << Level) + IndexX;
s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3);
byte Byte = m_RejectMask[ ByteNumber ];
s32 Bit = (Byte >> (BitNumber & 0x07)) & 0x01;
return( Bit );
}
//==============================================================================
s32 fluid::GetAcceptBit( s32 Level, s32 IndexX, s32 IndexY ) const
{
if( Level == 5 )
return( !GetRejectBit( 5, IndexX, IndexY ) );
s32 BitNumber = (IndexY << Level) + IndexX;
s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3);
byte Byte = m_AcceptMask[ ByteNumber ];
s32 Bit = (Byte >> (BitNumber & 0x07)) & 0x01;
return( Bit );
}
//==============================================================================
void fluid::SetRejectBit( s32 Level, s32 IndexX, s32 IndexY, s32 Value )
{
s32 BitNumber = (IndexY << Level) + IndexX;
s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3);
byte Byte = 1 << (BitNumber & 0x07);
if( Value ) m_RejectMask[ ByteNumber ] |= Byte;
else m_RejectMask[ ByteNumber ] &= ~Byte;
}
//==============================================================================
void fluid::SetAcceptBit( s32 Level, s32 IndexX, s32 IndexY, s32 Value )
{
if( Level == 5 )
SetRejectBit( 5, IndexX, IndexY, !Value );
s32 BitNumber = (IndexY << Level) + IndexX;
s32 ByteNumber = m_MaskOffset[Level] + (BitNumber >> 3);
byte Byte = 1 << (BitNumber & 0x07);
if( Value ) m_AcceptMask[ ByteNumber ] |= Byte;
else m_AcceptMask[ ByteNumber ] &= ~Byte;
}
//==============================================================================
void fluid::BuildLowerMasks( void )
{
s32 X, Y;
// Initially, at all non-level-5 mask levels, we want to both accept and
// reject everything. Then, we'll go back and correct it all.
MEMSET( m_AcceptMask, 0xFF, 1+1+2+8+32 );
MEMSET( m_RejectMask, 0xFF, 1+1+2+8+32 );
// Now, for each entry in the level 5 mask, push its implications down
// through all the other levels.
for( Y = 0; Y < 32; Y++ )
for( X = 0; X < 32; X++ )
{
if( GetRejectBit( 5, X, Y ) )
{
// The block is set for reject.
// We cannot accept it on the lower levels.
SetAcceptBit( 4, X>>1, Y>>1, 0 );
SetAcceptBit( 3, X>>2, Y>>2, 0 );
SetAcceptBit( 2, X>>3, Y>>3, 0 );
SetAcceptBit( 1, X>>4, Y>>4, 0 );
SetAcceptBit( 0, X>>5, Y>>5, 0 );
}
else
{
// The block is set for accept.
// We cannot reject it on the lower levels.
SetRejectBit( 4, X>>1, Y>>1, 0 );
SetRejectBit( 3, X>>2, Y>>2, 0 );
SetRejectBit( 2, X>>3, Y>>3, 0 );
SetRejectBit( 1, X>>4, Y>>4, 0 );
SetRejectBit( 0, X>>5, Y>>5, 0 );
}
}
}
//==============================================================================
struct fill_segment
{
s32 Y;
s32 X0, X1;
s32 DY; // +1 or -1
};
#define STACK_SIZE 50
//------------------------------------------------------------------------------
#define PUSH(y,x0,x1,dy) \
{ \
if( ((y+(dy)) >= 0) && ((y+(dy)) < SizeY) ) \
{ \
if( Count < STACK_SIZE ) \
{ \
Stack[Count].Y = y; \
Stack[Count].X0 = x0; \
Stack[Count].X1 = x1; \
Stack[Count].DY = dy; \
Count++; \
} \
else \
{ \
FloodFill( pGrid, x0, y, SizeX, SizeY ); \
} \
} \
}
//------------------------------------------------------------------------------
#define POP(y,x0,x1,dy) \
{ \
Count--; \
Y = Stack[Count].Y + Stack[Count].DY; \
X0 = Stack[Count].X0; \
X1 = Stack[Count].X1; \
DY = Stack[Count].DY; \
}
//------------------------------------------------------------------------------
void fluid::FloodFill( u8* pGrid, s32 x, s32 y, s32 SizeX, s32 SizeY )
{
fill_segment Stack[ STACK_SIZE ];
s32 Count = 0;
s32 X, Y, X0, X1, DY, Left;
u8* p;
if( !pGrid[ (y*SizeX) + x ] )
return;
PUSH( y, x, x, 1 ); // Needed in a few cases.
PUSH( y+1, x, x, -1 ); // Primary seed point. Popped first.
while( Count > 0 )
{
POP( Y, X0, X1, DY );
// A span in y=(Y-DY) for X0<=x<=X1 was previously filled. Now consider
// adjacent entries in y=Y.
// Clear going towards decreasing X.
X = X0;
p = &pGrid[ (Y*SizeX) + X ];
while( (X >= 0) && *p )
{
*p = 0;
X--;
p--;
}
if( X >= X0 )
goto Skip;
Left = X + 1;
if( Left < X0 )
PUSH( Y, Left, X0-1, -DY )
X = X0 + 1;
do
{
// Clear going towards increasing X.
p = &pGrid[ (Y*SizeX) + X ];
while( (X < SizeX) && *p )
{
*p = 0;
X++;
p++;
}
PUSH( Y, Left, X-1, DY );
if( X > X1+1 )
PUSH( Y, X1+1, X-1, -DY );
Skip: X++;
p = &pGrid[ (Y*SizeX) + X ];
while( (X <= X1) && !(*p) )
{
X++;
p++;
}
Left = X;
}
while( X <= X1 );
}
}
//==============================================================================
void fluid::RebuildMasks( void )
{
u8* pGrid;
u8* pG; // Traveling grid pointer
s32 GridSize;
s32 X, Y;
s32 x, y;
s32 i; // Index
s32 SquaresPerBlock = m_HighResMode ? 4 : 8;
s32 ShiftPerBlock = m_HighResMode ? 2 : 3;
//
// We need a grid to classify all terrain data points which are within the
// fluid area. We will use this grid to reject underground blocks, reject
// "wet" edges if requested, and to dry fill where requested.
//
GridSize = (m_SquaresInX+1) * (m_SquaresInY+1);
pGrid = (u8*)MALLOC( GridSize );
// Default to allow all waterblocks in case we don't have a terrain object
dMemset(pGrid, 1, GridSize);
// Classify each point as above or below ground.
if( m_pTerrain )
{
u16 FluidLevel = (u16)((m_SurfaceZ + (m_WaveAmplitude/2.0f)) * 32.0f);
pG = pGrid;
for( Y = 0; Y < m_SquaresInY+1; Y++ )
for( X = 0; X < m_SquaresInX+1; X++ )
{
i = (((m_SquareY0+Y) & 255) << 8) + ((m_SquareX0+X) & 255);
*pG = (u8)(FluidLevel > m_pTerrain[i]);
pG++;
}
}
// If requested, "dry up" all edges which "protrude" into the air.
if( m_RemoveWetEdges && m_pTerrain )
{
for( X = 0; X < m_SquaresInX+1; X++ )
{
FloodFill( pGrid, X, 0 , m_SquaresInX+1, m_SquaresInY+1 );
FloodFill( pGrid, X, m_SquaresInY, m_SquaresInX+1, m_SquaresInY+1 );
}
for( Y = 0; Y < m_SquaresInY+1; Y++ )
{
FloodFill( pGrid, 0 , Y, m_SquaresInX+1, m_SquaresInY+1 );
FloodFill( pGrid, m_SquaresInX, Y, m_SquaresInX+1, m_SquaresInY+1 );
}
}
// Time to build the masks. First, reject everything! We will work on the
// level 5 reject mask. (Level 5 is the most detailed mask, and there is no
// accept mask at that level.)
MEMSET( m_RejectMask + m_MaskOffset[5], 0xFF, 128 );
// Any block which as useful points left in the grid is to be kept.
for( Y = 0; Y < m_BlocksInY; Y++ )
for( X = 0; X < m_BlocksInX; X++ )
{
s32 Accept = 0;
// If ANY point in the block is acceptable, then accept the whole block.
for( y = 0; y <= SquaresPerBlock; y++ )
for( x = 0; x <= SquaresPerBlock; x++ )
{
s32 GridX = (X << ShiftPerBlock) + x;
s32 GridY = (Y << ShiftPerBlock) + y;
i = (GridY * (m_SquaresInX+1)) + GridX;
if( pGrid[i] )
{
Accept = 1;
goto BailOut;
}
}
BailOut:
if( Accept )
SetRejectBit( 5, X, Y, 0 );
}
FREE( pGrid );
BuildLowerMasks();
}
//==============================================================================
S32 getPower(S32 x)
{
// Returns 2^n (the highest bit).
S32 i = 0;
if (x)
do
i++;
while (x >>= 1);
return i;
}
void fluid::SetInfo( f32& X0,
f32& Y0,
f32& SizeX,
f32& SizeY,
f32 SurfaceZ,
f32 WaveAmplitude,
f32& Opacity,
f32& EnvMapIntensity,
s32 RemoveWetEdges,
bool UseDepthMap,
f32 TessellationSurface,
f32 TessellationShore,
f32 SurfaceParallax,
f32 FlowAngle,
f32 FlowRate,
f32 mDistortGridScale,
f32 mDistortMagnitude,
f32 mDistortTime,
ColorF SpecColor,
F32 SpecPower,
bool tiling,
u32 terrainSize,
u32 terrainBlockSize) // MM: Added Various Parameters.
{
m_TerrainSize = terrainSize;
m_TerrainBlockSize = terrainBlockSize;
m_TerrainBlockShift = getPower(terrainBlockSize-1);
m_SpecColor = SpecColor;
m_SpecPower = SpecPower;
// MM: Calculate Depth-map Texel X/Y.
m_DepthTexelX = 1.0f / SizeX;
m_DepthTexelY = 1.0f / SizeY;
// MM: Added Depth-Map Toggle.
m_UseDepthMap = UseDepthMap;
// MM: Tessellations.
m_TessellationSurface = TessellationSurface;
m_TessellationShore = TessellationShore;
// MM: Surface Parallax.
m_SurfaceParallax = SurfaceParallax;
// MM: Flow Control.
m_FlowAngle = FlowAngle;
m_FlowRate = FlowRate;
m_FlowMagnitudeS =
m_FlowMagnitudeT = 0.0f;
// MM: Surface Disturbance. RemoveMe!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
m_DistortGridScale = mDistortGridScale;
m_DistortMagnitude = mDistortMagnitude;
m_DistortTime = mDistortTime;
// MM: Put in neater constraints.
m_EnvMapIntensity = mClampF(EnvMapIntensity, 0.0f, 1.0f);
// MM: Removed Section.
/*
// Constrain the range of parameters.
if( Opacity > 1.0f ) Opacity = 1.0f;
if( Opacity < 0.0f ) Opacity = 0.0f;
if( EnvMapIntensity > 1.0f ) EnvMapIntensity = 1.0f;
if( EnvMapIntensity < 0.0f ) EnvMapIntensity = 0.0f;
*/
// Get the easy stuff first.
m_SurfaceZ = SurfaceZ;
m_WaveAmplitude = WaveAmplitude;
m_RemoveWetEdges = RemoveWetEdges;
m_Opacity = mClampF(Opacity,0.0f,1.0f); // MM: Put in neater constraints.
m_EnvMapIntensity = EnvMapIntensity;
m_WaveFactor = m_WaveAmplitude * 0.25f;
// Place the "min" corner.
m_SquareX0 = (s32)((X0 / F32(m_TerrainBlockSize)) + 0.5f);
m_SquareY0 = (s32)((Y0 / F32(m_TerrainBlockSize)) + 0.5f);
// Constrain the range of values.
if( m_SquareX0 < 0.0f ) m_SquareX0 = 0;
if( m_SquareY0 < 0.0f ) m_SquareY0 = 0;
F32 max = terrainSize - terrainBlockSize;
if( m_SquareX0 > max ) m_SquareX0 = max;
if( m_SquareY0 > max ) m_SquareY0 = max;
// Decide on the size of the block.
m_SquaresInX = (s32)((SizeX / F32(m_TerrainBlockSize)) + 0.5f);
m_SquaresInY = (s32)((SizeY / F32(m_TerrainBlockSize)) + 0.5f);
//
// If the fluid is meant to cover less than 1/4 of a terrain rep, then we
// will enter "High Resolution Mode". The fluid will cover the same area
// as specified, but it will have twice the vertex resolution in each
// direction. So, on "small lakes", we get better memory utilization,
// better terrain fitting, and so on.
//
if( (m_SquaresInX <= 128) && (m_SquaresInY <= 128) )
{
// High Resolution Mode!
m_HighResMode = 1;
// A Block is now 4x4 terrain squares. And the number of squares in
// the fluid must be a multiple of 4 so we get whole blocks.
m_SquaresInX = (m_SquaresInX + 3) & ~0x03;
m_SquaresInY = (m_SquaresInY + 3) & ~0x03;
// Constrain the range of values.
if( m_SquaresInX <= 0 ) m_SquaresInX = 4;
if( m_SquaresInY <= 0 ) m_SquaresInY = 4;
m_BlocksInX = m_SquaresInX >> 2;
m_BlocksInY = m_SquaresInY >> 2;
}
else
{
// Normal resolution.
m_HighResMode = 0;
// A Block is now 8x8 terrain squares. And the number of squares in
// the fluid must be a multiple of 8 so we get whole blocks.
m_SquaresInX = (m_SquaresInX + 7) & ~0x07;
m_SquaresInY = (m_SquaresInY + 7) & ~0x07;
// Constrain the range of values.
if( m_SquaresInX > 256 ) m_SquaresInX = 256;
if( m_SquaresInY > 256 ) m_SquaresInY = 256;
if( m_SquaresInX <= 0 ) m_SquaresInX = 8;
if( m_SquaresInY <= 0 ) m_SquaresInY = 8;
m_BlocksInX = m_SquaresInX >> 3;
m_BlocksInY = m_SquaresInY >> 3;
}
// Set some internal values for later usage.
for(U32 i=0; i<5; i++)
{
if(m_HighResMode)
m_Step[i] = i * m_TerrainBlockSize;
else
m_Step[i] = i * m_TerrainBlockSize * 2;
}
// Set values back into parameters for caller.
X0 = m_SquareX0 * F32(m_TerrainBlockSize);
Y0 = m_SquareY0 * F32(m_TerrainBlockSize);
SizeX = m_SquaresInX * F32(m_TerrainBlockSize);
SizeY = m_SquaresInY * F32(m_TerrainBlockSize);
// Recompute our masks.
RebuildMasks();
}
//==============================================================================
void fluid::SetTerrainData( u16* pTerrainData )
{
m_pTerrain = pTerrainData;
RebuildMasks();
}
//==============================================================================
// Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F
void fluid::SetFrustrumPlanes( f32* pFrustrumPlanes )
{
f32 BackOff = m_WaveAmplitude * 0.5f;
m_Plane[0].A = pFrustrumPlanes[ 0];
m_Plane[0].B = pFrustrumPlanes[ 1];
m_Plane[0].C = pFrustrumPlanes[ 2];
m_Plane[0].D = pFrustrumPlanes[ 3] + BackOff;
m_Plane[1].A = pFrustrumPlanes[ 4];
m_Plane[1].B = pFrustrumPlanes[ 5];
m_Plane[1].C = pFrustrumPlanes[ 6];
m_Plane[1].D = pFrustrumPlanes[ 7] + BackOff;
m_Plane[2].A = pFrustrumPlanes[ 8];
m_Plane[2].B = pFrustrumPlanes[ 9];
m_Plane[2].C = pFrustrumPlanes[10];
m_Plane[2].D = pFrustrumPlanes[11] + BackOff;
m_Plane[3].A = pFrustrumPlanes[12];
m_Plane[3].B = pFrustrumPlanes[13];
m_Plane[3].C = pFrustrumPlanes[14];
m_Plane[3].D = pFrustrumPlanes[15] + BackOff;
m_Plane[4].A = pFrustrumPlanes[16];
m_Plane[4].B = pFrustrumPlanes[17];
m_Plane[4].C = pFrustrumPlanes[18];
m_Plane[4].D = pFrustrumPlanes[19] + BackOff;
m_Plane[5].A = pFrustrumPlanes[20];
m_Plane[5].B = pFrustrumPlanes[21];
m_Plane[5].C = pFrustrumPlanes[22];
m_Plane[5].D = pFrustrumPlanes[23];
}
//==============================================================================
void fluid::SetTextures( TextureHandle Base,
TextureHandle EnvMapOverTexture,
TextureHandle EnvMapUnderTexture,
TextureHandle ShoreTexture,
TextureHandle DepthTexture,
TextureHandle ShoreDepthTexture,
TextureHandle SpecMaskTexture ) // MM: Added Various Textures.
{
m_BaseTexture = Base;
m_EnvMapOverTexture = EnvMapOverTexture; // MM: Added Over/Under Env Texture Support.
m_EnvMapUnderTexture = EnvMapUnderTexture; // MM: Added Over/Under Env Texture Support.
m_ShoreTexture = ShoreTexture; // MM: Added Shore Texture.
m_DepthTexture = DepthTexture; // MM: Added Depth-Map Texture.
m_ShoreDepthTexture = ShoreDepthTexture; // MM: Added Depth-Map Texture.
m_SpecMaskTex = SpecMaskTexture;
}
//==============================================================================
void fluid::SetLightMapTexture( TextureHandle LightMapTexture )
{
m_LightMapTexture = LightMapTexture;
}
//==============================================================================
void fluid::SetFogParameters( f32 R, f32 G, f32 B, f32 VisibleDistance )
{
m_FogColor.R = R;
m_FogColor.G = G;
m_FogColor.B = B;
m_FogColor.A = 1.0f;
m_VisibleDistance = VisibleDistance;
}
//==============================================================================
void fluid::SetFogFn( compute_fog_fn* pFogFn )
{
m_pFogFn = pFogFn;
}
//==============================================================================
s32 fluid::IsFluidAtXY( f32 X, f32 Y ) const
{
s32 x, y;
s32 ShiftPerBlock = m_HighResMode ? 5 : 6;
//
// Convert fluid space (X,Y) to block (x,y). Use the accept mask. Note
// that the masks are anchored at the min point rather than terrain (0,0).
//
// Convert to integer.
F32 scale = 8.0f / F32(m_TerrainBlockSize);
x = (s32)(X * scale);
y = (s32)(Y * scale);
// Compensate for min point offset.
x -= (m_SquareX0 * F32(m_TerrainBlockSize) * scale);
y -= (m_SquareY0 * F32(m_TerrainBlockSize) * scale);
// If we're outside the range and not tiling, ignore it.
if(!mTile)
{
if(x < 0 || x > 2047)
return false;
if(y < 0 || y > 2047)
return false;
}
// We only want points in the range [0,2048).
x &= 2047;
y &= 2047;
// Convert to block coordinate.
x >>= ShiftPerBlock;
y >>= ShiftPerBlock;
// When we are in high res mode, there are "virtually" 64 blocks per terrain
// along a particular axis. But only the first 32 of them are used.
if( x >= 32 ) return( 0 );
if( y >= 32 ) return( 0 );
// Consult mask.
return( GetAcceptBit( 5, x, y ) );
}
//==============================================================================