水波纹,鼠标点击后水会四散,产生涟漪的感觉,十分真实.

实现原理:


扩散:当你投一块石头到水中,你会看到一个以石头入水点为圆心所形成的一圈圈的水波,这里,你可能会被这个现象所误导,以为水波上的每一点都是以石头入水点为中心向外扩散的,这是错误的。实际上,水波上的任何一点在任何时候都是以自己为圆心向四周扩散的,之所以会形成一个环状的水波,是因为水波的内部因为扩散的对称而相互 抵消了。 
衰减:因为水是有阻尼的,否则,当你在水池中投入石头,水波就会永不停止的震荡下去。 
水的折射:因为水波上不同地点的倾斜角度不同,所以,因为水的折射,我们从观察点垂直往下看到的水底并不是在观 察点的正下方,而有一定的偏移。如果不考虑水面上部的光线反射,这就是我们能感觉到水波形状的原因。 
反射:水波遇到障碍物会反射。 
衍射:如果能在水池中央放上一块礁石,或放一个中间有缝的隔板,那么就能看到水波的衍射现象了


多说无益,看一下效果图

android 波纹扩散效果 波纹扩散原理_初始化

                                     

android 波纹扩散效果 波纹扩散原理_初始化_02


这样子看起来还是极好的.

我先要定义两个波能缓冲区

int   *m_pWaveBuf1; //波能缓冲区1
int   *m_pWaveBuf2; //波能缓冲区2


在初始化函数里,波能缓冲区水池图象一样大小的数组

//获取位图宽、高、一行的字节数
	m_iBmpWidth = stBitmap.bmWidth;
	m_iBmpHeight = stBitmap.bmHeight;
	//分配波能缓冲区
	m_pWaveBuf1 = new int[m_iBmpWidth*m_iBmpHeight];
	m_pWaveBuf2 = new int[m_iBmpWidth*m_iBmpHeight];



水波扩散函数

void CRipple::WaveSpread()
{
	int *lpWave1 = m_pWaveBuf1;
	int *lpWave2 = m_pWaveBuf2;
	for(int i = m_iBmpWidth; i < (m_iBmpHeight - 1)*m_iBmpWidth; i++)
	{
		//波能扩散
		lpWave2[i] = ((lpWave1[i - 1] + lpWave1[i - m_iBmpWidth] +
					   lpWave1[i + 1] + lpWave1[i + m_iBmpWidth]) >> 1) - lpWave2[i];
		//波能衰减
		lpWave2[i] -= (lpWave2[i] >> 5);
	}
	//交换缓冲区
	m_pWaveBuf1 = lpWave2;
	m_pWaveBuf2 = lpWave1;
}

某一时刻,X0点的振幅除了受X0点自身振幅的影响外,同时受来自它周围前、后、左、右四个点(X1、X2、X3、X4)

的影响.这四个点对a0点的影响力可以说是机会均等的。那么我们可以假设这个一次公式为:

X0’=a(X1+X2+X3+X4)+bX0            (公式1)

最终我们得到这个函数

X0’=(X1+X2+X3+X4)/ 2- X0

水在实际中是存在阻尼的,否则,用上面这个公式,一旦你在水中增加一个波源,水面将永不停止的震荡下去。所以,

还需要对波幅数据进行衰减处理,让每一个点在经过一次计算后,波幅都比理想值按一定的比例降低。这个衰减率经过

测试,用1/32比较合适,也就是1/2^5。可以通过移位运算很快的获得。


渲染函数:

在程序中,用一个页面装载原始的图象,用另外一个页面来进行渲染。取得指向页面内存区的指针,然后用根据偏

移量将原始图象上的每一个象素复制到渲染页面上。进行页面渲染.



void CRipple::WaveRender()
{
	int iPtrSource = 0;
	int iPtrRender = 0;
	int lineIndex = m_iBmpWidth;
	int iPosX = 0;
	int iPosY = 0;
       //扫描位图
	for(int y = 1; y < m_iBmpHeight - 1; y++)
	{
		for(int x = 0; x < m_iBmpWidth; x++)
		{
			//根据波幅计算位图数据偏移值,渲染点(x,y)对应原始图片(iPosX,iPosY)
// 			iPosX = x + (m_pWaveBuf1[lineIndex - 1] - m_pWaveBuf1[lineIndex + 1]);
// 			iPosY = y + (m_pWaveBuf1[lineIndex - m_iBmpWidth] - m_pWaveBuf1[lineIndex + m_iBmpWidth]);
			//另外一种计算偏移的方法
			int waveData = (1024 - m_pWaveBuf1[lineIndex]);
			iPosX = (x - m_iBmpWidth/2)*waveData/1024 + m_iBmpWidth/2;
			iPosY = (y - m_iBmpHeight/2)*waveData/1024 + m_iBmpHeight/2;

			if(0 <= iPosX && iPosX < m_iBmpWidth &&
			   0 <= iPosY && iPosY < m_iBmpHeight)
			{
				//分别计算原始位图(iPosX,iPosY)和渲染位图(x,y)对应的起始位图数据
				iPtrSource = iPosY*m_iBytesPerWidth + iPosX*3;
				iPtrRender = y*m_iBytesPerWidth + x*3;
				//渲染位图,重新打点数据
				for(int c = 0; c < 3; c++)
				{
					m_pBmpRender[iPtrRender + c] = m_pBmpSource[iPtrSource + c];
				}
			}
                        lineIndex++;
		}
	}
        //设置渲染后的位图
	SetDIBits(m_hRenderDC, m_hRenderBmp, 0, m_iBmpHeight, m_pBmpRender, &m_stBitmapInfo, DIB_RGB_COLORS);
}



增加波源:


我们必须在水池中加入波源,你可以想象成向水中投入石头,形成的波源的大小和能量与石头的半径和你扔石头的力量都有关系。知道了这些,那么好,我们只要修改波能数据缓冲区m_pWaveBuf1,让它在石头入水的地点来一个负的“尖脉冲”.


void CRipple::DropStone(int x, int y, int stoneSize, int stoneWeight)
{
	int posX = 0;
	int posY = 0;
	for(int i = -stoneSize; i < stoneSize; i++)
	{
		for(int j = -stoneSize; j < stoneSize; j++)
		{
			posX = x + i;
			posY = y + j;

			//控制范围,不能超出图片
			if(posX < 0 || posX >= m_iBmpWidth ||
			   posY < 0 || posY >= m_iBmpHeight)
			{
				continue;
			}
			//在一个圆形区域内,初始化波能缓冲区1
			if(i*i + j*j <= stoneSize*stoneSize)
			{
				m_pWaveBuf1[posY*m_iBmpWidth + posX] = stoneWeight;
			}
		}
	}
}