2D Water Effect
Site Map Feedback
Water Effect
Up Sand Water

OldWater.exe demonstrates the Water Effect (with dripping) on a background created using the other Stencils from this site.

Water.exe has more subtle rendering and some fish to play with: one loves the mouse and chases the cursor, two others chase another which tries to evade them in three dimensions. The fish themselves aren't rendered but you'll see their wake as they surface.

Too Wet: The Water Effect

CWaterInterface makes the contents of a window appear to be under water. The constant speed is a consequence of an algorithm described here. This class reproduces the behaviour using minimal resources and code. Most implementations of this algorithm use large arrays, DirectX or OpenGL... This implementation doesn't need DirectX or OpenGL, all drawing is done using CPixelBlock.

The two height Arrays are chars giving an overhead of one Byte for every two pixels. The Lighting effect is done by altering the pixel's brightness using CPixelBlock::BlendPixel(...) to Blend the Pixel with Black or White.

To use it, either draw first(and only once!) and call the Draw function afterwards or derive a class from it which implements the InitDC and InitPixelBlock functions and put your drawing code in there.

This example uses an Owner-draw Button (IDC_Water) in a Dialog:
class CAboutButton : public CButton, public CWaterInterface {
  LPDRAWITEMSTRUCT pDrawItemStruct;
  WORD LastX, LastY;
public:
  CAboutButton() : CWaterInterface(false,true) {}
  virtual ~CAboutButton() {}
private:
  bool InitDC(HDC hDC, WORD Width, WORD Height) { // Draw what you want on the DC and then return true;
    CRect Rect(0,0,Width,Height);
    FillRect(hDC, &Rect, CreateSolidBrush(GetSysColor(COLOR_3DFACE))); //Draw in dialogs background colour
    DrawEdge(hDC, (LPRECT)&Rect, EDGE_RAISED, BF_RECT);
    CString Title;
    GetWindowText(Title);
    DrawText(hDC, Title,-1, (LPRECT)&Rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    return true; // Always
  }
  void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {Draw(lpDrawItemStruct->hDC);}
  DECLARE_MESSAGE_MAP()
  afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  afx_msg void OnMouseMove  (UINT nFlags, CPoint point);
};

//------------- AboutButton -----------------

BEGIN_MESSAGE_MAP(CAboutButton, CButton)
  ON_WM_LBUTTONDOWN()
  ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()

void CAboutButton::OnLButtonDown(UINT nFlags, CPoint point) {CButton::OnLButtonDown(nFlags, point); Dip((WORD)point.x, (WORD)point.y, 10, 127);}
void CAboutButton::OnMouseMove  (UINT nFlags, CPoint point) {
  if(IsDripping()) Straight(LastX, LastY, (WORD)point.x, (WORD)point.y,1);
  LastX=(WORD)point.x;
  LastY=(WORD)point.y;
}

Then in the Dialog Header have:
  CAboutButton About;
You need to size the PixelBlock in OnInitDialog:
BOOL CPhidippusDlg::OnInitDialog() {
  CDialog::OnInitDialog();
  Water.SubclassDlgItem(IDC_Water, this);
  CRect Rect;
  Water.GetClientRect(&Rect);
  Water.Set(Rect.Width(), Rect.Height());
  ...
}

void CMyDlg::OnBnClickedAbout() {
  About.StopRaining();
  CDialog(IDD_ABOUTBOX).DoModal();
  About.StartRaining(m_hWnd);
}
You can stop flashing as Windows erases control backgrounds by setting the "Clip Children" Property for the Dialog.
You can save all the frames as bitmaps using the Draw function with the SaveFiles parameter set to true.
they get saved in the current Directory as t000.bmp upwards.

To Draw Directly on the Dialog Window do the following:
Create a Class that is the "paper" you will draw to derived from CWaterInterface:
  class CMyGame : public CWaterInterface {
   ...
  }
Declare one in the dialog .cpp file as a global:
  CMyGame MyGame;

  void CMyDlg::OnPaint() {
    CPaintDC dc(this); // device context for painting
    if(IsIconic()) {
      CRect Rect;
      GetClientRect(&Rect);
      SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
      int cxIcon=GetSystemMetrics(SM_CXICON);
      int cyIcon=GetSystemMetrics(SM_CYICON);
      int x=(Rect.Width ()-cxIcon+1)/2;
      int y=(Rect.Height()-cyIcon+1)/2;
      dc.DrawIcon(x, y, m_hIcon);
    }else MyGame.Draw(dc.GetSafeHdc()); // <<<<<<<<<<<< Water Drawn Here!
  }

  BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) {return TRUE;} // Don't erase - it flashes!

  void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point) {
    MyGame.OnClick((WORD)point.x, (WORD)point.y);
    CDialog::OnLButtonDown(nFlags, point);
  }

// You need to tell the PixelBlock its size which for complete windows is best done in OnSize.
// For re-sizing windows you need to stop the water before resizing and restart it afterwards:
  void CMyDlg::OnSize(UINT nType, int cx, int cy) {
    MyGame.StopRaining();
    MyGame.Resample(); // Calls your InitDC and InitPixelBlock again next redraw
    MyGame.Set(cx,cy); // Resizes the Pixel Block
	  CDialog::OnSize(nType, cx, cy);
    MyGame.StartRaining(m_hWnd);
  }
If you pop up other dialogs like the about box you will need to stop the water first:
  void CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) {
    if((nID & 0xFFF0)==IDM_ABOUTBOX) {
      MyGame.StopRaining();
        CDialog(IDD_ABOUTBOX).DoModal();
      MyGame.StartRaining(m_hWnd);
    }else CDialog::OnSysCommand(nID, lParam);
  }
SetDripping() keeps the animation going with a central drip.
SetRaining() keeps the animation going with random drips.
You can mix in as many 'Stencils' as you like to provide all the primitives you need.

The Colours, Drawing and Patterns sections have further extensions.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.