//-----------------------------------------------------------------------------
// 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 "environment/waterPlane.h"

#include "core/util/safeDelete.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "lighting/lightInfo.h"
#include "core/stream/bitStream.h"
#include "math/mathIO.h"
#include "math/mathUtils.h"
#include "console/consoleTypes.h"
#include "gui/3d/guiTSControl.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxTransformSaver.h"
#include "gfx/gfxDebugEvent.h"
#include "gfx/gfxOcclusionQuery.h"
#include "renderInstance/renderPassManager.h"
#include "sim/netConnection.h"
#include "scene/reflectionManager.h"
#include "ts/tsShapeInstance.h"
#include "T3D/gameFunctions.h"
#include "postFx/postEffect.h"
#include "math/util/matrixSet.h"

extern ColorI gCanvasClearColor;

#define BLEND_TEX_SIZE 256
#define V_SHADER_PARAM_OFFSET 50

IMPLEMENT_CO_NETOBJECT_V1(WaterPlane);

ConsoleDocClass( WaterPlane,
   "@brief Represents a large body of water stretching to the horizon in all directions.\n\n"

   "WaterPlane's position is defined only height, the z element of position, "
   "it is infinite in xy and depth. %WaterPlane is designed to represent the "
   "ocean on an island scene and viewed from ground level; other uses may not "
   "be appropriate and a WaterBlock may be used.\n\n"

   "@see WaterObject for inherited functionality.\n\n"

   "Limitations:\n\n"
   
   "Because %WaterPlane cannot be projected exactly to the far-clip distance, "
   "other objects nearing this distance can have noticible artifacts as they "
   "clip through first the %WaterPlane and then the far plane.\n\n"
   
   "To avoid this large objects should be positioned such that they will not line up with "
   "the far-clip from vantage points the player is expected to be. In particular, "
   "your TerrainBlock should be completely contained by the far-clip distance.\n\n"
   
   "Viewing %WaterPlane from a high altitude with a tight far-clip distance "
   "will accentuate this limitation. %WaterPlane is primarily designed to "
   "be viewed from ground level.\n\n"
      
   "@ingroup Water"
);

WaterPlane::WaterPlane()
{
   mGridElementSize = 1.0f;
   mGridSize = 101;
   mGridSizeMinusOne = mGridSize - 1;

   mNetFlags.set(Ghostable | ScopeAlways);

   mVertCount = 0;
   mIndxCount = 0;
   mPrimCount = 0;   
}

WaterPlane::~WaterPlane()
{
}

bool WaterPlane::onAdd()
{
   if ( !Parent::onAdd() )
      return false;
   
   setGlobalBounds();
   resetWorldBox();
   addToScene();

   mWaterFogData.plane.set( 0, 0, 1, -getPosition().z );

   return true;
}

void WaterPlane::onRemove()
{   
   removeFromScene();

   Parent::onRemove();
}

void WaterPlane::initPersistFields()
{
   addGroup( "WaterPlane" );     

      addProtectedField( "gridSize", TypeS32, Offset( mGridSize, WaterPlane ), &protectedSetGridSize, &defaultProtectedGetFn,
		  "Spacing between vertices in the WaterBlock mesh" );

      addProtectedField( "gridElementSize", TypeF32, Offset( mGridElementSize, WaterPlane ), &protectedSetGridElementSize, &defaultProtectedGetFn,
		  "Duplicate of gridElementSize for backwards compatility");

   endGroup( "WaterPlane" );

   Parent::initPersistFields();

   removeField( "rotation" );
   removeField( "scale" );
}

U32 WaterPlane::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);

   stream->write( mGridSize );
   stream->write( mGridElementSize );   

   if ( stream->writeFlag( mask & UpdateMask ) )
   {
      stream->write( getPosition().z );        
   }

   return retMask;
}

void WaterPlane::unpackUpdate(NetConnection* con, BitStream* stream)
{
   Parent::unpackUpdate(con, stream);

   U32 inGridSize;
   stream->read( &inGridSize );
   setGridSize( inGridSize );

   F32 inGridElementSize;
   stream->read( &inGridElementSize );
   setGridElementSize( inGridElementSize );

   if( stream->readFlag() ) // UpdateMask
   {
      F32 posZ;
      stream->read( &posZ );
      Point3F newPos = getPosition();
      newPos.z = posZ;
      setPosition( newPos );
   }  
}

