池塘夜降彩色雨
作者:中科大少年班 陈辉
作者主页: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;

};

对链表的操作混杂在类CCrainDlgmfc 的对话框类)中。

三、大致的程序流程:
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类的xacceyacce的值来使绘图子线程得到相应的,因此这是即时的响应,不断改变鼠标位置可以使雨点曲线下落。

四、调试分析

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