池塘夜降彩色雨
作者:中科大少年班 陈辉
作者主页:http://go2debug.yeah.net
下载本文示例源代码
程序运行效果图如下:

一、问题描述:
本程序是利用MFC制作的微软基础类应用程序,目的是模拟彩色雨滴滴落到池塘的情景,达到彩色雨滴从天而降,入水有声(尽管不怎么好听),产生圈圈涟漪。
二、数据结构:
主要的数据类型有两个带头结点的双向循环链表,这两个链表与MFC应用程序自动生成的对象类型混合使用,如下:
typedef struct { //单个雨滴
COLORREF color;//雨滴颜色
bool visibility; //可见性
float radius; //半径
float x;//雨滴中心位置 x
float y;//雨滴中心位置 y
float xvelocity;//雨滴速率 vx
float yvelocity;//雨滴速率 vy
} droplet;
struct dropletchain {//所有雨滴组成的链表
struct dropletchain * pre;
droplet * drop;
struct dropletchain * next;
};
typedef struct {//单个涟漪
COLORREF color;//颜色
float xdrop;//涟漪中心 x
float ydrop;//涟漪中心 y
float radius;//涟漪半径
int shown;//是否绘制涟漪(这个参数在判断是否需要重绘时用到)
}ripple;
struct ripplechain {// 所有涟漪组成的链表
struct ripplechain * pre;
ripple * aripple;
struct ripplechain * next;
};
对链表的操作混杂在类CCrainDlg(mfc 的对话框类)中。
三、大致的程序流程:
a) 在程序的初始化阶段 定义了两个链表
struct dropletchain dc;
struct ripplechain rc;
这两个都是空的链表,且dc.drop=NULL;
dc.pre=&dc;
dc.next=&dc;
rc.aripple=NULL;
rc.pre=&rc;
rc.next=&rc;
b) 当点击“rain please "按钮开始绘图时,抛出一个windows子线程,这个线程负责图形的绘制工作,而主程序线程负责参数的调节和显示,以及流程的转移(如关闭子线程)。
c) 子线程的主要部分是个无限循环(直到主线程发出退出信号:通过改变重载了的CCrainDlg类的endthread参数值)
while(!this->endthread )
{
draw();
draw();
drive();
}
第一个draw函数将图形(包括雨滴和涟漪)绘制出,第二个draw函数重绘一边,由于设置绘图模式为xor
PaintWindDC->SetROP2(R2_XORPEN);
所以第二个draw将绘好的图形擦去,由于视觉暂留,形成动画。
d) drive是驱动函数,负责对每个"元素"(包括雨滴和涟漪)的状态进行分析以得到下个时刻的状态,并随机产生新的雨滴:
//这是处理雨点程序的主要部分
adropchain=dc.next;
while(adropchain!=&dc)
{
adrop=adropchain->drop;
adrop->x+=(adrop->xvelocity+xacce) ;//计算下个时刻的雨点位置
adrop->y+=(adrop->yvelocity+yacce) ;
if(adrop->x>aRect.right ||
adrop->x<aRect.left ||
adrop->y<aRect.top||
adrop->y>aRect.bottom )//分析雨点是否越界,是则删去
{
adropchain->next->pre=adropchain->pre;
adropchain=adropchain->next;
delete adropchain->pre->next->drop ;
delete adropchain->pre->next ;
adropchain->pre->next=adropchain;
this->m_dropnumber --;
sprintf(s,"%d",m_dropnumber);
this->SetDlgItemText(IDC_EDIT5,s);
}
else if(adrop->y>Rect1.top+40 && rand()%4==1)//雨点到达水面则删去此雨点并产生新涟漪
{
struct ripplechain * arc=new struct ripplechain;
ripple * aripple=new ripple;
arc->aripple =aripple;
aripple->color=adrop->color ;//删去的雨点和新生的涟漪颜色相同
aripple->radius =adrop->radius ;//初始半径也相同
aripple->xdrop =adrop->x;//初位置传递下来
aripple->ydrop =adrop->y;
aripple->shown =0;
arc->next=rc.next ;
arc->pre =&rc;
rc.next->pre =arc;
rc.next=arc;
adropchain->next->pre=adropchain->pre;
adropchain=adropchain->next;
delete adropchain->pre->next->drop ;
delete adropchain->pre->next ;
adropchain->pre->next=adropchain;
this->m_dropnumber --;
sprintf(s,"%d",m_dropnumber);
this->SetDlgItemText(IDC_EDIT5,s);
this->m_ripplenumber ++;
sprintf(s,"%d",m_ripplenumber);
this->SetDlgItemText(IDC_EDIT6,s);
if(rand()%10==1 && soundon)
Beep(4000,1); //落水有声
}
else//对下个雨点进行判断
{
adropchain=adropchain->next;
}
//下面是处理涟漪的程序的主要部分
aripplechain=rc.next;
while(aripplechain!=&rc)
{
aripple=aripplechain ->aripple;
if(aripple->radius>40 && aripple->shown ==0)//判断是否应删去此涟漪
{
aripplechain->next->pre=aripplechain->pre;
aripplechain=aripplechain->next;
delete aripplechain->pre->next->aripple ;
delete aripplechain->pre->next ;
aripplechain->pre->next=aripplechain;
this->m_ripplenumber --;
sprintf(s,"%d",m_ripplenumber);
this->SetDlgItemText(IDC_EDIT6,s);
}
else
{
aripple->radius +=1;//涟漪扩展
aripplechain=aripplechain->next;//对下个涟漪进行判断
}
}
e) 风的引入
这个程序对风进行即时的调节:
当鼠标在绘图区之外时无风;
当鼠标在绘图区之内时风矢量的起点在绘图区中心,终点在鼠标所指点,并且“风力”和“风向”参数在右侧显示出来。
风的实现是通过主线程改变CCrainDlg类的xacce和yacce的值来使绘图子线程得到相应的,因此这是即时的响应,不断改变鼠标位置可以使雨点曲线下落。
四、调试分析
a )数据类型很简单,对其的操作主要是遍历和插入以及删除,遍历的时间复杂度O(n)其它两个是O(1)
b )主要的难点在
线程控制,使程序达到参数即时可控,同时提高程序的健壮性
动画绘制,为了不产生抖动,尽可能不引入过多的函数,因为函数太多会带来系统栈操作的开销
windows图形处理,由于mfc绘制图形要很多“设备”所以比较麻烦,我曾没把将用过的CDC类型设备release掉而使计算机内存以每秒大约1兆的速率被吞噬掉,系统内存资源很快用光。
c )通过这个程序,较好地理解了前台处理与后台“结构”之间地关系,只要数据结构设计合理,关键在前台,但前台有时会制约后台(入为了提高速度以达到“即时响应“地目的)
d )通过这个程序,熟练了mfc的编程。
五、使用说明
点击"rain please"按钮即可进行绘图。鼠标的位置可以控制风的大小和方向.一些数据即时显示在右方.点击"hear
rain"即可听到雨声。 点击"exit"退出。
作者简介:
中国科学技术大学少年班 陈辉 PB00004006
邮箱:go2debug@hotmail.com
主页:go2debug.yeah.net
作者近影:
|