void WaterPlane::setupVBIB( SceneRenderState *state )
{
   const Frustum &frustum = state->getCullingFrustum();
   
   // World-Up vector, assigned as normal for all verts.
   const Point3F worldUp( 0.0f, 0.0f, 1.0f );

   // World-unit size of a grid cell.
   const F32 squareSize = mGridElementSize;

   // Column/Row count.
   // So we don't neet to access class-specific member variables
   // in the code below.
   const U32 gridSize = mGridSize;

   // Number of verts in one column / row
   const U32 gridStride = gridSize + 1;

   // Grid is filled in this order...
   
   // Ex. Grid with gridSize of 2.
   //
   // Letters are cells.
   // Numbers are verts, enumerated in their order within the vert buffer.       
   //
   // 6  7  8   
   // (c) (d)
   // 3  4  5
   // (a) (b)
   // 0  1  2
   //   
   // Note...
   //   Camera would be positioned at vert 4 ( in this particular grid not a constant ).
   //   Positive Y points UP the diagram ( verts 0, 3, 6 ).
   //   Positive X points RIGHT across the diagram ( verts 0, 1, 2 ).

   // Length of a grid row/column divided by two.
   F32 gridSideHalfLen = squareSize * gridSize * 0.5f;

   // Position of the first vertex in the grid.
   // Relative to the camera this is the "Back Left" corner vert.
   const Point3F cornerPosition( -gridSideHalfLen, -gridSideHalfLen, 0.0f );   

   // Number of verts in the grid centered on the camera.
   const U32 gridVertCount = gridStride * gridStride;

   // Number of verts surrounding the grid, projected by the frustum.
   const U32 borderVertCount = gridSize * 4;

   // Number of verts in the front-most row which are raised to the horizon.
   const U32 horizonVertCount = gridStride;

   // Total number of verts. Calculation explained above.
   mVertCount = gridVertCount + borderVertCount + horizonVertCount;

   
   // Fill the vertex buffer...

   mVertBuff.set( GFX, mVertCount, GFXBufferTypeStatic );

   GFXWaterVertex *vertPtr = mVertBuff.lock();

   // Fill verts in the camera centered grid...

   // Temorary storage for calculation of vert position.
   F32 xVal, yVal;

   for ( U32 i = 0; i < gridStride; i++ )
   {
      yVal = cornerPosition.y + (F32)( i * squareSize );

      for ( U32 j = 0; j < gridStride; j++ )
      {
         xVal = cornerPosition.x + (F32)( j * squareSize );

         vertPtr->point.set( xVal, yVal, 0.0f );
         vertPtr->normal = worldUp;
         vertPtr->undulateData.set( xVal, yVal );
         vertPtr->horizonFactor.set( 0, 0, 0, 0 );
         vertPtr++;
      }
   }

   // Fill in 'border' verts, surrounding the grid, projected by the frustum.

   // Ex. Grid with gridSize of 2.
   //
   // Letters in parenthesis are cells.
   // x's are grid-verts ( we have already filled ).
   // Numbers are border verts, enumerated in their order within the vert buffer.       
   // 
   // Lines connecting verts explained in the code below.
   //
   //      Front
   //
   //  L   0------1      2   R
   //  e       x  x  x   |   i
   //  f       (c) (d)   |   g
   //  t   7   x  x  x   3   h
   //      |   (a) (b)       t
   //      |   x  x  x       
   //      6      5------4
   //
   //      Back
   //   
   // As in previous diagram...
   //   Camera would be positioned at vert 4 ( in this particular grid not a constant ).
   //   Positive Y points UP the diagram ( verts 6, 7, 0 ).
   //   Positive X points RIGHT across the diagram ( verts 0, 1, 2 ).


   // Iterator i is looping through the 4 'sides' of the grid.
   // Inner loop ( using iterator j ) will fill in a number of verts   
   // where that count is 'gridSize'.
   //    
   // 
   // Ex. Given the grid with gridSize of 2 diagramed above,
   // Outer loop iterates through: Front, Right, Back, Left
   // Inner loop fills 2 verts per iteration of the outer loop: { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }

   // Grid-space vectors indexed by 'side'.
   // Each vector describes the direction we iterate when
   // filling in verts ( mathematically the tangent ).
   const Point2F sBorderTangentVec [4] = {
      Point2F(  1,  0 ), // Front ( 0 )
      Point2F(  0, -1 ), // Right ( 1 )
      Point2F( -1,  0 ), // Back  ( 2 )
      Point2F(  0,  1 )  // Left  ( 3 )
   };
   

   // Normalized positions indexed by 'side'
   // Defines the 'start' position of each side, eg. the position of the first vert.
   // See Diagram below.
   const Point2F sBorderStartPos [4] = {
      Point2F( -1,  1 ), // Front ( 0 )
      Point2F(  1,  1 ), // Right ( 1 )
      Point2F(  1, -1 ), // Back  ( 2 )
      Point2F( -1, -1 )  // Left  ( 3 )
   };

   // Diagram of Start vert position per Side.
   //
   // Labeling convention for verts is 'As' where A is the first letter of 
   // that side's descriptive name and lower-case s indicates 'start'. 
   // 
   // 
   //
   //          Front
   //          (-1,1)
   //          Fs------o-----Rs(1,1)R
   //          |             |      i
   //          |             |      g
   //          o     (0,0)   o      h
   //          |             |      t
   //          |             |     
   //  L(-1,-1)Ls------o-----Bs
   //  e                     (1,-1)   
   //  f                     Back
   //  t

   // Calculate the world-space dimension of the border-vert-ring...

   // Diagram shows overall layout of the WaterPlane with a gridSize of 1,
   // with all 'quads' enumerated.
   // center-grid ( 1 ), border ( 2, 3, 4, 5 ), and horizon ( 6 ).
   //
   //
   //   x------------x
   //    \      6     \    <- horizon quad is really 'above' the front border
   //      x ---------  x     not in front of it
   //      | \    2   / |
   //      |   x---- x  |
   //      |   |     |  |
   //      |  5|  1  |3 |
   //      |   x --- x  |
   //      | /    4    \|
   //      x------------x


   // WaterPlane renders relative to the camera rotation around z and xy position.
   //
   // That is, it rotates around the z-up axis with the camera such that the
   // camera is always facing towards the front border unless looking straight
   // down or up.
   //
   // Also note that the horizon verts are pulled straight up from the front
   // border verts.
   //
   // Therefore...
   //
   // The front border must be as close to the farclip plane as possible 
   // so distant objects clip through the horizon and  farplane at the same time.
   // 
   // The left and right borders must be pulled outward a distance such
   // that water extends horizontally across the entire viewable area while
   // looking straight forward +y or straight down -z.
   //

   // 
   const F32 farDistScale = 0.99f;

   // 
   F32 farDist = frustum.getFarDist() * farDistScale;
   
   //
   F32 farWidth = (F32)state->getViewport().extent.x * farDist / state->getWorldToScreenScale().x;

   Point2F borderExtents( farWidth * 2.0f, farDist * 2.0f );
   Point2F borderHalfExtents( farWidth, farDist );   

   Point2F borderDir;
   Point2F borderStart;

   for ( U32 i = 0; i < 4; i++ )
   {
      borderDir = sBorderTangentVec[i];
      borderStart = sBorderStartPos[i];

      for ( U32 j = 0; j < gridSize; j++ )
      {
         F32 frac = (F32)j / (F32)gridSize;

         Point2F pos( borderStart * borderHalfExtents );
         pos += borderDir * borderExtents * frac;

         vertPtr->point.set( pos.x, pos.y, 0.0f );
         vertPtr->undulateData.set( pos.x, pos.y );
         vertPtr->horizonFactor.set( 0, 0, 0, 0 );
         vertPtr->normal = worldUp;         
         vertPtr++;
      }
   }

   // Fill in row of horizion verts.
   // Verts are positioned identical to the front border, but will be
   // manipulated in z within the shader.
   //
   // Z position of 50.0f is unimportant unless you want to disable
   // shader manipulation and render in wireframe for debugging.

   for ( U32 i = 0; i < gridStride; i++ )
   {
      F32 frac = (F32)i / (F32)gridSize;
      
      Point2F pos( sBorderStartPos[0] * borderHalfExtents );
      pos += sBorderTangentVec[0] * borderExtents * frac;

      vertPtr->point.set( pos.x, pos.y, 50.0f );
      vertPtr->undulateData.set( pos.x, pos.y );
      vertPtr->horizonFactor.set( 1, 0, 0, 0 );
      vertPtr->normal = worldUp;
      vertPtr++;
   }

   mVertBuff.unlock();


   // Fill in the PrimitiveBuffer...

   // 2 triangles per cell/quad   
   const U32 gridTriCount = gridSize * gridSize * 2;

   // 4 sides, mGridSize quads per side, 2 triangles per quad
   const U32 borderTriCount = 4 * gridSize * 2;

   // 1 quad per gridSize, 2 triangles per quad
   // i.e. an extra row of 'cells' leading the front side of the grid
   const U32 horizonTriCount = gridSize * 2;
   
   mPrimCount = gridTriCount + borderTriCount + horizonTriCount;

   // 3 indices per triangle.
   mIndxCount = mPrimCount * 3; 

   mPrimBuff.set( GFX, mIndxCount, mPrimCount, GFXBufferTypeStatic );
   U16 *idxPtr;
   mPrimBuff.lock(&idxPtr);        

   // Temporaries to hold indices for the corner points of a quad.
   U32 p00, p01, p11, p10;
   U32 offset = 0;

   // Given a single cell of the grid diagramed below,
   // quad indice variables are in this orientation.
   //
   // p01 --- p11
   //  |       |
   //  |       |
   // p00 --- p10
   //
   //   Positive Y points UP the diagram ( p00, p01 ).
   //   Positive X points RIGHT across the diagram ( p00, p10 )
   //

   // i iterates bottom to top "column-wise"
   for ( U32 i = 0; i < mGridSize; i++ )
   {
      // j iterates left to right "row-wise"
      for ( U32 j = 0; j < mGridSize; j++ )
      {
         // where (j,i) is a particular cell.
         p00 = offset;
         p10 = offset + 1;
         p01 = offset + gridStride;
         p11 = offset + 1 + gridStride;

         // Top Left Triangle

         *idxPtr = p00;
         idxPtr++;
         *idxPtr = p01;
         idxPtr++;
         *idxPtr = p11;
         idxPtr++;

         // Bottom Right Triangle

         *idxPtr = p00;
         idxPtr++;
         *idxPtr = p11;
         idxPtr++;
         *idxPtr = p10;
         idxPtr++;

         offset += 1;
      }

      offset += 1;
   }

   // Fill border indices...

   // Given a grid size of 1, 
   // the grid / border verts are in the vertex buffer in this order.
   //
   //
   // 4           5  
   //    2 --- 3
   //    |     |
   //    |     |
   //    0 --- 1
   // 7           6
   //
   //   Positive Y points UP the diagram ( p00, p01 ).
   //   Positive X points RIGHT across the diagram ( p00, p10 )
   //
   //  Note we duplicate the first border vert ( 4 ) since it is also the last
   //  and this makes our loop easier.

   const U32 sBorderStartVert [4] = {
      gridStride * gridSize,              // Index to the Top-Left grid vert.
      gridStride * gridSize + gridSize,   // Index to the Top-Right grid vert.
      gridSize,                           // Index to the Bottom-Right grid vert.
      0,                                  // Index to the Bottom-Left grid vert.
   };

   const S32 sBorderStepSize [4] = {
      // Step size to the next grid vert along the specified side....
      1,             // Top
      -(S32)gridStride,   // Right
      -1,             // Bottom
      (S32)gridStride,    // Left
   };

   const U32 firstBorderVert = gridStride * gridSize + gridStride;
   const U32 lastBorderVert = firstBorderVert + ( borderVertCount - 1 );
   U32 startBorderVert = firstBorderVert;
   U32 startGridVert;
   U32 curStepSize;
   
   
   for ( U32 i = 0; i < 4; i++ )
   {
      startGridVert = sBorderStartVert[i];
      curStepSize = sBorderStepSize[i];
      
      for ( U32 j = 0; j < gridSize; j++ )
      {
         // Each border cell is 1 quad, 2 triangles.

         p00 = startGridVert;
         p10 = startGridVert + curStepSize;
         p01 = startBorderVert;
         p11 = startBorderVert + 1;
         
         if ( p11 > lastBorderVert )
            p11 = firstBorderVert;

         // Top Left Triangle

         *idxPtr = p00;
         idxPtr++;
         *idxPtr = p01;
         idxPtr++;
         *idxPtr = p11;
         idxPtr++;

         // Bottom Right Triangle

         *idxPtr = p00;
         idxPtr++;
         *idxPtr = p11;
         idxPtr++;
         *idxPtr = p10;
         idxPtr++;

         startBorderVert++;
         startGridVert += curStepSize;
      }
   }

   // Fill in 'horizon' triangles.

   U32 curHorizonVert = lastBorderVert + 1;
   U32 curBorderVert = firstBorderVert;

   for ( U32 i = 0; i < gridSize; i++ )
   {
      p00 = curBorderVert;
      p10 = curBorderVert + 1;
      p01 = curHorizonVert;
      p11 = curHorizonVert + 1;
      
      // Top Left Triangle

      *idxPtr = p00;
      idxPtr++;
      *idxPtr = p01;
      idxPtr++;
      *idxPtr = p11;
      idxPtr++;

      // Bottom Right Triangle

      *idxPtr = p00;
      idxPtr++;
      *idxPtr = p11;
      idxPtr++;
      *idxPtr = p10;
      idxPtr++;

      curBorderVert++;
      curHorizonVert++;
   }

   mPrimBuff.unlock();
}

