【轉】遊戲程式員的數學食糧05——向量速查表

來源:互聯網
上載者:User

標籤:pre   部分   ndpi   功能   返回   座標   get   協助   pac   

原文:http://gad.qq.com/program/translateview/7172922

翻譯:王成林(麥克斯韋的麥斯威爾)  審校:黃秀美(厚德載物)

 

這是本系列大家盼望已久的第五篇。如果你對向量瞭解不多,請先查看本系列的前四篇文章:介紹,向量基礎,向量的幾何表示,向量的運算。

這篇速查表會列舉一些遊戲中常見的幾何問題,以及使用數學向量解決它們的方法。

 

基本向量運算的完整表單

首先,先複習一下。

 

首先我假設你有一個可用的向量類。它的功能大部分集中在2D上,但是3D的原理相同。差別只在於向量乘積,在2D中我假設向量相乘只會返回代表“z”軸的標量。我會特別指出任何只適用於2D或3D的情況。

 

嚴格來說,一個點不是一個向量——但是一個向量可以代表從原點(0,0)到點的距離,那麼把向量當做點代表位置就很合理了。

 

我預期在該類中你擁有每個分量,以及下列各運算(使用C++風格的標記法,包括運算元的重載——但根據你的需求應該很容易將它翻譯到任何其它的語言)的使用許可權。如果一個運算不可用,你仍可以通過拓展該類或者建立一個“VectorUtils”的類來實現它。以下的例子一般適用於2D向量——但對於3D通常只需簡單地按照x、y的形式加入z座標即可。

 

  • Vector2foperator+(Vector2f vec):返回兩向量的和。(在沒有重載的語言中,該函數可能叫做add()。下面幾個例子類似。)

a+b=Vector2f(a.x+b.x,a.y+b.y);

  • Vector2foperator-(Vector2f vec):返回兩向量的差

a-b=Vector2f(a.x-b.x,a.y-b.y);

  • Vector2foperator*(Vector2f vec):返回兩向量的逐分量的乘積

a*b=Vector2f(a.x*b.x,a.y*b.y);

  • Vector2foperator/(Vector2f vec):返回兩向量的逐分量的商

a/b=Vector2f(a.x/b.x,a.y/b.y);

  • Vector2foperator*(float scalar):返迴向量所有分量分別乘以一標量參數的結果。

a*s=Vector2f(a.x*s,a.y*s);

s*a=Vector2f(a.x*s,a.y*s);

  • Vector2foperator/(float scalar):返迴向量所有分量分別除以一標量參數的結果。

a/s=Vector2f(a.x/s,a.y/s);

  • floatdot(Vector2f vec):返回兩向量的點乘

a.dot(b)=a.x*b.x+a.y*b.y;

  • floatcross(Vector2f vec):(2D情況)返回兩向量叉乘(3D向量)的z分量

a.cross(b)=a.x*b.y-a.y*b.x;

  • Vector3fcross(Vector3f vec):(3D情況)返回兩個向量的叉乘。

a.cross(b)=Vector3f(a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x);

  • floatlength():返迴向量的長度。

a.length()=sqrt(a.x*a.x+a.y*a.y);

  • floatsquaredLength():返迴向量長度的平方。適用於比較兩向量的長度,避免了計算平方根。

a.squaredLength()=a.x*a.x+a.y*a.y;

  • floatunit():返回一個指向同一方向長度為1的向量。

a.unit()=a/a.length();

  • Vector2fturnLeft():返迴向量向左旋轉90度的結果。適用於計演算法向量。(假設y軸指向上,否則就是向右轉)

a.turnLeft=Vector2f(-a.y,a.x);

  • Vector2fturnRight():返迴向量向右旋轉90度的結果。適用於計演算法向量。(假設y軸指向上,否則就是向左轉)
  • a.turnRight=Vector2f(a.y,-a.x);
  • Vector2frotate(float angle):按照特定角度旋轉向量。儘管很少能夠在向量類中找到,但這是非常有用的運算。等效於乘以一個2x2旋轉矩陣

