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 here
SetTimer(1,30,NULL);
}
void CMoveSpringView::OnMStop()
{
// TODO: Add your command handler code here
KillTimer(1);
}
10、最核心的是通过OnTimer事件改变物体的位置。物体是按照重力和弹簧的拉力来决定它的运动规律,是一个变加速直线运动。(这里我们简单处理,按匀加速直线运动来考虑)。
11、添加时钟函数,操作步骤如下。
12、在OnTimer()里编程,代码如下。
void CMoveSpringView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
float 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*a
delta_s = 0.5*(m_object.v + m_object.v + m_object.a*delta_t)*delta_t; //s = v*t + (a*t*t)/2
m_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*t
Invalidate(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 default
bool m_pause; //键盘空格键控制
if(32 == nChar) //键盘上的空格键ASCII码为32
{
m_pause = !m_pause;
if(m_pause)
KillTimer(1);
else
SetTimer(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 default
return 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; //定义内存DC
int 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()函数里的设置。首先要想明白的一点就是:弹簧因何而动?要牢牢抓住在整个运动过程中,弹簧(矩形)右下角的竖坐标点位置一直在改变即可。还有一点就是,只需要通过弹簧(矩形)左上角位置、长度、宽度就能确定重物以及连接弹簧和重物之间连线的位置。
上一篇: JAVA之简单排序(冒泡、选择、插入)
下一篇: VC6.0编译jpeglib库
推荐阅读