SceneData WaterPlane::setupSceneGraphInfo( SceneRenderState *state )
{
   SceneData sgData;

   sgData.lights[0] = LIGHTMGR->getSpecialLight( LightManager::slSunLightType );

   // fill in water's transform
   sgData.objTrans = &getRenderTransform();

   // fog
   sgData.setFogParams( state->getSceneManager()->getFogData() );

   // misc
   sgData.backBuffTex = REFLECTMGR->getRefractTex();
   sgData.reflectTex = mPlaneReflector.reflectTex;
   sgData.wireframe = GFXDevice::getWireframe() || smWireframe;

   return sgData;
}

void WaterPlane::setShaderParams( SceneRenderState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles)
{
   // Set variables that will be assigned to shader consts within WaterCommon
   // before calling Parent::setShaderParams

   mUndulateMaxDist = mGridElementSize * mGridSizeMinusOne * 0.5f;

   Parent::setShaderParams( state, mat, paramHandles );   

   // Now set the rest of the shader consts that are either unique to this
   // class or that WaterObject leaves to us to handle...    

   MaterialParameters* matParams = mat->getMaterialParameters();

   // set vertex shader constants
   //-----------------------------------   
   matParams->setSafe(paramHandles.mGridElementSizeSC, (F32)mGridElementSize);
   //matParams->setSafe( paramHandles.mReflectTexSizeSC, mReflectTexSize );
   if ( paramHandles.mModelMatSC->isValid() )
      matParams->set(paramHandles.mModelMatSC, getRenderTransform(), GFXSCT_Float4x4);

   // set pixel shader constants
   //-----------------------------------

   ColorF c( mWaterFogData.color );
   matParams->setSafe( paramHandles.mBaseColorSC, c );   
   
   // By default we need to show a true reflection is fullReflect is enabled and
   // we are above water.
   F32 reflect = mPlaneReflector.isEnabled() && !isUnderwater( state->getCameraPosition() );
   
   // If we were occluded the last frame a query was fetched ( not necessarily last frame )
   // and we weren't updated last frame... we don't have a valid texture to show
   // so use the cubemap / fake reflection color this frame.
   if ( mPlaneReflector.lastUpdateMs != REFLECTMGR->getLastUpdateMs() && mPlaneReflector.isOccluded() )
      reflect = false;

   //Point4F reflectParams( getRenderPosition().z, mReflectMinDist, mReflectMaxDist, reflect );
   Point4F reflectParams( getRenderPosition().z, 0.0f, 1000.0f, !reflect );
   
   // TODO: This is a hack... why is this broken... check after
   // we merge advanced lighting with trunk!
   //
   reflectParams.z = 0.0f;
   matParams->setSafe( paramHandles.mReflectParamsSC, reflectParams );

   VectorF reflectNorm( 0, 0, 1 );
   matParams->setSafe(paramHandles.mReflectNormalSC, reflectNorm ); 
}