a.rotate(angle)=Vector2f(a.x*cos(angle)-a.y*sin(angle),a.x*sin(angle)+a.y*cos(angle));

  • floatangle():返迴向量指向的角度。

a.angle()=atan2(a.y,a.x);

 

簡單的樣本——熱身

例1——兩點間距離

也許你知道可以用畢達哥拉斯定理得出,但是向量方法更簡單。給定向量a和向量b:

1

float distance = (a-b).length();

 

例2——矯直(alignment)

一些情況下你也許想要按照一張照片的中心矯直它。有時要按照它的左上方或者中上點。更廣泛來說,為了最大限度地控制矯直線,你可以使用一個兩分量從0到1(甚至超出也可以,如果你希望的話)的向量進行任意方向的矯直。

1

2

// imgPos, imgSize and align are all Vector2f

Vector2f drawPosition = imgPos + imgSize * align

 

例3——參數化直線方程

兩點定義一條直線,但是該定義有很多玄機。一個處理直線不錯的方法是使用參數化方程:一個點(“P0”)和一個方向(“dir”)。

1

2

Vector2f p0 = point1;

Vector2f dir = (point2 - point1).unit();

 

有了這個,你可以,例如,你可以得到一個距離p010單位遠的點:

1

Vector2f p1 = p0 + dir * 10;

 

例4:——中點和兩點間的內延(interpolation)

給定向量p0和p1。它們的中點是(p0+p1)/2。更廣泛來講,p0和p1定義的線段可以通過線性內延在0到1間改變t來得到

1

Vector2f p = (1-t) * p0 + t * p1;

 

 

在t=0時,你得到p0;在t=1時,你得到p1;在t=0.5時,你得到中點,等等。

 

例5:找到一線段的法向

你已經知道如何找到線段的方向了(例3)。將它旋轉90度得到法向量,所以對它使用turnLeft()或turnRight()即可得到結果。

 

使用點乘的投影

點乘對於計算向量沿一個方向投影的長度具有很大協助。我們需要向量(“a”)和一個代表投影方向(“dir”)的單位向量(所以確保你首先使用unit())。那麼投影長度就是a.dot(dir)。例如,如果a=(3,4),dir=(1,0),那麼a.dot(dir)=3。你可以判斷這是正確的,因為(1,0)是x軸的方向。實際上a.x恒等於a.dot(Vector2f(1,0)),a.y恒等於a.dot(Vector2f(0,1))。

因為a和b的點乘還可以被定義為|a||b|cos(alpha)(alpha是兩向量夾角),所以如果兩者垂直結果為0,如果兩者夾角小於90°結果為正,大於90°結果為負。我們可以使用這一點判斷兩向量是否指向同一大方向。

如果將點乘的結果乘以方向向量,你會得到沿著那方向的向量的投影——我們稱之為“at”(t代表切向)。如果我們做a-at,我們會得到垂直於方向向量的向量——我們稱之為“an”(n代表法向)。at+an=a。

 

例6——決定最靠近dir的方向

假設你有許多指向不同方向的單位向量,你想要找出哪一個方向最靠近dir。只需找到列表中向量和dir點乘的結果最大的那個。同樣,最小的點乘意味著距離最遠的那個。

 

例7——判斷兩向量的夾角是否小於alpha

使用上述方程,我們知道如果兩向量a和b的單位向量的點乘小於cos(alpha),那麼它們之間的夾角小於alpha。

123 bool isLessThanAlpha(Vector2f a, Vector2f b, float alpha) {    return a.unit().dot(b.unit()) < cos(alpha);}

 

例8——判斷一點所在的半平面

假設平面中有任意一點p0,和一個方向(單位)向量,dir。假設一條穿過p0,垂直於dir的無線長的線將平面一分為二:dir指向的半平面和dir沒有指向的半平面。那麼如何判斷一個點p是否在dir指向的那一邊呢?記住當向量間夾角小於90°時它們的點乘為正,那麼只需做投影然後檢查:

123 bool isInsideHalfPlane(Vector2f p, Vector2f p0, Vector dir) {    return (p - p0).dot(dir) >= 0;}

 

例9——迫使一點在半平面內

