2D Water + Refraction (maybe)
Author: Richard Owen
Based on some code i saw along time ago and I can not find now to reference
Move your mouse around the stage and check out the water trail
this works well as rain drops if you want to randomly drop points onto the surface
Hi Richard Again
I am adding a refraction code and a background image to refract
// forked from Metrix's 2D Water Ripples - 2009-1-9
// Author: Richard Owen
//
// Based on some code i saw along time ago and I can not find now to reference
// Move your mouse around the stage and check out the water trail
// this works well as rain drops if you want to randomly drop points onto the surface
//
// Hi Richard Again
// I am adding a refraction code and a background image to refract
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.setInterval;
/**
* ...
* @author ...
*/
public class Main extends Sprite
{
private var Water:WaterMaterial;
private var intervalID:uint = 0;
private var dummyBMP:flash.display.BitmapData;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
//Add backgroud pattern
var size:int = 8;
var gridwidth:int = 58;
var gridheight:int = 58;
this.graphics.clear();
this.graphics.lineStyle(1, 0x000000, 1);
this.graphics.beginFill(0xFFFFFF, 1);
var col:int;
var row:int;
col=0;
row=0;
row = 0;
for (col = 0; col < gridwidth; col++)
{
for(row = 0; row < gridheight; row++){
this.graphics.drawRect(col * size, row * size, size, size);
}
}
this.graphics.endFill();
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
Water = new WaterMaterial();
stage.addChild(Water);
Water.addEventListener(MouseEvent.MOUSE_DOWN, MouseClicky)
Water.addEventListener(MouseEvent.MOUSE_UP, MouseRelease)
Water.addEventListener(MouseEvent.MOUSE_MOVE, MouseMove)
intervalID = setInterval(WaterUpdate, 15);
}
private function MouseClicky(event:MouseEvent):void {
Water.setDrop(event.localX, event.localY);
Water.setDrop(event.localX-1, event.localY+1);
Water.setDrop(event.localX+1, event.localY+1);
Water.setDrop(event.localX-1, event.localY-1);
Water.setDrop(event.localX+1, event.localY-1);
};
/*
private function MouseClicky(event:MouseEvent):void {
//top
Water.setDrop(event.localX-2, event.localY-2);
Water.setDrop(event.localX , event.localY-2);
Water.setDrop(event.localX+2, event.localY-2);
//left
Water.setDrop(event.localX-2, event.localY-2);
Water.setDrop(event.localX-2, event.localY);
Water.setDrop(event.localX-2, event.localY+2);
//bottom
Water.setDrop(event.localX-2, event.localY+2);
Water.setDrop(event.localX , event.localY+2);
Water.setDrop(event.localX+2, event.localY+2);
//right
Water.setDrop(event.localX+2, event.localY-2);
Water.setDrop(event.localX+2, event.localY);
Water.setDrop(event.localX+2, event.localY+2);
};
*/
private function MouseMove(event:MouseEvent):void {
Water.setDrop(event.localX, event.localY);
};
public function WaterUpdate():void {
Water.forceRefresh();
}
private function MouseRelease(event:MouseEvent):void {
//clearInterval(intervalID);
};
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.InteractiveObject;
import flash.geom.Rectangle;
import flash.display.Sprite;
import flash.events.Event;
class WaterMaterial extends Sprite
{
private const MaxSize:uint = 460;
private const TestScale:Number = 4.0;
private const BufWidth:uint = Math.floor(MaxSize / TestScale);
private const BufHeight:uint = Math.floor(MaxSize / TestScale);
private const MaxHeight:uint = 256;
private const damping:Number = 7;
private var Buffer1:Array = new Array(BufWidth);
private var Buffer2:Array = new Array(BufWidth);
private var tempBuff:Array;
private var waterData:BitmapData;
private var waterMap:Bitmap;
private var dummyBMP:BitmapData;
public function WaterMaterial()
{
addEventListener(Event.ADDED_TO_STAGE, init);
}
public function init(e:Event): void {
removeEventListener(Event.ADDED_TO_STAGE, init);
//Create a bitmap to hold the rendering data
waterData = new BitmapData(BufWidth, BufHeight, true, 0x00000000);
//add the water as a bitmap to the stage
waterMap = new Bitmap(waterData);
addChild(waterMap);
//
this.x = 0;
this.y = 0;
this.width = BufWidth * TestScale;
this.height = BufHeight * TestScale;
//Set all the water to a flat 0
var col:int;
var row:int;
for (col = 0; col < BufWidth ; col++ ) {
Buffer1[col] = new Array(BufHeight);
Buffer2[col] = new Array(BufHeight);
for (row = 1; row < BufHeight; row++) {
Buffer1[col][row] = 0;
Buffer2[col][row] = 0;
}
}
dummyBMP = new flash.display.BitmapData(500, 500);
//dummyBMP.floodFill(10,10,0xff0000ff);
dummyBMP.draw(root);
//trace("0x"+dummyBMP.getPixel(150, 150).toString(16));
}
//Add a drop of water to the pool
public function setDrop(dropX:uint, dropY:uint):void {
Buffer1[dropX][dropY] = MaxHeight;
}
//update the bitmap witht he current height
private function HeightToPixel(col: int, row:int ): void {
var pixelColor:uint;
if(true){
pixelColor = SolidColorWater(col, row);
}
else
{
pixelColor = RefractionColor(col, row);
};
waterData.setPixel32(col + 1, row + 1, pixelColor);
}
private function SolidColorWater(col: int, row: int): uint{
var pixelColor:uint;
pixelColor = Buffer2[col][row] << 24;
pixelColor = pixelColor + 0xff;
return pixelColor;
}
private function RefractionColor(col: int, row: int): uint {
var rIndex:Number;
rIndex = 2.0; //Water Refraction Index
var y:int;
var x:int;
var xDiff:int;
var xAngle:Number;
var xRefraction:Number;
var xDisplace:Number;
var yDiff:int;
var yAngle:Number;
var yRefraction:Number;
var yDisplace:Number;
var newcolor:uint;
x = col;
y = row;
//for y := 1 to MAXY-1 do begin
//for(y = 1; y < BufHeight; y++){// to MAXY-1 do begin
//for x := 1 to MAXX-1 do begin
//for(x=1; x<BufWidth;x++){
//xDiff := Trunc(WaveMap[x+1, y] - WaveMap[x, y]);
if(((x > 1) && (x < BufWidth - 1)) && ((y > 1) && (y < BufHeight - 1)))
{
xDiff = Math.floor(Buffer2[x+1][y] - Buffer2[x][y]);
//yDiff := Trunc(WaveMap[x, y+1] - WaveMap[x, y]);
yDiff = Math.floor(Buffer2[x][y+1] - Buffer2[x][y]);
//xAngle := arctan( xDiff );
xAngle = Math.atan(xDiff);
//xRefraction := arcsin( sin( xAngle ) / rIndex );
xRefraction = Math.asin(Math.sin(xAngle)/rIndex);
//xDisplace := Trunc( tan( xRefraction ) * xDiff );
xDisplace = Math.floor(Math.tan(xRefraction) * xDiff);
//yAngle := arctan( yDiff );
yAngle = Math.atan(yDiff);
//yRefraction := arcsin( sin( yAngle ) / rIndex );
yRefraction = Math.asin( Math.sin( yAngle) / rIndex);
//yDisplace := Trunc( tan( yRefraction ) * yDiff );
yDisplace = Math.floor(Math.tan( yRefraction) * yDiff);
//if xDiff < 0 then begin
if(xDiff < 0){
if(yDiff < 0 ){
//newcolor := BackgroundImage[x-xDisplace, y-yDisplace]
newcolor = dummyBMP.getPixel32(x-xDisplace, y-yDisplace);
}
else
{
//newcolor := BackgroundImage[x-xDisplace, y+yDisplace]
newcolor = dummyBMP.getPixel32(x-xDisplace, y+yDisplace);
}
}
else
{
if(yDiff < 0)
{
//newcolor := BackgroundImage[x+xDisplace, y-yDisplace]
newcolor = dummyBMP.getPixel32(x+xDisplace, y-yDisplace);
}
else
{
//newcolor := BackgroundImage[x+xDisplace, y+yDisplace]
newcolor = dummyBMP.getPixel32(x+xDisplace, y+yDisplace);
}
};
//TargetImage[x, y] := newcolor;
//waterData.setPixel32(x, y, newcolor);
//}
}
else
{
newcolor = dummyBMP.getPixel32(x,y);
};
return newcolor;
//return dummyBMP.getPixel32(10,10);
//newcolor = Buffer2[col][row] << 24;
//newcolor = newcolor + 0xff;
//return newcolor;
}
public function dampEdgeOfWater(): void
{
var col:int;
var row:int;
//reduce the edge pixels
for (col = 0; col < BufWidth; col++){
Buffer2[col][0] = Math.floor(Buffer2[col][0] >> damping);
Buffer2[col][BufHeight - 1] = Math.floor(Buffer2[col][BufHeight - 1] >> damping);
if (Buffer2[col][0] < 0) Buffer2[col][0] = 0;
if (Buffer2[col][BufHeight - 1] < 0) Buffer2[col][BufHeight - 1] = 0;
//SetColor
HeightToPixel(col, 0);
HeightToPixel(col, BufHeight - 1);
}
for (row = 0; row < BufHeight; row++) {
Buffer2[0][row] = Math.floor(Buffer2[0][row] >> damping);
Buffer2[BufWidth - 1][row] = Math.floor(Buffer2[BufWidth - 1][row] >> damping);
if (Buffer2[0][row] < 0) Buffer2[0][row] = 0;
if (Buffer2[BufWidth - 1][row] < 0) Buffer2[BufWidth - 1][row] = 0;
//SetColor
HeightToPixel(0, row);
HeightToPixel(BufWidth - 1, row);
}
}
//smooth outthe ripples
public function updateRipple():void {
var col:int;
var row:int;
var oldVal:int;
var newVal:int;
var smoothedVal:uint;
//Now process the inner rectangle of water
for (col = 1; col < BufWidth - 1; col++ ) {
for (row = 1; row < BufHeight - 1; row++) {
smoothedVal = Buffer1[col - 1][row] +
Buffer1[col + 1][row] +
Buffer1[col][row + 1] +
Buffer1[col][row - 1] +
Buffer1[col - 1][row - 1] +
Buffer1[col - 1][row + 1] +
Buffer1[col + 1][row - 1] +
Buffer1[col + 1][row + 1];
newVal = Math.floor((((smoothedVal ) >> 2 ) - Buffer2[col][row]));
newVal = newVal - ( newVal >> damping);
if (newVal < 0) newVal = 0;
//small optimisation
if(Buffer2[col][row]!= newVal) {
Buffer2[col][row] = newVal;
HeightToPixel(col, row);
}
}
}
dampEdgeOfWater()
}
//update the arrays
public function forceRefresh(): void {
//Update the water
waterData.lock();
updateRipple();
waterData.unlock();
//Swap buffer to retain direction and velocity
tempBuff = Buffer2;
Buffer2 = Buffer1;
Buffer1 = tempBuff;
}
}