void WaterPlane::prepRenderImage( SceneRenderState *state )
{
   PROFILE_SCOPE(WaterPlane_prepRenderImage);

   if( !state->isDiffusePass() )
      return;

   mBasicLighting = dStricmp( LIGHTMGR->getId(), "BLM" ) == 0;
   mUnderwater = isUnderwater( state->getCameraPosition() );

   mMatrixSet->setSceneView(GFX->getWorldMatrix());
   
   const Frustum &frustum = state->getCameraFrustum();

   if ( mPrimBuff.isNull() || 
        mGenerateVB ||         
        frustum != mFrustum )
   {      
      mFrustum = frustum;
      setupVBIB( state );
      mGenerateVB = false;

      MatrixF proj( true );
      MathUtils::getZBiasProjectionMatrix( 0.0001f, mFrustum, &proj );
      mMatrixSet->setSceneProjection(proj);
   }

   _getWaterPlane( state->getCameraPosition(), mWaterPlane, mWaterPos );
   mWaterFogData.plane = mWaterPlane;
   mPlaneReflector.refplane = mWaterPlane;
   updateUnderwaterEffect( state );

   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
   ri->renderDelegate.bind( this, &WaterObject::renderObject );
   ri->type = RenderPassManager::RIT_Water;
   state->getRenderPass()->addInst( ri );

   //mRenderUpdateCount++;
}