和上面範例相似,但是不僅僅是檢查,如果投影小於0的話,我們使用它將目標-投影移動到dir方向,這樣目標點就在半平面的邊緣了。

12345 Vector2f makeInsideHalfPlane(Vector2f p, Vector2f p0, Vector dir) {    float proj = (p - p0).dot(dir);    if (proj >= 0) return p;    else return p - proj * dir;}

 

例10——檢查/迫使一點在一凸多面體內。

一個凸多面體可以被定義為多個半平面相交所得結果,每個交線作為多面體的邊。它們的p0是邊上的頂點,它們的dir是邊的內面法線向量(例如,如果你按順時針方向走,那就是turnRight()法向)。一個點在多面體內部若且唯若它在所有的半平面內。同樣地,你可以迫使它在多面體內(通過移動到最靠近的邊)通過對所有半平面運用makeInsideHalfPlane 演算法。[哎呀!其實只有當所有角度>=90°時才管用]

 

例11——按照給定法線反射一個向量

彈球類遊戲,球撞向一個斜牆面。已知球的速度向量和牆的法向量(見例5)。現實情況下它會如何反彈?簡單!只需反射球的法向速度,保持它的切向速度即可。

12345 Vector2f vel = getVel();Vector2f dir = getWallNormal(); // Make sure this is a unit vectorVector2f velN = dir * vel.dot(dir); // Normal componentVector2f velT = vel - velN; // Tangential componentVector2f reflectedVel = velT - velN;

想要更貼近現實,你可以將velT和velN分別乘以一個代表摩擦力和恢複係數的常數。

 

例12——抵消沿軸的運動

有時我們需要將運動限制在一個給定座標軸內。思路和上面相同:將速度分解到法向和切向上,然後僅保留切線速度。這有助於計算沿軌道運動的人的速度。

 

旋轉

例13——點繞定點做旋轉

假設我們有一點,rotate()會將點按原點進行旋轉。這很有趣,但是也有限制。按任意一定點旋轉簡單而且實用——只需用點減去定點,也就是將定點平移到原點,然後旋轉,然後再把定點加回去。

123 Vector2f rotateAroundPivot(Vector2f p, Vector2f pivot) {    return (pos - pivot).rotate(angle) + pivot;}

 

例14——判斷向哪一方向旋轉

假設我們有一個角色想要轉身面向敵人。已知他的方向和面向敵人的方向。那麼他應該向左轉還是向右轉?叉乘給出了簡單的答案:curDir.cross(targetDir)返回正數如果你應該左轉,負數如果你應該右轉(返回0如果已經面對他了或者180°背對他)。

 

其他幾何樣本

有一些其它實用的例子,其中沒有過多使用向量,但是很有用:

 

例15——等距空間到螢幕座標

等距遊戲中,你知道世界的(0,0)在螢幕的什麼位置(讓我們稱之為原點,使用一個向量代表它),但是你如何知道(x,y)在螢幕的什麼位置呢?首先,你需要兩個代表座標基的向量,新x軸和新y軸。對於一個典型的等距遊戲來說,它們可以是bx=Vector2f(2,1)和by=Vector2f(-2,1)——它們不一定是單位向量。現在,一切都簡單了。

12 Vector2f p = getWorldPoint();Vector2f screenPos = bx * p.x + by * p.y + origin;

沒錯,的確很簡單。

 

例16——等距螢幕到全局座標

相同情況,但是這次你想要知道滑鼠滑過哪塊兒拼接圖(tile)。這要更複雜一些。我們知道(x’,y’)=(x*bx.x+y*by.x,x*bx.y+y*by.y)+原點,所以可以先減去原點,然後對線性方程求解。使用克拉默法則,我們可以聰明地使用2D叉乘(查看文章開始的定義)進行簡化:

12345 Vector2f pos = getMousePos() - origin;float demDet = bx.cross(by);float xDet = pos.cross(by);float yDet = bx.cross(pos);Vector2f worldPos = Vector2f(xDet / demDet, yDet / demDet);

 

我看多許多人都在用“找到矩形然後查看位元影像”的方法,現在你不需要了。

【轉】遊戲程式員的數學食糧05——向量速查表

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.