向量几何在游戏编程中的使用.docx
- 文档编号:30013453
- 上传时间:2023-08-04
- 格式:DOCX
- 页数:50
- 大小:121.07KB
向量几何在游戏编程中的使用.docx
《向量几何在游戏编程中的使用.docx》由会员分享,可在线阅读,更多相关《向量几何在游戏编程中的使用.docx(50页珍藏版)》请在冰豆网上搜索。
向量几何在游戏编程中的使用
向量几何在游戏编程中的使用
-Twinsen编写
AndreLamothe说:
“向量几何是游戏程序员最好的朋友”。
一点不假,向量几何在游戏编程中的地位不容忽视,因为在游戏程序员的眼中,显示屏幕就是一个坐标系,运动物体的轨迹就是物体在这个坐标系曲线运动结果,而描述这些曲线运动的,就是向量。
使用向量可以很好的模拟物理现象以及基本的AI。
<1>简单的2-D追踪
AndreLamothe说:
“向量几何是游戏程序员最好的朋友”。
一点不假,向量几何在游戏编程中的地位不容忽视,因为在游戏程序员的眼中,显示屏幕就是一个坐标系,运动物体的轨迹就是物体在这个坐标系曲线运动结果,而描述这些曲线运动的,就是向量。
使用向量可以很好的模拟物理现象以及基本的AI。
现在,先来点轻松的,复习一下中学知识。
向量v(用粗体字母表示向量)也叫矢量,是一个有大小有方向的量。
长度为1的向量称为单位向量,也叫幺矢,这里记为E。
长度为0的向量叫做零向量,记为0,零向量没有确定方向,换句话说,它的方向是任意的。
一、向量的基本运算
1、向量加法:
a+b等于使b的始点与a的终点重合时,以a的始点为始点,以b的终点为终点的向量。
2、向量减法:
a-b等于使b的始点与a的始点重合时,以b的终点为始点,以a的终点为终点的向量。
3、数量乘向量:
k*a,k>0时,等于a的长度扩大k倍;k=0时,等于0向量;k<0时,等于a的长度扩大|k|倍然后反向。
4、向量的内积(数量积、点积):
a.b=|a|*|b|*cosA等于向量a的长度乘上b的长度再乘上a与b之间夹角的余弦。
它的几何意义就是a的长度与b在a上的投影长度的乘积,或者是b的长度与a在b上投影长的乘积,它是一个标量,而
且可正可负。
因此互相垂直的向量的内积为0。
5、向量的矢积(叉积):
axb=|a|*|b|*sinA*v=c,|a|是a的长度,|b|是b的长度,A是a和b之间的锐夹角,v是与a,b所决定的平面垂直的幺矢,即axb与a、b都垂直。
a,b,c构成右手系,即右手拇指伸直,其余四指按由a到b的锐角蜷曲,此时拇指所指方向就是c的方向。
因此axb!
=bxa,bxa是手指朝b到a的锐角蜷曲时,拇指指向的方向,它和c相反,即-c。
axb的行列式计算公式在左右手坐标系下是不同的,如上图所示。
两个向量的矢积是一个向量。
6、正交向量的内积:
互相垂直的两个向量是正交的,正交向量的内积为零。
a.b=|a|.|b|*cos(PI/2)=|a|.|b|*0=0。
二、向量的性质
没有下面的这些性质做基础,我们后面向量技巧的推导将无法进行。
1)a+b=b+a
2)(a+b)+c=a+(b+c)
3)a+0=0+a=a
4)a+(-a)=0
5)k*(l*a)=(k*l)*a=a*(k*l)
6)k*(a+b)=k*a+k*b
7)(k+l)*a=k*a+l*a
8)1*a=a
9)a.b=b.a
10)a.(b+c)=a.b+a.c
11)k*(a.b)=(k*a).b=a.(k*b)
12)0.a=0
13)a.a=|a|^2
三、自由向量的代数(分量)表示
1、向量在直角坐标中的代数表示方法:
a=(x,y)
其中x,y分别是向量在x轴和y轴上的分量。
任何一个在直角坐标轴上的分量为(x,y)的向量都相等。
比如上图中的每个向量都表示为(-2,1)。
或者写成a=x*i+y*j,即i和j的线性组合,这里i是x轴方向的单位向量(1,0),j是y轴方向的单位向量(0,1),因此i正交于j。
任意一个2-D向量都可以表成i与j的线性组合。
|i|=|j|=1
2、向量的代数(分量)表示的运算:
向量加法分量表示:
a+b=(xa,ya)+(xb,yb)=(xa+xb,ya+yb)
向量减法分量表示:
a-b=(xa,ya)-(xb,yb)=(xa-xb,ya-yb)
向量的内积(数量积、点积)分量表示:
a.b
=(xa*i+ya*j).(xb*i+yb*j)
=xa*i*xb*i+xa*i*yb*j+ya*j*xb*i+ya*j*yb*j
=(xa*xb)*(i*i)+(xa*yb)*(i*j)+(xb*ya)*(i*j)+(ya*yb)*(j*j)
=xa*xb+ya*yb
3、向量长度(模)的计算以及单位化(归一化):
设a=(x,y),则
|a|=|(x,y)|=|x*i+y*j|=sqrt(x^2*i^2+y^2*j^2)=sqrt(x^2+y^2),这里sqrt是开平方符号。
a的单位向量为a/|a|,即(x,y)/sqrt(x^2+y^2)。
四、简单的2-D追踪
现在,有了向量的基本知识,我们就可以分析一个常见的问题-屏幕上一点到另一点的追踪,其实这一问题也可理解为画线问题,画线的算法有很多:
DDA画线法、中点画线法以及高效的Bresenham算法。
但这些算法一般只是画一些两端固定的线段时所使用的方法,再做一些动态的点与点之间的跟踪时显得不很灵活。
使用向量的方法可以很好的解决此类问题。
现在假设你正在编写一个飞行射击游戏,你的敌人需要一种很厉害的武器-跟踪导弹,这种武器在行进的同时不断的修正自己与目标之间的位置关系,使得指向的方向总是玩家,而不论玩家的位置在哪里,这对一个水平不高的玩家(我?
)来说可能将是灭顶之灾,玩家可能很诧异敌人会拥有这么先进的秘密武器,但对于你来说只需要再程序循环中加入几行代码
它们的原理是向量的单位化和基本向量运算。
首先我们要知道玩家的位置(x_player,y_player),然后,我们的导弹就可以通过计算得到一个有初始方向的速度,速度的方向根据玩家的位置不断修正,它的实质是一个向量减法的计算过程。
速度的大小我们自己来设置,它可快可慢,视游戏难易度而定,它的实质就是向量单位化和数乘向量的过程。
具体算法是:
导弹的更新速度(vx_missile,vy_missile)=玩家的位置(x_player,y_player)-导弹的位置(x_missile,y_missile),然后再对(vx_missile,vy_missile)做缩小处理,导弹移动,判断是否追到玩家,重新更新速度,缩小...
看一下这个简单算法的代码:
//假设x_player,y_player是玩家位置分量
//x_missile,y_missile是导弹位置分量
//xv_missile,yv_missile是导弹的速度分量
//让我们开始吧!
floatn_missile;//这是玩家位置与导弹位置之间向量的长度
floatv_rate;//这是导弹的速率缩放比率
//计算一下玩家与导弹之间的位置向量
xv_missile=x_player-x_missile;//向量减法,方向由导弹指向玩家,x分量
yv_missile=y_player-y_missile;//y分量
//计算一下它的长度
n_missile=sqrt(xv_missile*xv_missile+yv_missile*yv_missile);
//归一化导弹的速度向量:
xv_missile/=n_missile;
yv_missile/=n_missile;
//此时导弹的速率为1,注意这里用速率。
//导弹的速度分量满足xv_missile^2+yv_missile^2=1
//好!
现在导弹的速度方向已经被修正,它指向玩家。
//由于现在的导弹速度太快,为了缓解一下紧张的气氛,我要给导弹减速
v_rate=0.2f;//减速比率
xv_missile*=v_rate;//这里的速率缩放比率,你可以任意调整大小
yv_missile*=v_rate;//可以加速:
v_rate大于1;减速v_rate大于0小于1,这里就这么做!
//导弹行进!
导弹勇敢的冲向玩家!
x_missile+=xv_missile;
y_missile+=yv_missile;
//然后判断是否攻击成功
现在,你编写的敌人可以用跟踪导弹攻击玩家了。
你也可以稍加修改,变为直线攻击武器。
这样比较普遍。
基本的跟踪效果用向量可以很好的模拟。
此时,我们只用到了所述向量知识的很少的一部分。
其他的知识会慢慢用到游戏中。
这次先介绍到这里。
下次我将说说利用向量模拟2-D物体任意角度返弹的技巧:
)但是!
别忘了复习一下向量的基础知识,我们要用到它们。
<2>2-D物体任意角度的反弹
第一次我说了一下向量知识的基础内容和一点使用技巧,浅显的展示了它在游戏编程中的作用。
这次深入一些,充分利用向量的性质模仿一个物理现象。
首先,我要介绍一下将要使用的两个基本但非常重要的技巧。
一、求与某个向量a正交的向量b
根据向量内积的性质以及正交向量之间的关系,有:
设a=(xa,ya),b=(xb,yb)
a.b=0
=>xa*xb+ya*yb=0
=>xa*xb=-ya*yb
=>xa/-ya=yb/xb
=>xb=-ya,yb=xa或xb=ya,yb=-xa
则向量(xa,ya)的正交向量为(xb,yb)=(-ya,xa)
比如上图中,向量(2,3)的逆时针旋转90度的正交向量是(-3,2),顺时针旋转90度的正交向量为(3,-2)。
这样,任给一个非零向量(x,y),则它相对坐标轴逆时针转90度的正交向量为(-y,x),顺时针转90度的正交向量为(y,-x)。
二、计算一个向量b与另一向量a共线的两个相反的投影向量
我们看一下上面的图,很明显,cosA(A=X)关于y轴对称,是偶函数,因此cosA=cos(-A),
又因为cosA是周期函数,且周期是2*PI,则有cos(A+2*PI)=cosA=cos(-A)=cos(-A+2*PI),
则根据cosA=cos(2*PI-A)以及a.b=|a|*|b|*cosA,有
a.b=|a|*|b|*cosA=|a|*|b|*cos(2*PI-A)
现在,根据上图,就有a.b=|a|*|b|*cosA=|a|*|b|*cos(2*PI-A)=ax*bx+ay*by
按照这个规则,当上面的b与c的模相等时,有|a|*|b|=|a|*|c|,进一步的,当它们与a的夹角A=B时,就有
a.b=|a|*|b|*cosA=|a|*|c|*cosB=a.c,相应的有
a.b=|a|*|b|*cosA=|a|*|b|*cos(2*PI-A)=|a|*|c|*cosB=|a|*|c|*cos(2*PI-B)=a.c也就是
ax*bx+ay*by=ax*cx+ay*cy
我们还注意到在一个周期内,比如在[0,2*PI]中,cosA有正负两种情况,分别是:
在(0,PI/2)&(3*PI/2,2*PI)为正,在(PI/2,3/2*PI)为负。
好,知道了这件事情之后,再看a.b=|a|*|b|*cosA,|a|和|b|都为正,所以a.b的正负性就由cosA决定,换句话说,a.b与它们夹角A的余弦cos有相同的符号。
所以,还看上面的图,我们就有:
1)当A在(0,PI/2)&(3*PI/2,2*PI)中,此时2*PI-A在(-PI/2,0)&(0,PI/2)中,a.b为正
2)当A在(PI/2,3*PI/2)中,此时2*PI-A也在(PI/2,3*PI/2)中,a.b为负
现在我们再来看一下同模相反(夹角为PI)向量b和b'与同一个向量a的两个内积之间有什么关系。
首先B+B'=2*PI-PI=PI,所以有b=-b',b'=-b,即
(bx,by)=(-b'x,-b'y)=-(b'x,b'y)
(b'x,b'y)=(-bx,-by)=-(bx,by)
所以
a.b=(ax,ay).(bx,by)=(ax,ay).-(b'x,b'y)=a.-b'=-(a.b')
a.b'=(ax,ay).(b'x,b'y)=(ax,ay).-(bx,by)=a.-b=-(a.b)
我们看到,一个向量b的同模相反向量b'与向量a的内积a.b',等于b与a的内积的相反数-(a.b)。
好,有了上面的基础,我们就可以求一个向量b与另一向量a共线的两个相反的投影向量c和c'了。
要求b在a上的投影向量c,我们可以用一个数乘上一个单位向量,这个单位向量要和a方向一至,我们记为a1。
而这个数就是b在a上的投影长。
先来求单位向量a1,我们知道它就是向量a乘上它自身长度的倒数(数乘向量),它的长度我们
可以求出,就是m=sqrt(ax^2+ay^2),所以a1就是(ax/m,ay/m),记为(a1x,a1y)。
再求投影长/c/(注意//与||的区别,前者是投影长,可正可负也可为零,后者是实际的长度,衡为非负)。
根据内积的几何意义:
一个向量b点乘另一个向量a1,等于b在a1上投影长与a1的长的乘积。
那我们要求b在a上的投影长,就用它点乘a的单位向量a1就可以了,因为单位向量的长度为1,b的投影长/c/乘上1还等于投影长自身,即:
/c/=b.a1=(bx,by).(a1x,a1y)=bx*a1x+by*a1y
好,我们得到了c的投影长,现在就可以求出c:
c=/c/*a1=((bx*a1x+by*a1y)*a1x,(bx*a1x+by*a1y)*a1y)
总结一下,就是c=(b.a1)*a1。
我们看到,b与a1的夹角在(0,PI/2)之间,因此它们的点积/c/是个正值。
因此当它乘a1之后,得到向量的方向就是a1的方向。
现在来看b',它是b的同模相反向量,它和a1的夹角在(PI/2,3*PI/2)之间,因此b'点乘a1之后得到/c'/是个负值,它再乘a1,得到向量的方向和a1相反。
我们知道,一个向量b的同模相反向量b'与向量a的内积a.b',等于b与a的内积的相反数-(a.b)。
因此,/c'/=-/c/,也就是说,它们的绝对值相等,符号相反。
因此它们同乘一个a1,得到的的两个模相等向量c与c'共线。
让我们把它完成:
(b'.a1)=-(b.a1)
=>-(b'.a1)=(b.a1),好,代入c=(b.a1)*a1,得到
c=-(b'.a1)*a1
=>(b'.a1)*a1=-c=c'
c=(b.a1)*a1=(-b'.a1)*a1
c'=(b'.a1)*a1=(-b.a1)*a1
至此为止,我们得出结论:
当一个向量b与另一个向量a的夹角在(0,PI/2)&(3*PI/2,2*PI)之间,它在a方向上的投影向量c就是c=(b.a1)*a1,其中a1是a的单位向量;它在a相反方向的投影向量c'是c'=(b'.a1)*a1,其中向量b'是b的同模相反向量。
相反的,也可以这样说:
当一个向量b'与另一个向量a的夹角在(PI/2,3*PI/2)之间,它在a相反方向上的投影向量c'是
c'=(b'.a1)*a1,其中a1是a的单位向量;它在a方向上的投影向量c是c=(b.a1)*a1。
其中向量b是b'的同模相反向量。
特别的,点乘两个单位向量,得到它们夹角的余弦值:
E.E=|E|*|E|*cosA=1*1*cosA=cosA
好了,可完了。
现在就可以看一下
三、使用向量模拟任意角度反弹的原理
根据初等物理,相互接触的物体在受到外力具有接触面相对方向相对运动趋势的时候,接触面会发生形变从而产生相互作用的弹力。
弹力使物体形变或形变同时运动形式发生改变。
在知道了这件事情之后,我们开始具体讨论下面这种情况:
矩形框和小球碰撞,碰撞时间极短,墙面无限光滑从而碰撞过程没有摩擦,碰撞时间极短,没有能量损失...总之是一个理想的物理环境。
我们在这种理想环境下讨论,小球与墙面发生了完全弹性碰撞,且入射角和反射角相等:
A=A',B=B',C=C',...。
虚线是法线,它和墙面垂直。
小球将在矩形框中永无休止的碰撞下去,且每次碰撞过程中入射角和反射角都相等。
我们再具体点,现在假设上面那个矩形墙壁的上下面平行于x轴,左右面平行于y轴。
这样太好了,我们在编写程序的时候只要判断当球碰到上下表面的时候将y方向速度值取返,碰到左右表面时将x方向速度值取返就行了,这种方法常常用在简单物理模型和规则边界框的游戏编程上,这样可以简化很多编程步骤,编写简单游戏时可以这样处理。
可事实不总是像想向中的那么好。
如果情况像下面这样:
虽然在碰撞过程中入射角仍然等于反射角,但是边界的角度可没那么“纯”了,它们的角度是任意的,这样就不能简单的将x方向或者y方向的速度取返了,我们要另找解决办法。
我们现在的任务是:
已知物体的速度向量S和边界向量b,求它的反射向量F。
我们先来看一下在碰撞过程中都有哪些向量关系:
设b是障碍向量,S是入射速度向量,F是反射速度向量,也就是我们要计算的向量。
A是入射角度,A'是反射角度,A=A'。
N是b的法向量,即N垂直于b。
n是与N共线的向量,n'是N方向的单位向量。
T是垂直于N的向量。
根据向量加法,现在有关系:
(1)S+n=T
(2)n+T=F
合并,得
F=2*T-S
我们已经找到了计算F的公式了。
这里S是已知的,我们要计算一下T,看
(1)式:
T=S+n
要计算T,S是已知的,就要计算一下n。
我们知道,n是S在N方向上投影得到的,S已知所以要得到n就要再计算一下N,而N又是和b垂直的。
还记得刚才我们导出的使用向量的两个技巧吧,这里我们都要用到:
1、任给一个非零向量(x,y),则它相对坐标轴逆时针转90度的垂直向量为(-y,x),顺时针转90度垂直向量为(y,-x)。
2、当一个向量b与另一个向量a的夹角在(0,PI/2)&(3*PI/2,2*PI)之间,它在a方向上的投影向量c就是c=(b.a1)*a1,其中a1是a的单位向量;它在a相反方向的投影向量c'是c'=(b'.a1)*a1,其中向量b'是b的同模相反向量。
我们知道了b,用技巧1可以计算出N。
然后归一化N计算出n',再用技巧2,这里S和n'之间的夹角在(PI/2,3*PI/2)中,因此要想用c=(b.a1)*a1,必须要使b=-S,a1=n'。
这样就计算出了n。
然后根据上面的
(1)式计算出T,好了,有了T和F=2*T-S,你就拥有了一切!
计算出的F就是物体碰撞后的速度向量,在2-D中它有两个分量x和y,3-D中有x,y,z三个分量。
这里也证明了使用向量的一个好处就是在一些类似这样关系推导过程中不用去考虑坐标问题,而直接的用简单的向量就可以进行。
这里注意我们的障碍向量b在实际的编程中是用障碍的两个端点坐标相减计算出的,计算的时候不需要考虑相减的顺序问题。
因为虽然用不同的相减顺序得到b的方向相反,且计算得到的单位法向量n'方向也相反(看上图的虚线部分),但是当用-S去点乘单位法向量n'之后得到的值也是相反的,它有一个自动的调节功能:
现在假设以b为界,S一侧为正方向。
则如果单位法向量n'是正方向,与-S点积值也是正,正的n'再乘正点积得正的n;如果单位法向量为负方向,与-S点积值也为负值,负的n'再乘负的点积得到的n为正方向。
总之n的方向是不变的,算出的F当然也是不变的。
四、编码实现它
现在我想编码实现它,但之前有一点我想说一下,可能读者已经想到了,在反弹之前我们要先判断什么时候开始反弹,也就是什么时候碰撞,这是一个碰撞检测问题,本来这是我们应该先要解决的问题,但我想把它放到下一次在具体说,所以这里的编码省略碰撞检测的一步,直接计算反弹速度向量!
目的是把上述理论迅速用到算法中去。
//在游戏循环中
//移动的物体简化为质点,位置是x=0.0f,y=0.0f
//质点速度向量的分量是Svx=4.0f,Svy=2.0f
//障碍向量是bx=14.0f-6.0f=8.0f,by=4.0f-12.0f=-8.0f
//则障碍向量的垂直向量是Nx=-8.0f,Ny=-8.0f
//这里可以加入碰撞检测
//现在假设已经碰撞完毕,开始反弹计算!
//计算N的长度
floatlengthN=sqrt(Nx*Nx+Ny*Ny);
//归一化N为n'
floatn0x=Nx/lengthN;//n0x就是n'的x分量
floatn0y=Ny/lengthN;//n0y就是n'的y分量
//计算n,就是S在N方向上的投影向量
//根据b'=(-b.a1').a1',有n=(-S.n').n'
floatnx=-(Svx*n0x+Svy*n0y)*n0x;//n的x分量
floatny=-(Svx*n0x+Svy*n0y)*n0y;//n的y分量
//计算T
//T=S+n
floatTx=Svx+nx;//T的x分量
floatTy=Svy+ny;//T的y分量
//有了T,有了F=2*T-S,好了,你现在拥有一切了
//计算F
floatFx=2*Tx-Svx;//F的x分量
floatFy=2*Ty-Svy;//F的y分量
//现在已经计算出了反弹后的速度向量了
//更新速度向量
Svx=Fx;
Svy=Fy;
//质点移动
x+=Svx;
y+=Svy;
//现在你就可以看到质点被无情的反弹回去了
//而且是按照物理法则在理想环境下模拟
就是这么简单,一个物理现象就可以模拟出来,但是还不完善,只是针对直线障碍,且没有碰撞检测,下次分析一下后者,还是用向量的知识。
这次先到这,Seeunexttime!
<3>2-D边界碰撞检测
-Twinsen编写
-本人水平有限,疏忽错误在所难免,还请各位数学高手、编程高手不吝赐教
-我的Email-address:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 向量 几何 游戏 编程 中的 使用