void WaterPlane::innerRender( SceneRenderState *state )
{
   GFXDEBUGEVENT_SCOPE( WaterPlane_innerRender, ColorI( 255, 0, 0 ) );

   const Point3F &camPosition = state->getCameraPosition();

   Point3F rvec, fvec, uvec, pos;

   const MatrixF &objMat = getTransform(); //getRenderTransform();
   const MatrixF &camMat = state->getCameraTransform();

   MatrixF renderMat( true );

   camMat.getColumn( 1, &fvec );
   uvec.set( 0, 0, 1 );
   rvec = mCross( fvec, uvec );
   rvec.normalize();   
   fvec = mCross( uvec, rvec );
   pos = camPosition;
   pos.z = objMat.getPosition().z;      

   renderMat.setColumn( 0, rvec );
   renderMat.setColumn( 1, fvec );
   renderMat.setColumn( 2, uvec );
   renderMat.setColumn( 3, pos );

   setRenderTransform( renderMat );

   // Setup SceneData
   SceneData sgData = setupSceneGraphInfo( state );   

   // set the material
   S32 matIdx = getMaterialIndex( camPosition );
   
   if ( !initMaterial( matIdx ) )
      return;

   BaseMatInstance *mat = mMatInstances[matIdx];
   WaterMatParams matParams = mMatParamHandles[matIdx];

   // render the geometry
   if ( mat )
   {      
      // setup proj/world transform
      mMatrixSet->restoreSceneViewProjection();
      mMatrixSet->setWorld(getRenderTransform());

      setShaderParams( state, mat, matParams );     

      while( mat->setupPass( state, sgData ) )
      {    
         mat->setSceneInfo(state, sgData);
         mat->setTransforms(*mMatrixSet, state);
         setCustomTextures( matIdx, mat->getCurPass(), matParams );

         // set vert/prim buffer
         GFX->setVertexBuffer( mVertBuff );
         GFX->setPrimitiveBuffer( mPrimBuff );
         GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, mVertCount, 0, mPrimCount );
      }
   }
}

