VC6.0 MFC 模拟弹簧运动
模拟弹簧运动
一、内容描述
运用VC6.0新建工程MFC AppWizard(exe),创建单文档应用程序,画一个弹簧(用矩形代替),下面挂有重物(用圆球代替),设定重物质量和弹簧的弹性系数,模拟弹簧运动。
二、最终实现效果图(静态展示)
三、实现步骤及相关代码说明(详细到每一步)
1、新建一个工程,并取名为“MoveSpring”,步骤如下。
2、选择“单文档(S)”,点击“完成”,“确定”即可,如下图所示。
3、首先,定义两个结构体(弹簧结构体和重物结构体),如下图所示。
typedef struct{POINT posL; //定义弹簧左上角顶点(用矩形模拟弹簧)float len,width; //定义弹簧的长度和宽度float k; //定义弹簧的弹性系数float s; //定义弹簧被拉长的长度(拉长为正,压缩为负)}MySpring; //定义弹簧结构体typedef struct{POINT center; //定义重物(小球)中心int radius; //定义重物(小球)半径float m,v,a; //定义重物(小球)质量,速度,加速度}MyObject; //定义重物结构体
4、接下来就是绘制弹簧和重物,操作步骤如下。
①绘制弹簧
void CMoveSpringView::DrawSpring(CDC *pDC, MySpring spring){pDC->Rectangle(spring.posL.x,spring.posL.y,spring.posL.x + spring.width,spring.posL.y + spring.len); //画矩形}
②绘制重物
void CMoveSpringView::DrawObject(CDC *pDC, MyObject object){CBrush brush;brush.CreateSolidBrush(RGB(0,0,0)); //画黑色物体pDC->SelectObject(&brush);pDC->BeginPath();pDC->Ellipse(object.center.x - object.radius,object.center.y - object.radius,object.center.x + object.radius,object.center.y + object.radius); //画圆pDC->EndPath();pDC->FillPath();}
5、为了更好的显示重物挂在弹簧上面,我们画个线将两者连起来(当然也可以在顶端画两条线将弹簧挂起)。我们现在来绘制弹簧和重物之间的连线,操作步骤如下。
void CMoveSpringView::DrawLine(CDC *pDC, MySpring spring, MyObject object){pDC->MoveTo(spring.posL.x + spring.width/2,spring.posL.y + spring.len); //线的起点pDC->LineTo(object.center); //线的终点}
6、通过OnDraw()函数调用上面的函数,代码如下。
void CMoveSpringView::OnDraw(CDC* pDC){CMoveSpringDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data here//画悬挂弹簧的两条牵引线pDC->MoveTo(m_spring.posL.x - 50,0);pDC->LineTo(m_spring.posL.x,m_spring.posL.y);pDC->MoveTo(m_spring.posL.x + m_spring.width + 50,0);pDC->LineTo(m_spring.posL.x + m_spring.width,m_spring.posL.y);DrawSpring(pDC,m_spring); //画弹簧DrawObject(pDC,m_object); //画重物DrawLine(pDC,m_spring,m_object);//画弹簧和重物之间的连线}
7、在构造函数里对弹簧、重物、连线进行初始化(当然你也可以在OnInitialUpdate()里初始化),代码如下。
CMoveSpringView::CMoveSpringView(){// TODO: add construction code here m_spring.posL.x = 200; m_spring.posL.y = 100; m_spring.width = 100.0; m_spring.len = 200.0; //初始化弹簧形状大小 m_spring.s = 0.0; //弹簧拉伸的位移,向下为正,向上为负 m_spring.k = 100.0; //设定弹性系数(牛顿/米) m_object.center.x = m_spring.posL.x + m_spring.width/2;m_object.center.y = m_spring.posL.y + m_spring.len + m_spring.s + 50; //初始化重物 m_object.radius = 30; m_object.m = 3.0; //重物质量(千克) m_object.v = 30.0; //重物初速度}
8、此时我们就将静态效果做出来了,如下图所示。
9、为了让弹簧能够动起来,首先需要做的就是添加一个菜单来控制弹簧的运动。建立菜单并添加消息响应函数,如下图所示。
void CMoveSpringView::OnMStart() {// TODO: Add your command handler code hereSetTimer(1,30,NULL);}void CMoveSpringView::OnMStop() {// TODO: Add your command handler code hereKillTimer(1);}
10、最核心的是通过OnTimer事件改变物体的位置。物体是按照重力和弹簧的拉力来决定它的运动规律,是一个变加速直线运动。(这里我们简单处理,按匀加速直线运动来考虑)。
11、添加时钟函数,操作步骤如下。
12、在OnTimer()里编程,代码如下。
void CMoveSpringView::OnTimer(UINT nIDEvent) {// TODO: Add your message handler code here and/or call defaultfloat delta_s; //在0.01秒的时间段里,物体产生的位移的增量(有正有负)float delta_t = 0.01; m_object.a = (m_object.m*9.8 - m_spring.k*m_spring.s)/m_object.m; //m*g - k*s = m*adelta_s = 0.5*(m_object.v + m_object.v + m_object.a*delta_t)*delta_t; //s = v*t + (a*t*t)/2m_spring.s += delta_s;m_spring.len += m_spring.s; //计算弹簧长度的变化m_object.center.y = m_spring.posL.y + m_spring.len + 50;m_object.v = (m_object.v + m_object.a*delta_t); //v = v0 + a*tInvalidate(true);CView::OnTimer(nIDEvent);}
13、为方便控制,可添加一个键盘响应。比如,当按下键盘中的空格键(space)时,开始运动。添加消息句柄“WM_ KEYDOWN”如下。
14、在OnKeyDown()里添加如下代码。
void CMoveSpringView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {// TODO: Add your message handler code here and/or call defaultbool m_pause; //键盘空格键控制if(32 == nChar) //键盘上的空格键ASCII码为32{m_pause = !m_pause;if(m_pause)KillTimer(1);elseSetTimer(1,30,NULL);}CView::OnKeyDown(nChar, nRepCnt, nFlags);}
15、为了消除在运动过程中的闪动,可设置双缓存。先添加一个消息句柄“WM_ERASEBKGND”,并编码,如下图所示。
BOOL CMoveSpringView::OnEraseBkgnd(CDC* pDC) {// TODO: Add your message handler code here and/or call defaultreturn true;return CView::OnEraseBkgnd(pDC);}
16、最后只需在OnDraw()里做如下改动即可。
void CMoveSpringView::OnDraw(CDC* pDC){CMoveSpringDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data here/*//画悬挂弹簧的两条牵引线pDC->MoveTo(m_spring.posL.x - 50,0);pDC->LineTo(m_spring.posL.x,m_spring.posL.y);pDC->MoveTo(m_spring.posL.x + m_spring.width + 50,0);pDC->LineTo(m_spring.posL.x + m_spring.width,m_spring.posL.y);DrawSpring(pDC,m_spring); //画弹簧DrawObject(pDC,m_object); //画重物DrawLine(pDC,m_spring,m_object);//画弹簧和重物之间的连线*/CDC MemDC; //定义内存DCint width,height; //定义屏幕宽度、高度CRect rect; //建立rect对象CBitmap MemBitmap; //缓冲的内存位图GetWindowRect(&rect); //获取当前视图的大小width = rect.Width();height = rect.Height(); //记录当前屏幕大小MemDC.CreateCompatibleDC(NULL); //建立兼容内存DC(设备上下文),NULL为系统默认模式MemBitmap.CreateCompatibleBitmap(pDC,width,height);CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap); //保存之前的内存位图MemDC.FillSolidRect(0,0,width,height,RGB(240,255,255)); //设置背景颜色MemDC.SetBkMode(TRANSPARENT); //设置缓冲DC参数,为双缓存机制做准备//================================================DrawSpring(&MemDC,m_spring);DrawObject(&MemDC,m_object);DrawLine(&MemDC,m_spring,m_object);//在缓冲DC中画图pDC->BitBlt(0,0,width,height,&MemDC,0,0,SRCCOPY);MemBitmap.DeleteObject();MemDC.DeleteDC();//画悬挂弹簧的两条牵引线pDC->MoveTo(m_spring.posL.x - 50,0);pDC->LineTo(m_spring.posL.x,m_spring.posL.y);pDC->MoveTo(m_spring.posL.x + m_spring.width + 50,0);pDC->LineTo(m_spring.posL.x + m_spring.width,m_spring.posL.y);}
17、运行结果如下图所示。
四、总结
实现弹簧运动最关键的就是OnTimer()函数里的设置。首先要想明白的一点就是:弹簧因何而动?要牢牢抓住在整个运动过程中,弹簧(矩形)右下角的竖坐标点位置一直在改变即可。还有一点就是,只需要通过弹簧(矩形)左上角位置、长度、宽度就能确定重物以及连接弹簧和重物之间连线的位置。