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

Sand.exe demonstrates the Sand Effect on a background created using the other Stencils from this site like the image to the left but far faster. Click the background to restart the process.

Too Dry: The Sand Effect

CSandInterface makes the contents of a window collapse as if it were turned to sand (the above animation is slowed down a lot). All drawing is done using CPixelBlock. To use it, either draw the background to the DC first(and only once!) and call the CSandInterface::Draw function afterwards or derive a class from CSandInterface which implements the InitDC and InitPixelBlock functions and put your background drawing code in there.

This example uses an Owner-draw Button in a Dialog:
Sand Example
class CSand : public CSandInterface {
public:
  void InitDC(HDC hDC, const CRect& Rect) {
    FillRect(hDC, &Rect, CreateSolidBrush(GetSysColor(COLOR_3DFACE))); // Draw in dialogs background colour
    DrawText(hDC, "Hello",-1, (LPRECT)&Rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
  void InitPixelBlock() {
    Rectangle(10,20,10,20,0xCAFE69);
    Rectangle(20,10,50,10,0x69CAFE);
  }
};
Then in the Dialog Header have:
  CSand Sand;
and in the .cpp file use ClassWizard to add an OnDrawItem handler like this:
void CMyDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {
  if(nIDCtl==IDC_Pic) Sand.Draw(lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem, 1000); //Delay 1 second (1000ms) before collapsing
  CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

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.

If its too fast (for small buttons etc), call SetSlow().

Bear in mind that this algorithm needs to know what the background colour is (ie. what colour Pixel is _not_ sand... By default it uses the System Dialog Background Colour, if you're using something else use the SetBGColor function.

Interested in how it was done? Each frame is iterated from the last simply by scanning the last frame and working on a 2x2 square of pixels. The switch statement handles a set of rules that decide how each of the four pixels should behave. With a few sketches you'll be able to work the rules out from the code. The sand reflects nature in having at most a 45° slope. If DoneOne is still false after the loop, the sand is still and animation can stop.

    bool DoneOne=false;
    DWORD a,aa,b,bb,c,d;
    for(int y=Rect.bottom-1; y>=Rect.top+1; --y) {
      a=GetPixel((WORD)Rect.left, y-1, Bg);
      b=GetPixel((WORD)Rect.left, y  , Bg);
      for(int x=Rect.left+1; x<Rect.right; ++x) {
        d=a;
        c=b;
        a=aa=GetPixel(x, y-1, Bg); // (0,0)-> Origin is top-left
        b=bb=GetPixel(x, y  , Bg); //    | d a
        switch(((((((d!=Bg) <<1)   //    V c b
                   |(c!=Bg))<<1)
                   |(b!=Bg))<<1)   // (x,y) is b
                   |(a!=Bg)) {
          case  1: case  5: case 13: b=a; a=Bg; break; // a falls to b
          case  8: case 10: case 11: SetPixel(x-1,y-1, Bg); SetPixel(x-1,y, d); DoneOne=true; break; // d falls to c
          case  9:        b=a; a=Bg; SetPixel(x-1,y-1, Bg); SetPixel(x-1,y, d); break; // a falls to b AND d falls to c
          case  3:      if(y&4)     {SetPixel(x-1,y  , a ); a=Bg; break;}// a falls to c
                   else if(y&2)     {SetPixel(x-1,y  , b ); b=Bg; break;}// b moves to c
                   else             {SetPixel(x-1,y-1, b ); b=Bg; break;}// b moves to d
          case 12:      if(y&4)     {SetPixel(x-1,y-1, Bg); b=d;  break;}// d falls to b
                   else if(y&2)     {SetPixel(x-1,y  , Bg); b=c;  break;}// c moves to b
                   else             {SetPixel(x-1,y  , Bg); a=c;  break;}// c moves to a
        }
        if(a!=aa) {SetPixel(x,y-1, a); DoneOne=true;}
        if(b!=bb) {SetPixel(x,y,   b); DoneOne=true;}
    } }
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.