bool WaterPlane::isUnderwater( const Point3F &pnt ) const
{
   F32 height = getPosition().z;

   F32 diff = pnt.z - height;

   return ( diff < 0.1 );
}

F32 WaterPlane::distanceTo( const Point3F& point ) const
{
   if( isUnderwater( point ) )
      return 0.f;
   else
      return ( point.z - getPosition().z );
}

bool WaterPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& )
{
   if(context == PLC_Navigation)
   {
      polyList->setObject( this );
      polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );

      F32 z = getPosition().z;
      Point3F
         p0(box.minExtents.x, box.maxExtents.y, z),
         p1(box.maxExtents.x, box.maxExtents.y, z),
         p2(box.maxExtents.x, box.minExtents.y, z),
         p3(box.minExtents.x, box.minExtents.y, z);

      // Add vertices to poly list.
      U32 v0 = polyList->addPoint(p0);
      polyList->addPoint(p1);
      polyList->addPoint(p2);
      polyList->addPoint(p3);

      // Add plane between first three vertices.
      polyList->begin(0, 0);
      polyList->vertex(v0);
      polyList->vertex(v0+1);
      polyList->vertex(v0+2);
      polyList->plane(v0, v0+1, v0+2);
      polyList->end();

      // Add plane between last three vertices.
      polyList->begin(0, 1);
      polyList->vertex(v0+2);
      polyList->vertex(v0+3);
      polyList->vertex(v0);
      polyList->plane(v0+2, v0+3, v0);
      polyList->end();

      return true;
   }

   return false;
}

