距離上次發佈已經有了很長一段時間,期間因爲各類緣由沒有更新這方面的技術分享,在這裏深表遺憾。在MMO或其餘的遊戲中,會有針對各類形狀的計算,一般在攻擊區域裏不會很複雜,常見的爲矩形、圓形、扇形。今天分享的是判斷一個目標點是否在扇形內的計算,用到的是比較簡單的運算,高效率的算法我很儘快更新。html
在這裏須要你們回顧一下初中以及高中的代數和平面幾何的知識,想必大部分的朋友在這方面和我同樣幾乎忘記了或是對這些數學知識感受有些頭痛。不過你們沒有必要擔憂,在實際運用中,咱們都不會涉及太複雜的計算,由於咱們不須要追求的十分精確。git
可是在如下內容中,你們須要知道一些基本的定理和公式:勾股定理、餘弦定理。github
須要瞭解弧度和角度的一些轉換:角度 = 弧度 * 180.0 / ∏算法
一些數學函數:atan二、acos等。跨域
中心線:以中心線順、逆時針展開半角,造成一個完整的目標扇形區域。函數
極座標概念:優化
如上圖座標點A的平面座標爲(7.96, 5.43),對應的極座標爲(ρ, θ)編碼
相對座標的概念:spa
在扇形的計算中,咱們的X軸與Y軸的方向與原點的的X軸和Y軸平行,在上圖中,若是以A點做爲中心點,那麼B的相對座標爲(xb - xa, yb - ya)。code
必要的數據: 原點A(攻擊者座標)、方向點B(direction)、目標點C(point)、扇形角度(β)、扇形的半徑(r)。
扇形的有效區域:
如上圖中的扇形的角度爲180°,其有效面積爲:以X軸爲中心分佈的可攻擊區域1(area1紫色區域)和可攻擊區域2(area2綠色區域)。
若是目標點的極座標爲(ρ,Θ),那麼上述的目標點判斷爲:目標點到原點A的距離小於扇形的半徑(ρ <= r)、在區域1(0 <= Θ <= α + β / 2)或區域2((a - β / 2) + 360 <= Θ <= 360)。
由於咱們的極座標的範圍爲(0-360),因此若是算出來的扇形最小的角度爲負,須要轉換爲正來比較。
從上述的判斷中,並無將全部的狀況考慮到,若是須要的扇形角度超過180或者方向點落在不一樣的象限內,計算的方式是不一樣的,接下來再看一張示意圖。
如上圖所示,這樣的面積計算就不能使用第一種方式,方向的極座標的角度爲315°,而扇形的角度的不過270°,那麼α、β、γ到底變成了怎樣的關係,咱們求出了方向的極座標是否能把這樣兩個面積的角度分別計算出來?
區域1(0 <= Θ <= α + β / 2),若是是上圖的數據則爲0 <= Θ <= 315 + 270 / 2,範圍就是[0,450],若是用詞判斷則象限2中的空白部分的本不符合的點就符合條件了,其實咱們能夠看出區域1的實際範圍爲[0,90],同理區域2的範圍爲[180,360]。而超過了360°的角度只要減去360,這裏的450轉換爲正常角度則爲90剛剛是咱們的正確角度。而第一種方式中的區域二的區域爲[a - β / 2,360],在上圖中剛剛爲[180,360]是符合的。
如何判斷一個扇形是否跨域了X軸正方向?
由於咱們的扇形是以中心線分紅兩個部分,那麼只須要判斷這兩個部分是否超過了就能夠,當前若是中心線在X軸正方向時絕對是跨域了兩個方向。
三種跨域的方式:示例圖1中(a - β / 2)的值小於零、示例圖2中的(α + β / 2)大於360、方向點在X軸正方向上(極座標的角度Θ爲0或絕對座標X大於0和Y等於0)。
同時兩個扇形的角度須要轉換:小於0的角度須要轉換爲正(示例圖1中a - β / 2,加上360)、大於360的角度須要轉換爲360之內的角度(示例圖中的α + β / 2,減去360)。
那麼兩個區域就爲[0, a + β / 2]、[a - β / 2, 360],須要注意這裏的角度都須要進行上述的轉換。
若是扇形的範圍沒有跨域X軸正方向,那麼角度的範圍是[a - β / 2, a + β / 2]。
咱們知道了點和麪的關係後,就可以對目標點進行斷定了,編碼實現纔有了依據。(上面三種狀況是否會有遺漏,暫時沒有考慮過,歡迎你們指正)
基礎結構定義:
struct point_struct { double x; double y; point_struct() : x{.0}, y{.0} {} }; using point_t = point_struct;
相對座標轉換(沒有方向):
/** * 沒有方向的相對座標轉換,x、y軸的方向與原點相同 * @param origin 座標系原點 * @param point 須要轉換的點 */ /** * Y * | * | CY * | | * | | .point * | | * | | * | origin----------------- CX * | * | * O--------------------------------------------------- X * */ point_t absolute_to_relative(const point_t &origin, const point_t &point) { point_t result; result.x = point.x - origin.x; result.y = point.y - origin.y; return result; }
極座標轉換:
/** * 簡單極座標轉換(轉換後的x爲斜邊,y爲角度), * 減小數學函數調用(能夠忽略,由於一般狀況下這幾種狀況命中機率極低) * @param point 須要轉換的點 */ point_t to_spolar_coordinate(const point_t &point) { point_t result; result.x = 0; result.y = -1; if (0 == point.x == point.y) { result.y = 0; return result; } if (0 == point.y) { result.x = abs(point.x); result.y = point.x > 0 ? 0 : 180; return result; } if (0 == point.x) { result.x = abs(point.y); result.y = point.y > 0 ? 90 : 270; return result; } if (abs(point.x) == abs(point.y)) { result.x = 1.41421 * abs(point.x); if (point.x > 0 && point.y > 0) { result.y = 45; } else if (point.x < 0 && point.y > 0) { result.y = 135; } else if (point.x < 0 && point.y < 0) { result.y = 225; } else if (point.x > 0 && point.y < 0) { result.y = 315; } } return result; }
/** * 轉換爲極座標(轉換後的x爲斜邊,y爲角度) * @param point 須要轉換的點 */ inline point_t to_polar_coordinate(const point_t &point) { point_t result; result.x = sqrt(point.x * point.x + point.y * point.y); result.y = (180.0 / PI) * atan2(point.y , point.x); //弧度轉角度 result.y = result.y < .0 ? result.y + 360.0 : result.y; return result; }
判斷是否在扇形內:
/** * 判斷一個點是否在扇形內(相對中心點) * @param center 扇形的中心點 * @param direction 中心線的方向座標 * @param r 半徑 * @param angle 角度(0 < angle < 360) * @param point 須要檢查的點 */ bool in_circular_sector(const point_t ¢er, const point_t &direction, double r, int angle, const point_t &point) { //實際使用中,咱們會把方向點的極座標放到外部進行計算 point_t d_rpoint = absolute_to_relative(center, direction); //方向相對座標 point_t d_pc_point = to_spolar_coordinate(d_rpoint); //方向極座標 if (-1 == d_pc_point.y) { //簡單的若是轉換不出,則須要調用角度函數計算 d_pc_point = to_polar_coordinate(d_rpoint); } point_t rpoint = absolute_to_relative(center, point); //目標相對座標 point_t pc_point = to_polar_coordinate(rpoint); //目標極座標 if (pc_point.x > r) return false; bool result = false; auto half_angle = angle / 2; auto angle_counter = d_pc_point.y - half_angle; //中心線順時針方向的範圍 auto angle_clockwise = d_pc_point.y + half_angle; //中心線逆時針方向的範圍 /* std::cout << "angle_counter: " << angle_counter << " angle_clockwise: " << angle_clockwise << " d_pc_point.y" << d_pc_point.y << std::endl; */ if (0 == d_pc_point.y || angle_counter < 0 || angle_clockwise > 360) { angle_counter = angle_counter < 0 ? angle_counter + 360 : angle_counter; angle_clockwise = angle_clockwise > 360 ? angle_counter - 360 : angle_counter; if (pc_point.y >= 0 && pc_point.y <= angle_counter) { result = true; } else if (pc_point.y >= angle_clockwise && pc_point.y <= 360) { result = true; } } else { result = angle_counter <= pc_point.y && angle_clockwise >= pc_point.y; } return result; }
實際使用的優化:
能夠先求出圓範圍內的全部玩家對象,這樣能夠減小atan2調用,其次方向點的極座標放到循環以外,減小循環次數。
開篇語
咱們沒有大神,只有解決問題的人。
咱們沒有強悍的技術,只有一顆嚮往簡單的心。
咱們沒有驚人的理論,只有一堆難以想象的妄想。
咱們不須要複雜,只須要夠簡潔。
咱們不須要固定的思惟,只須要你能想獲得。
PF託管地址
https://github.com/viticm/plainframework1
PF安裝教程
http://www.cnblogs.com/lianyue/p/3974342.html
PF交流QQ羣
348477824(同時歡迎技術人員加入進行交流)