class
CWaterPlane
{
protected:
struct VERTEX_XYZ_DIFFUSE
{
D3DXVECTOR3 position; // The position
D3DCOLOR color; // The color
};
// Our Flexible Vertex Format Desription.
DWORD D3DFVF_XYZ_DIFFUSE; // =
D3DFVF_XYZ|D3DFVF_DIFFUSE;
protected:
IDirect3DDevice8* m_pD3DDevice; // DirectX
Screen Object
int m_iGridDensity;
// Water Surface Parts
// Water Surface Buffers
IDirect3DVertexBuffer8* m_pVertexBuffer;
IDirect3DIndexBuffer8* m_pIndexBuffer;
// Surface hights
float *m_pActiveHeightArray;
float *m_pScratchHeightArray;
// Ripples and splashes must die away, so we set a
damping value.
float m_fDampValue;
// Need a value
float m_fRefractionIndex;
public:
// Called once when we create our water plane.
bool Create(IDirect3DDevice8** pDevice,
int iGridDensity)
{
//[1]
m_pD3DDevice = *pDevice;
m_iGridDensity = iGridDensity;
// SetUp Our Grid Vertices which will
represent our 3D water surface.
D3DFVF_XYZ_DIFFUSE = D3DFVF_XYZ|D3DFVF_DIFFUSE;
//[2]
// Create our Water Surface.
CreateVertexGrid(&m_pVertexBuffer, &m_pIndexBuffer,
1.0f, // The size
of our water plane (width=height=fTotalSize)
0xffff0000,
// Simple Red Colour
m_iGridDensity, m_iGridDensity);
//[3]
// Now we need to create a 2D array
that will hold our 3D surface
// heights. - initialize height arrays
int iNumVerts = iGridDensity*iGridDensity;
float* pfHeightArray1 =
new float[iNumVerts];
float* pfHeightArray2 =
new float[iNumVerts];
memset(pfHeightArray1, 0, sizeof(float)*iNumVerts);
memset(pfHeightArray2, 0, sizeof(float)*iNumVerts);
m_pActiveHeightArray = pfHeightArray1;
m_pScratchHeightArray = pfHeightArray2;
// Set a damping factor.
m_fDampValue = 1.1f;
m_fRefractionIndex = 1.30f;
return
true;
}//End
of Create(..) Method
void Release()
{
m_pVertexBuffer->Release();
m_pIndexBuffer->Release();
delete[] m_pActiveHeightArray;
delete[] m_pScratchHeightArray;
}//End
of Release() Method
// We call render to render our shape to the
screen.
void Render()
{
int iNumVerts = m_iGridDensity*m_iGridDensity;
m_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP,
D3DTOP_SELECTARG1);
m_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1,
D3DTA_DIFFUSE);
m_pD3DDevice->SetStreamSource( 0, m_pVertexBuffer,
sizeof(VERTEX_XYZ_DIFFUSE) );
m_pD3DDevice->SetVertexShader(D3DFVF_XYZ_DIFFUSE);
m_pD3DDevice->SetIndices( m_pIndexBuffer, 0L );
// Well at this stage is almost
impossible to see what you have, if you
// render the triangles as solid, all
you see is a flat square surface...
// so I added a little option here, so
you can switch on wire mesh view
// which displays our triangles as
lines...
// Draw as a wire mesh or a solid fill
if(
false ) // Wire Mesh
{
m_pD3DDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0,
iNumVerts, // Number of points
0,
// Starting Triangle
iNumVerts*2); // Number of Triangles
}
else
// Sold Fill
{
m_pD3DDevice->DrawIndexedPrimitive( D3DPT_LINELIST, 0,
iNumVerts, // Number of points
0,
// Starting Triangle
iNumVerts*3); // Number of Triangles
}
}//End
of Render() Method
// This function creates our surface that will
represent our water surface, its
// made up of a grid of vertices and index
pointers into that grid.
bool
CreateVertexGrid(IDirect3DVertexBuffer8** pVB, IDirect3DIndexBuffer8 **pIB,
float fTotalSize,
DWORD dwColor, int iNumVerticesX,
int iNumVerticesY)
{
//[1] - CREATE VERTEX BUFFER
// create and fill vertex buffer
m_pD3DDevice->CreateVertexBuffer(iNumVerticesX*iNumVerticesY*sizeof(VERTEX_XYZ_DIFFUSE),
0,
D3DFVF_XYZ_DIFFUSE, D3DPOOL_MANAGED, pVB);
VERTEX_XYZ_DIFFUSE *pVertices;
float fSizeDiv2 = fTotalSize/2;
(*pVB)->Lock( 0, iNumVerticesX*iNumVerticesY*sizeof(VERTEX_XYZ_DIFFUSE),
(BYTE**)&pVertices, 0 );
for (int
x=0; x < iNumVerticesX; x++) {
for (int
y=0; y < iNumVerticesY; y++) {
pVertices[(y*iNumVerticesX)+x].position =
D3DXVECTOR3(
(iNumVerticesX > 1) ? (((float)x/(float)(iNumVerticesX-1))*fTotalSize)-fSizeDiv2
: 0,
0.0f,
(iNumVerticesY > 1) ? (((float)(iNumVerticesY-1-y)/(float)(iNumVerticesY-1))*fTotalSize)-fSizeDiv2
: 0);
pVertices[(y*iNumVerticesX)+x].color = dwColor;
}
}
(*pVB)->Unlock();
//[2] - CREATE INDEX BUFFER INTO OUR
CREATED VERTEX BUFFER ABOVE
// create index buffer
m_pD3DDevice->CreateIndexBuffer(
iNumVerticesX*iNumVerticesY*2*3*2,
// *2 (tris) *3 (indicies per tri) * 2 (bytes per
index)
D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,D3DPOOL_MANAGED,
pIB);
// lock and fill index buffer
WORD *pIndices;
(*pIB)->Lock(0, iNumVerticesX*iNumVerticesY*2*3*2, (unsigned
char **)&pIndices, 0);
WORD *pIndex = pIndices;
for (int
x=0; x < iNumVerticesX-1; x++) {
for (int
y=0; y < iNumVerticesY-1; y++) {
// first triangle
*(pIndex++) = ((y)*iNumVerticesX)+x;
*(pIndex++) = ((y)*iNumVerticesX)+x+1;
*(pIndex++) = ((y+1)*iNumVerticesX)+x+1;
// second triangle
*(pIndex++) = ((y)*iNumVerticesX)+x;
*(pIndex++) = ((y+1)*iNumVerticesX)+x+1;
*(pIndex++) = ((y+1)*iNumVerticesX)+x;
}
}
(*pIB)->Unlock();
return
true;
}//End
of CreateVertexGrid(..) Method
//=============================Dyamic Part of our
code=======================
void Update(float
fElapsedTime)
{
// For this tutorial I just added a
few lines of code.. not the way it should be
// done but introduces a small delay.
So the effect is better.
static
float delay=0.0f;
delay+= fElapsedTime;
if( delay < 1.0f)
return;
delay = 0.0f;
ProcessWater();
ApplyHeightArrayToVB();
// Really simple way to switch
buffers, when I first did this, I thought
// of doing a memcpy...but why do all
that work when we can just switch
// pointers!...da-da!
// flip-flop the water buffers.
float *temp =
m_pActiveHeightArray;
m_pActiveHeightArray = m_pScratchHeightArray;
m_pScratchHeightArray = temp;
};//End
of Update(..) Method
/****************************************************************************
ProcessWater: this function processes our water. It takes two input
buffers,
the water dimensions, and the cooling amount. It calculates the new
water
values from waterfield1 and puts them into waterfield2.
****************************************************************************/
void ProcessWater()
{
int iNumVerts = m_iGridDensity;
// loop through all the water
values...
for (int
y=0; y < iNumVerts; y++) {
for (int
x=0; x < iNumVerts; x++) {
// add up the values of all the
neighboring water values...
float value;
int xminus1 = x-1;
if (xminus1 < 0) xminus1 = 0;
int xminus2 = x-2;
if (xminus2 < 0) xminus2 = 0;
int yminus1 = y-1;
if (yminus1 < 0) yminus1 = 0;
int yminus2 = y-2;
if (yminus2 < 0) yminus2 = 0;
int xplus1 = x+1;
if (xplus1 >= iNumVerts) xplus1 = (iNumVerts)-1;
int xplus2 = x+2;
if (xplus2 >= iNumVerts) xplus2 = (iNumVerts)-1;
int yplus1 = y+1;
if (yplus1 >= iNumVerts) yplus1 = (iNumVerts)-1;
int yplus2 = y+2;
if (yplus2 >= iNumVerts) yplus2 = (iNumVerts)-1;
// Method 1: Slower but yields
slightly better looking water
{
value = m_pActiveHeightArray[((y) *iNumVerts)+xminus1];
value += m_pActiveHeightArray[((y) *iNumVerts)+xminus2];
value += m_pActiveHeightArray[((y) *iNumVerts)+xplus1];
value += m_pActiveHeightArray[((y) *iNumVerts)+xplus2];
value += m_pActiveHeightArray[((yminus1)*iNumVerts)+x];
value += m_pActiveHeightArray[((yminus2)*iNumVerts)+x];
value += m_pActiveHeightArray[((yplus1) *iNumVerts)+x];
value += m_pActiveHeightArray[((yplus2) *iNumVerts)+x];
value += m_pActiveHeightArray[((yminus1)*iNumVerts)+xminus1];
value += m_pActiveHeightArray[((yminus1)*iNumVerts)+xplus1];
value += m_pActiveHeightArray[((yplus1) *iNumVerts)+xminus1];
value += m_pActiveHeightArray[((yplus1) *iNumVerts)+xplus1];
// average them
value /= 6.0f;
}
// subtract the previous water
value
value -= m_pScratchHeightArray[(y*iNumVerts)+x];
// dampen it!
value /= m_fDampValue;
if (value > 10.0f) value =
10.0f;
if (value < -10.0f) value
= -10.0f;
// store it in array
m_pScratchHeightArray[(y*iNumVerts)+x] = value;
}
}
}//End
of ProcessWater() Method
// Helper Function, used by ApplyHeightArrayToVB()
float CalcDisplacement(float
fHeightdiff)
{
float fRefractionIndex = 1.0f;
float fDepth = 1.0f;
// the angle is the arctan of the
height difference
float angle = (float)atan(fHeightdiff);
// now, calculate the angle of the
refracted beam.
float beamangle = (float)asin(sin(angle)
/ fRefractionIndex);
// finally, calculate the
displacement, based on the refracted beam
// and the height difference.
return(tan(beamangle) * (fHeightdiff+
fDepth));
}//End
of CalcDisplacement(..) Method
void ApplyHeightArrayToVB()
{
// SET CONSTANT
float fEnvBlendFactor = 1.0f;
int iNumVerts = m_iGridDensity;
VERTEX_XYZ_DIFFUSE *pVertices;
float fSizeDiv2 = 0.5f;
m_pVertexBuffer->Lock( 0, iNumVerts*iNumVerts*sizeof(VERTEX_XYZ_DIFFUSE),
(BYTE**)&pVertices, 0 );
for (int
x=0; x < iNumVerts; x++) {
for (int
y=0; y < iNumVerts; y++) {
float fValue =
m_pActiveHeightArray[(y*iNumVerts)+x];
if (fValue > 2.0f)
fValue = 2.0f;
pVertices[(y*iNumVerts)+x].position = D3DXVECTOR3(
(iNumVerts > 1) ? (((float)x/(float)(iNumVerts-1)))-fSizeDiv2
: 0,
fValue,
(iNumVerts > 1) ? (((float)(iNumVerts-1-y)/(float)(iNumVerts-1)))-fSizeDiv2
: 0);
pVertices[(y*iNumVerts)+x].color =
D3DXCOLOR(1.0f, 1.0f, 1.0f, fEnvBlendFactor);
}
}
m_pVertexBuffer->Unlock();
}//End
of ApplyHeightArrayToVB() Method
void CreateWaterDroplet(int
iX, int iY, int
iSize,
float
iSplashStrength)
{
int iNumVerts = m_iGridDensity;
for (int
x=iX-iSize; x <= iX+iSize; x++) {
for (int
y=iY-iSize; y <= iY+iSize; y++) {
// make sure we're in
bounds
if (x < 0 || x >=
iNumVerts || y < 0 || y >= iNumVerts) continue;
// see if the point at (x,y)
is within the circle of radius size
int square_x =
(x-iX)*(x-iX);
int square_y =
(y-iY)*(y-iY);
int square_size =
iSize*iSize;
if (square_x+square_y
<= square_size) {
// it's within the
size circle! apply it to the water buffer.
m_pActiveHeightArray[(y*iNumVerts)+(x)] += (float)iSplashStrength*sqrt((float)square_x+square_y);
}
}
}
}//End
of CreateWaterDroplet(..) Method
};
|