//----------------------------------------------------------------------------- // 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 ) ); } //==============================================================================