void WaterPlane::inspectPostApply()
{
   Parent::inspectPostApply();

   setMaskBits( UpdateMask );
}

void WaterPlane::setTransform( const MatrixF &mat )
{
   // We only accept the z value from the new transform.

   MatrixF newMat( true );
   
   Point3F newPos = getPosition();
   newPos.z = mat.getPosition().z;  
   newMat.setPosition( newPos );

   Parent::setTransform( newMat );

   // Parent::setTransforms ends up setting our worldBox to something other than
   // global, so we have to set it back... but we can't actually call setGlobalBounds
   // again because it does extra work adding and removing us from the container.

   mGlobalBounds = true;
   mObjBox.minExtents.set(-1e10, -1e10, -1e10);
   mObjBox.maxExtents.set( 1e10,  1e10,  1e10);

   // Keep mWaterPlane up to date.
   mWaterFogData.plane.set( 0, 0, 1, -getPosition().z );   
}

void WaterPlane::onStaticModified( const char* slotName, const char*newValue )
{
   Parent::onStaticModified( slotName, newValue );

   if ( dStricmp( slotName, "surfMaterial" ) == 0 )
      setMaskBits( MaterialMask );
}

bool WaterPlane::castRay(const Point3F& start, const Point3F& end, RayInfo* info )
{
   // Simply look for the hit on the water plane
   // and ignore any future issues with waves, etc.
   const Point3F norm(0,0,1);
   PlaneF plane( Point3F::Zero, norm );

   F32 hit = plane.intersect( start, end );
   if ( hit < 0.0f || hit > 1.0f )
      return false;
   
   info->t = hit;
   info->object = this;
   info->point = start + ( ( end - start ) * hit );
   info->normal = norm;
   info->material = mMatInstances[ WaterMat ];

   return true;
}

F32 WaterPlane::getWaterCoverage( const Box3F &testBox ) const
{
   F32 posZ = getPosition().z;
   
   F32 coverage = 0.0f;

   if ( posZ > testBox.minExtents.z ) 
   {
      if ( posZ < testBox.maxExtents.z )
         coverage = (posZ - testBox.minExtents.z) / (testBox.maxExtents.z - testBox.minExtents.z);
      else
         coverage = 1.0f;
   }

   return coverage;
}

F32 WaterPlane::getSurfaceHeight( const Point2F &pos ) const
{
   return getPosition().z;   
}

void WaterPlane::onReflectionInfoChanged()
{
   /*
   if ( isClientObject() && GFX->getPixelShaderVersion() >= 1.4 )
   {
      if ( mFullReflect )
         REFLECTMGR->registerObject( this, ReflectDelegate( this, &WaterPlane::updateReflection ), mReflectPriority, mReflectMaxRateMs, mReflectMaxDist );
      else
      {
         REFLECTMGR->unregisterObject( this );
         mReflectTex = NULL;
      }
   }
   */
}

void WaterPlane::setGridSize( U32 inSize )
{
   if ( inSize == mGridSize )
      return;

   // GridSize must be an odd number.
   //if ( inSize % 2 == 0 )
   //   inSize++;

   // GridSize must be at least 1
   inSize = getMax( inSize, (U32)1 );

   mGridSize = inSize;
   mGridSizeMinusOne = mGridSize - 1;
   mGenerateVB = true;
   setMaskBits( UpdateMask );
}

void WaterPlane::setGridElementSize( F32 inSize )
{
   if ( inSize == mGridElementSize )
      return;

   // GridElementSize must be greater than 0
   inSize = getMax( inSize, 0.0001f );

   mGridElementSize = inSize;
   mGenerateVB = true;
   setMaskBits( UpdateMask );
}

bool WaterPlane::protectedSetGridSize( void *obj, const char *index, const char *data )
{
   WaterPlane *object = static_cast<WaterPlane*>(obj);
   S32 size = dAtoi( data );

   object->setGridSize( size );

   // We already set the field.
   return false;
}

bool WaterPlane::protectedSetGridElementSize( void *obj, const char *index, const char *data )
{
   WaterPlane *object = static_cast<WaterPlane*>(obj);
   F32 size = dAtof( data );

   object->setGridElementSize( size );

   // We already set the field.
   return false;
}

void WaterPlane::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos )
{
   outPos = getPosition();   
   outPlane.set( outPos, Point3F(0,0,1) );   
}