嵌入式系统开发设计课程设计.docx
- 文档编号:4679815
- 上传时间:2022-12-07
- 格式:DOCX
- 页数:17
- 大小:298.63KB
嵌入式系统开发设计课程设计.docx
《嵌入式系统开发设计课程设计.docx》由会员分享,可在线阅读,更多相关《嵌入式系统开发设计课程设计.docx(17页珍藏版)》请在冰豆网上搜索。
嵌入式系统开发设计课程设计
嵌入式系统开发设计课程设计
设计题目软件中的典型算法分析
学院名称信息科学与技术学院
专业名称
学生姓名
学生学号
任课教师
设计成绩
教务处制
2015年3月24日
软件中的典型算法分析
摘要
此次课程设计为加农炮游戏,这是一款基于Linux平台,运用QtCreater软件开发的一款小游戏。
本文将从加农炮的设计及实现出发,分析在实现过程中的一些典型算法。
其中包括坐标转换算法、碰撞检测算法、边界检测算法、定时器驱动等。
关键词:
坐标转换算法;碰撞检测算法;边界检测算法;定时器驱动;QtCreater
1、加农炮整体实现流程
1.1加农炮设计与实现简述
这是一款基于Linux平台,运用QtCreater软件开发的一款加农炮小游戏。
游戏的界面为一个窗口,在窗口的右边从上到下以此是:
quit按钮,该按钮的作用是用于关闭游戏与窗口中×的作用相同;AngelLED显示屏,屏上显示的数据就是炮台当前的角度,下边是一个滚动滑条用于控制炮台的角度,炮台角度的范围是0度~70度,当滑条向左边滑动,炮台的角度变小,当滑条向右边滑动,炮台的角度变大;ForceLED显示屏,屏上显示的数据是炮台射出的炮弹的力度,下边的滑条用于控制炮台射出炮弹的速度,炮弹的速度范围是0~50,当滑条向右边滑动,炮弹的力度变大,当向左边滑动,炮弹的力度变小。
窗口的上边一栏从左到右分别是:
Shoot按钮,当用户按下这个按钮后炮台会发射一个炮弹,炮弹的轨迹遵循物理定理,将成一个抛物线运动;HitsLED显示屏,屏上的数据显示的是炮台发出的炮弹击中木块的次数,最多显示为15;ShootsLeftLED显示屏,屏上的数据显示的是炮台还可以发出的炮弹数,默认的初始值为15,最小为0;NewGame按钮,当用户按下这个按钮时,其将进入一个新游戏,所有的值都变为初始值。
窗口的右下方的大屏幕即为游戏显示界面,炮台位于游戏显示界面的左下角,在游戏显示界面的任意位置处有一个小木条。
当用户打开游戏界面后按下Shoot按钮时,炮台自动发射一个炮弹,如果炮弹击打到小木块,炮弹和小木块会同时消失,同时会在游戏显示界面的另外一个任意位置显示出另外一个小木块,如果用户发射出的炮弹没有击打到小木块,用户可以通过调整炮台的角度,发射炮弹的力度来调整炮弹的运动轨迹以此来使炮弹能够击打到小木块,当用户发射够15枚炮弹是,游戏显示界面会显示GameOver同时游戏显示界面的炮台和炮弹会消失,当没有发射完后,用户继续发射。
如果用户没有发射完炮弹就想进行新游戏,这个操作也是被允许的。
自定义窗口部件:
•lcdrange.h包含LCDRange类定义。
•lcdrange.cpp包含LCDRange类实现。
•cannon.h包含CannonField类定义。
•cannonfield.cpp包含CannonField类实现。
•main.cpp包含MyWidget和main。
1.2流程图
1.3加农炮模块分析及整体界面
1.3.1按钮
按钮是执行游戏界面的退出功能;
是执行炮弹的发射功能;
按钮式执行新游戏的功能。
1.3.2LED显示
击中木块数显示屏;
可发送炮弹数显示屏;
炮台角度显示屏及滑动条;
力度显示屏及滑动条。
其中,Angel和Force滚动条,两个作用分别用于控制炮台的角度与力度。
1.3.3加农炮界面
2、坐标转换算法
2.1坐标转换原理
我们已经能够使用QPainter的相关函数画出一些东西了。
接下来,我们要看的是QPainter的坐标系统。
同很多坐标系统一样,QPainter的默认坐标的原点(0,0)位于屏幕的左上角,X轴正方向是水平向右,Y轴正方向是竖直向下。
在这个坐标系统中,每个像素占据1x1的空间。
你可以把它想象成是一张坐标值,其中的每个小格都是1个像素。
这么说来,一个像素的中心实际上是一个“半像素坐标系”,也就是说,像素(x,y)的中心位置其实是在(x+0.5,y+0.5)的位置上。
因此,如果我们使用QPainter在(100,100)处绘制一个像素,那么,这个像素的中心坐标是(100.5,100.5)。
这种细微的差别在实际应用中,特别是对坐标要求精确的系统中是很重要的。
首先,只有在禁止反走样,也就是默认状态下,才会有这0.5像素的偏移;如果使用了反走样,那么,我们画(100,100)位置的像素时,QPainter会在(99.5,99.5),(99.5,100.5),(100.5,99.5)和(100.5,100.5)四个位置绘制一个亮色的像素,这么产生的效果就是在这四个像素的焦点处(100,100)产生了一个像素。
如果不需要这个特性,就需要将QPainter的坐标系平移(0.5,0.5)。
这一特性在绘制直线、矩形等图形的时候都会用到。
下图给出了在没有反走样技术时,使用drawRect(2,2,6,5)绘制一个矩形的示例。
在NoPen的情况下,请注意矩形左上角的像素是在(2,2),其中心位置是在(2.5,2.5)的位置。
然后注意下有不同的Pen的值的绘制样式,在Pen宽为1时,实际画出的矩形的面积是7x6的(图出自C++GUIProgrammingwithQt4,2ndEdition):
在具有反走样时,使用drawRect(2,2,6,5)的效果如下(图出自C++GUIProgrammingwithQt4,2ndEdition):
注意我们前面说过,通过平移QPainter的坐标系来消除着0.5像素的差异。
下面给出了使用drawRect(2.5,2.5,6,5)在反走样情况下绘制的矩形(图出自C++GUIProgrammingwithQt4,2ndEdition):
请对比与上图的区别。
在上述的QPainter的默认坐标系下,QPainter提供了视口(viewport)窗口(window)机制,用于绘制与绘制设备的大小和分辨率无关的图形。
视口和窗口是紧密的联系在一起的,它们一般都是矩形。
视口是由物理坐标确定其大小,而窗口则是由逻辑坐标决定。
我们在使用QPainter进行绘制时,传给QPainter的是逻辑坐标,然后,Qt的绘图机制会使用坐标变换将逻辑坐标转换成物理坐标后进行绘制。
通常,视口和窗口的坐标是一致的。
比如一个600x800的widget(这是一个widget,或许是一个对话框,或许是一个面板等等),默认情况下,视口和窗口都是一个320x200的矩形,原点都在(0,0),此时,视口和窗口的坐标是相同的。
注意到QPainter提供了setWindow()和setViewport()函数,用来设置视口和窗口的矩形大小。
比如,在上面所述的320x200的widget中,我们要设置一个从(-50,-50)到(+50,+50),原点在中心的矩形窗口,就可以使用painter.setWindow(-50,-50,100,100);
其中,(-50,-50)指明了原点,100,100指明了窗口的长和宽。
这里的“指明原点”意思是,逻辑坐标的(-50,-50)对应着物理坐标的(0,0);“长和宽”说明,逻辑坐标系下的长100,宽100实际上对应物理坐标系的长320,宽200。
或许你已经发现这么一个好处,我们可以随时改变window的范围,而不改变底层物理坐标系。
这就是前面所说的,视口与窗口的作用:
“绘制与绘制设备的大小和分辨率无关的图形”,如下图所示:
除了视口与窗口的变化,QPainter还提供了一个“世界坐标系”,同样也可以变换图形。
所不同的是,视口与窗口实际上是统一图形在两个坐标系下的表达,而世界坐标系的变换是通过改变坐标系来平移、缩放、旋转、剪切图形。
为了清楚起见,我们来看下面一个例子:
void PaintedWidget:
:
paintEvent(QPaintEvent*event)
{
QPainterpainter(this);
QFontfont("Courier",24);
painter.setFont(font);
painter.drawText(50,50, "Hello,world!
");
QTransformtransform;
transform.rotate(+45.0);
painter.setWorldTransform(transform);
painter.drawText(60,60, "Hello,world!
");
}
为了显示方便,我在这里使用了QFont改变了字体。
QPainter的drawText()函数提供了绘制文本的功能。
它有几种重载形式,我们使用了其中的一种,即制定文本的坐标然后绘制。
需要注意的是,这里的坐标是文字左下角的坐标(特别提醒这一点,因为很多绘图系统,比如Java2D都是把左上角作为坐标点的)!
下面是运行结果:
我们使用QTransform做了一个rotate变换。
这个变换就是旋转,而且是顺时针旋转45度。
然后我们使用这个变换设置了QPainter的世界坐标系,注意到QPainter是一个状态机,所以这种变换并不会改变之前的状态,因此只有第二个Hello,world!
被旋转了。
确切的说,被旋转的是坐标系而不是这个文字!
2.2坐标变换在加农炮中的实现
CannonField:
:
CannonField(QWidget*parent)
:
QWidget(parent)
{
r=QRect(0,0,0,0);
target=QRect(0,0,0,0);
timerCount=0;
currentAngle=45;
currentForce=45;
setPalette(QPalette(QColor(250,250,200)));
setAutoFillBackground(true);
}
voidCannonField:
:
paintEvent(QPaintEvent*/*event*/)
{
QPainterpainter(this);
painter.drawText(200,200,tr("Angle=")+QString:
:
number(currentAngle)+"\t"+tr("Force=")+QString:
:
number(currentForce));
painter.setPen(Qt:
:
NoPen);
painter.setBrush(Qt:
:
blue);
painter.drawRect(r);
painter.drawRect(target);
painter.translate(0,rect().height());
painter.drawPie(QRect(-35,-35,70,70),0,90*16);
painter.rotate(-currentAngle);
painter.drawRect(QRect(30,-5,20,10));
}
3、碰撞检测算法
3.1碰撞检测算法原理
目前碰撞检测算法主要有两种,一种是转化为判断线面的相交问题,另一种是建立在包围盒基础上,并对其不断完善的算法。
这两种算
法的原理为:
第一种:
以视点为起点,前进步长为长度形成线段,将此线段与场景中的所有可见面进行相交运算,如果有交点(相交),则表示发生了碰撞。
第二种:
如果是物体与场景之间的碰撞检测,则沿前进方向作一条射线,射线首先与包围盒检测相交,然后再作多变性相交检测;如果是三维实体之间的碰撞检测,给三维对象建立包围盒,计算包围盒在三维空间的相交情况,具体做法是先用包围盒(球)进行两两碰撞检测,如果碰撞了包围球,则用多边形碰撞检测方法进行计算,但场景实体很多的话,
这个方法的效率比较低。
传统的算法都是要给模型建立包围盒,包围盒分为盒状和球状,其中以球状的比较简单,只需记录圆心和半径即可检测碰撞,矩形包围盒相对比较麻烦。
但是,这两种用于检测碰撞不够精确,扩大了检测的区域,有些还没有碰撞的物体,也可能会被检测为已碰撞。
算法的提出主要是为了解决在场景中进行漫游时视点的碰撞问题,所以假定物体的运动轨迹为直线,并通过对模型生成不规则多面包围体来检测碰撞。
碰撞检测主要用来检测运动物体与场景内模型是否存在相互包含,运动物体可以是点也可以是实体。
此算法的设计,综合了包围盒(球)算法的优点,在一定程度上克服了它的弱点。
首先,选择运动物体的碰撞检测点,并记录碰撞检测点的坐标,作为碰撞检测的依据;然后,为模型建立不规则包围体;最后,进行几何计算检测碰撞的发生,主要分为三个步骤:
第一步,在运动物体上选取用于检测碰撞的点。
如果视点是一个点,那么我们直接记录其坐标即可;如果是一个实体我们需要选择其运动方向上最前面的点,具体做法是沿实体的前进方向做一条射线,确定任意一个与该射线垂直的平面α,改变平面α的在射线方向的位置,使其沿实体的前进方向靠近实体,计算平面α与实体的交点,并记录此交点,作为检测碰撞的点。
第二步,为模型建立包围盒。
以质心为中心,根据模型的点与质心的距离排列各点,选择距离质心最远的三个点建立一个三角形,然后查找一个距离质心第四远的点和第二、三远的点,建立三角形,依次类推,为模型建立不规则包围体。
第三步判断是否发生碰撞。
通过第一步我们已选择用于检测碰撞的点记为T(x,y,z),假如模型的不规则包围体有n个面组成,第i个面的方程为Ai*X+Bi*Y+Ci*Z+D=0即Ni*P+D=0,其中Ni=(Ai,Bi,Ci)为平面的法向量,P=(X,Y,Z)为平面中的点。
如果该平面的法向量指向前面的话,那么Ni*P+D>0,表示P点在平面前,反之,如果Ni*P+D<0,则表示此点在平面后。
将T带入此方程,对于构成这个凸多面体的所有平面,T点都在其后的话,那么T在这个多面体内部,反之,T点在其外部。
即判断构成模型的所有面Ni*T+D<0(i=1、2…n),是否成立,若成立则发生碰撞,反之,没有发生碰撞。
3.2碰撞检测算法在加农炮中的实现
if语句检查炮弹矩形和目标矩形是否相交。
如果是的,炮弹击中了目标(哎哟!
)。
我们停止射击定时器并且发射hit()信号来告诉外界目标已经被破坏,并返回。
注意,我们可以在这个点上创建一个新的目标,但是因为CannonField是一个组件,所以我们要把这样的决定留给组件的使用者。
}elseif(shotR.x()>width()||shotR.y()>height()){
autoShootTimer->stop();
emitmissed();
这个if语句和上一章一样,除了现在它发射missed()信号告诉外界这次失败。
}else{
函数的其余部分和以前一样。
CannonField:
:
paintEvent()isasbefore,exceptthatthishasbeenadded:
if(updateR.intersects(targetRect()))
paintTarget(&p);
这两行确认在需要的时候目标也被绘制。
voidCannonField:
:
paintTarget(QPainter*p)
{
p->setBrush(red);
p->setPen(black);
p->drawRect(targetRect());
}
这个私有函数绘制目标,一个由红色填充,有黑色边框的矩形。
QRectCannonField:
:
targetRect()const
{
QRectr(0,0,20,10);
r.moveCenter(QPoint(target.x(),height()-1-target.y()));
returnr;
}
这个私有函数返回封装目标的矩形。
从newTarget()中所得的target点使用0点在窗口部件的下边界的y。
我们在调用QRect:
:
moveCenter()之前在窗口坐标中计算这个点。
我们选择这个坐标映射的原因是在目标和窗口部件的下边界之间垂直距离。
记住这些可以让用户或者程序在任何时候都可以重新定义窗口部件的大小。
4、定时器驱动
4.1定时器驱动原理
Qt有两种定时器,一种是QObject类的定时器,另一种是QTimer类的定时器。
(1)QObject类的定时器
QObject类提供了一个基本的定时器,通过函数startTimer()来启动,通过killTimer()来结束,通过QTimerEvent来处理定时器事件。
intstartTimer(intinterval,Qt:
:
TimerTypetimerType=Qt:
:
CoarseTimer);
voidkillTimer(intid);
voidQObject:
:
timerEvent(QTimerEvent*event);
startTimer(intinterval)启动一个时间间隔为interval毫秒的定时器,返回一个定时器标识符,如果未能启动成功,则返回0。
该定时器只能使用killTime()来杀死,killTimer(intid)通过定时器标识符来杀死定时器。
如果有多个定时器,可以通过QTimerEvent:
:
timerId()来获取已经启动的定时器标识符。
(2)QTimer类的定时器
QTimer类定时器是QObject类定时器的扩展版或者说升级版,因为它可以提供更多的功能。
比如说,它支持单次触发和多次触发。
使用QTimer类定时器的步骤:
(a)创建一个QTimer定时器实例:
QTimer*timer=newQTimer(this);
(b)连接超时信号与槽:
connect(timer,SIGNAL(timeout()),this,SLOT(testFunc()));
(c)启动定时器start();
(d)适时关闭定时器:
stop();
(e)删除定时器实例:
deletetimer;
4.2定时器驱动在加农炮中的实现
在cannonField.cpp中添加代码。
添加#include
构造函数里添加代码:
QTimer*timer=newQTimer(this);
//新建定时器
connect(timer,SIGNAL(timeout()),this,SLOT(timerUpDate()));
//关联定时器计满信号和相应的槽函数
voidCannonField:
:
shoot()
{
m_nTimerId=startTimer(10);
}
//定时器开始计时,其中10表示10ms即1秒。
然后实现更新函数。
CannonField:
:
newTarget()
{
intx;
inty;
QTimetime;
time=QTime:
:
currentTime();
qsrand(time.msec()+time.second()*1000);
x=qrand()%480+160;
time=QTime:
:
currentTime();
qsrand(time.msec()+time.second()*1000);
y=qrand()%480;
target=QRect(QPoint(x,y),QPoint(x+15,y+15));
update();
}
5、边界检测算法
5.1边界检测算法原理
建立一个与图像色彩深度无关的一维存储轮廓信息矩阵存储图像中的边界点信息,为了便于进行8连通域法分析,将其设置为比原图像大一周,矩阵元素(x+1,y+1)的值即代表原图像中坐标(x,y)处的信息,将轮廓信息矩阵初始化为0(0表示无轮廓信息);建立一个区域轮廓坐标链表存储所有区域的轮廓坐标;建立一个区域轮廓坐标数目链表存储每个区域的轮廓坐标数目.本算法首先对原始图像进行扫描,提取图像中的区域轮廓信息并将其存入轮廓信息矩阵中的对应位置.将上下或左右像素均为背景色的特殊边界点像素在轮廓信息矩阵中对应位置的值赋为2,将其它普通边界点像素在轮廓信息矩阵中对应位置的值赋为1,在此过程中同步确定所有区域的总坐标范围(xmin,ymin)和(xmax,ymax),即有用信息区.然后从(xmin,ymin)到(xmax,ymax)对轮廓信息矩阵的有用信息区进行预处理:
将值为2的特殊边界点进一步区分为8个邻点均为空白的孤点、7个邻点均为空白的末梢点,以及宽度为1的孤线点.其中:
孤线点的值保持为2不变,以便追踪过程中可经过该点两次从而实现自动回溯;末梢点只需经过一次,因此其值应改为1,而孤点则直接追加到区域轮廓坐标链表和区域轮廓点数目链表中,并将其值置为0,使该信息消失.在轮廓追踪除了起点以外,每经过一个边界点就将其值减一,当边界点与起点重合时表示当前区域追踪完毕(闭合),将起点值也改为零,这样每处理一个区域,其轮廓的信息就会自动消失,从而达到“化难为易”的设计初衷。
将初始点的初值设为(xmin,ymin),进入轮廓追踪循环实现步骤如下.流程图:
(1)首先寻找新区域轮廓的起点.从轮廓追踪初始点开始,用扫描线方法从上到下、从左到右逐行、逐列扫描轮廓信息矩阵,直至遇到第一个大于0的值,将该处坐标作为新区域轮廓的起点和下一轮追踪的初始点.
(2)从新区域轮廓的起点开始,用八连通域法追踪该区域的轮廓,起始方向设定为向右(因为不可能有更高的点、也不可能有左边的点)。
(3)若起始方向所指的点的值大于0,即为下一个边界点,否则每次顺时针旋转45°检测邻点,直至旋转一周或找到其值为大于0的点.若在当前边界点的8个邻点中找到了其值为大于0的点,即可将其作为下一个边界点,同时,若当前边界点不是区域轮廓起点,则令其值减1(若原值为1则该信息消失).把从当前点到下一个边界点的方向逆时针旋转90°作为下一次检测的起始方向;若在当前边界点的8个邻点中未找到新的边界点,而当前区域边界点的数目已经大于2,说明遇到了被预处理遗漏的断点,此时需要用回溯方法找到新的边界点或返回当前区域轮廓的起点;否则将视为遗漏的孤点,从区域轮廓坐标链表的尾部删除其坐标信息并结束本次循环。
(4)重复步骤
(1)~(3)直至下一个边界点的坐标与起点重合,然后将起点的值置为0,使处理过的区域的信息消失。
(5)重复步骤
(1)~(4)直至扫描到坐标(xmax,ymax)处,此时图像已不会存在任何剩余区域。
6)根据链表信息创建区域并结束。
按照以上的边界检测算法,克服了传统算法遇到的问题;对不连通区域、连通区域、复连通区域进行了边界检测;对文字模板图像的边界检测。
5.2边界检测算法在加农炮中的实现
voidLCDRange:
:
setAngle(intvalue)
{
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式系统开发设计 课程设计 嵌入式 系统 开发 设计
![提示](https://static.bdocx.com/images/bang_tan.gif)