在上篇文章的基礎上,作了許多調整,修復了許多BUG。在解決bug的過程當中,我逐漸領悟到一個要領:枯燥地一步步調試太痛苦了,找不到問題的根源!因此我選擇將中間結果打到圖片上。如:git
(注意,裏面的點是我隨便點的,有互動了吧)github
這就很是爽了!框架
本文分兩個部分,一個是交併差的實現,一個是矩形的實現。ide
// 點信息 struct Geo2DPoint { Geo2DPoint(); Geo2DPoint(float distance, const vector2& position, const vector2& normal); const Geo2DPoint& operator = (const Geo2DPoint& r); float distance{ FLT_MAX }; // 光線起點到交點距離 vector2 position; // 交點位置 vector2 normal; // 交點法向量(指向物體外部) }; // 相交測試 struct Geo2DResult { Geo2DResult(); Geo2DResult(const Geo2DShape* body, bool inside, Geo2DPoint min_pt, Geo2DPoint max_pt); Geo2DResult(const Geo2DResult& r); const Geo2DResult& operator = (const Geo2DResult& r); const Geo2DShape* body{ nullptr }; bool inside{ false }; Geo2DPoint min_pt, max_pt;//交點較小解和較大解的信息 };
每次發出一道光線,須要計算:測試
說明:優化
緣由:this
上一篇文章中,雖然實現了交併差,可是還不完善:交點信息和法向量沒有計算正確,所以作了調整(並集沒有調整):spa
【計算交集】
https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries2D.cpp#L70
if (op == t_intersect) { const auto r1 = obj1->sample(ori, dst); if (r1.body) { const auto r2 = obj2->sample(ori, dst); if (r2.body) { const auto rd = ((r1.inside ? 1 : 0) << 1) | (r2.inside ? 1 : 0); switch (rd) { case 0: // not(A or B) if (r1.min_pt.distance < r2.min_pt.distance) { if (r2.min_pt.distance > r1.max_pt.distance) // AABB break; if (r2.max_pt.distance < r1.max_pt.distance) // ABBA return r2; auto r(r2); // ABAB r.max_pt = r1.max_pt; return r; } if (r2.min_pt.distance < r1.min_pt.distance) { if (r1.min_pt.distance > r2.max_pt.distance) // BBAA break; if (r1.max_pt.distance < r2.max_pt.distance) // BAAB return r1; auto r(r1); // BABA r.max_pt = r2.max_pt; return r; } break; case 1: // B if (r1.min_pt.distance < r2.max_pt.distance) { if (r1.max_pt.distance > r2.max_pt.distance) // ABA { auto r(r1); r.max_pt = r2.max_pt; return r; } else // AAB { auto r(r1); r.max_pt = r1.max_pt; return r; } } break; case 2: // A if (r2.min_pt.distance < r1.max_pt.distance) { if (r2.max_pt.distance > r1.max_pt.distance) // BAB { auto r(r2); r.max_pt = r1.max_pt; return r; } else // BBA { auto r(r2); r.max_pt = r2.max_pt; return r; } } break; case 3: // A and B if (r1.min_pt.distance > r2.min_pt.distance) { if (r1.max_pt.distance > r2.max_pt.distance) // BA { auto r(r2); r.min_pt = r1.min_pt; return r; } else // AB { return r1; } } else { if (r2.max_pt.distance > r1.max_pt.distance) // AB { auto r(r1); r.min_pt = r2.min_pt; return r; } else // AB { return r2; } } default: break; } } } }
【計算差集】
這裏注意的是某些狀況下須要作法向量翻轉
https://github.com/bajdcc/GameFramework/blob/master/CCGameFramework/base/pe2d/Geometries2D.cpp#L171
if (op == t_subtract) { const auto r1 = obj1->sample(ori, dst); const auto r2 = obj2->sample(ori, dst); const auto rd = ((r1.body ? 1 : 0) << 1) | (r2.body ? 1 : 0); switch (rd) { case 0: // not(A or B) break; case 1: // B break; case 2: // A if (r1.inside) // AA { if (r2.max_pt.distance == FLT_MAX) return r1; if (r1.min_pt.distance > r2.max_pt.distance) return r1; auto r(r1); r.min_pt = r2.max_pt; r.min_pt.normal = -r.min_pt.normal; return r; } else return r1; case 3: // A and B if (r1.inside && r2.inside) { if (r2.max_pt.distance < r1.max_pt.distance) // BA { auto r(r1); r.min_pt = r2.max_pt; r.min_pt.normal = -r.min_pt.normal; r.inside = false; return r; } else // AB { break; } } else if (r2.inside) { if (r1.max_pt.distance > r2.max_pt.distance) { if (r2.max_pt.distance > r1.min_pt.distance) // ABA { auto r(r1); r.min_pt = r2.max_pt; r.min_pt.normal = -r.min_pt.normal; r.inside = false; return r; } else // BAA { return r1; } } else // AAB break; } else if (r1.inside) // BAB { auto r(r1); r.max_pt = r2.min_pt; return r; } else { if (r1.min_pt.distance < r2.min_pt.distance) { if (r2.min_pt.distance > r1.max_pt.distance) // AABB return r1; if (r2.max_pt.distance < r1.max_pt.distance) // ABBA return r1; auto r(r1); // ABAB r.max_pt = r2.min_pt; r.max_pt.normal = -r.max_pt.normal; return r; } else { if (r1.min_pt.distance > r2.max_pt.distance) // BBAA return r1; if (r1.max_pt.distance < r2.max_pt.distance) // BAAB break; auto r(r1); // BABA r.min_pt = r2.max_pt; r.min_pt.normal = -r.min_pt.normal; return r; } } default: break; } }
想知道代碼爲何這麼寫,須要拿張紙比劃一下(逃
爲何這麼多if???由於我能夠調試啊(最開始兩張圖),當什麼問題都解決完的時候,代碼就變這麼長了。
一開始圓的實現很是簡單,由於算個距離很快,法向量就更簡單,而矩形不一樣。
【矩形】
static int SignBit(const float& a)//返回a的符號 { if (fabs(a) < EPSILON) { return 0;//接近0 } return a > 0 ? 1 : -1; } static bool IntersectWithLineAB(const vector2& ori, const vector2& dir, const vector2& p1, const vector2& p2, float& t, vector2& p) { //利用直線的參數方程計算一直線與另外一直線的交點 const auto tAB1 = dir.y * (p2.x - p1.x) - dir.x * (p2.y - p1.y);//計算平行 if (fabs(tAB1) > EPSILON)//不平行必有交點 { t = ((ori.x - p1.x) * (p2.y - p1.y) - (ori.y - p1.y) * (p2.x - p1.x)) / tAB1;//計算距離 p = ori + dir * t;//計算交點 return (SignBit(p1.x - p.x) == SignBit(p.x - p2.x)) && (SignBit(p1.y - p.y) == SignBit(p.y - p2.y));//交點是否在p1p2間 } return false;//兩直線平行,無交點 } Geo2DResult Geo2DBox::sample(vector2 ori, vector2 dir) const { const auto _A = vector2(costheta * -s.x + sintheta * -s.y, costheta * -s.y - sintheta * -s.x); const auto _B = vector2(costheta * s.x + sintheta * -s.y, costheta * -s.y - sintheta * s.x); const auto A = center + _A; const auto B = center + _B; const auto C = center - _A; const auto D = center - _B; const vector2 pts[4] = { A,B,C,D }; static int m[4][2] = { {0,1},{1,2},{2,3},{3,0} }; float t[2];//保存兩交點的距離 vector2 p[2];//保存兩交點的位置 int ids[2];//保存與矩形哪一條邊相交 int cnt = 0; for (int i = 0; i < 4 && cnt < 2; i++) { if (IntersectWithLineAB(ori, dir, pts[m[i][0]], pts[m[i][1]], t[cnt], p[cnt])) { ids[cnt++] = i; } } if (cnt == 2)//有兩個交點 { const auto td = ((t[0] >= 0 ? 1 : 0) << 1) | (t[1] >= 0 ? 1 : 0); switch (td) { case 0: // 雙反,無交點,在外 break; case 1: // t[1],有交點,在內 //只與t1相交,那麼t0確定是另外一交點,p0確定爲負 return Geo2DResult(this, false, Geo2DPoint(t[0], p[0], Normalize(pts[m[ids[0]][0]] + pts[m[ids[0]][1]] - center - center)), Geo2DPoint(t[1], p[1], Normalize(pts[m[ids[1]][0]] + pts[m[ids[1]][1]] - center - center))); case 2: // t[0],有交點,在內 //只與t0相交,那麼t1確定是另外一交點,p1確定爲負 return Geo2DResult(this, false, Geo2DPoint(t[1], p[1], Normalize(pts[m[ids[1]][0]] + pts[m[ids[1]][1]] - center - center)), Geo2DPoint(t[0], p[0], Normalize(pts[m[ids[0]][0]] + pts[m[ids[0]][1]] - center - center))); case 3: // 雙正,有交點,在外 if (t[0] > t[1])//都相交?就看看哪一個交點更近了 { return Geo2DResult(this, false, Geo2DPoint(t[1], p[1], Normalize(pts[m[ids[1]][0]] + pts[m[ids[1]][1]] - center - center)), Geo2DPoint(t[0], p[0], Normalize(pts[m[ids[0]][0]] + pts[m[ids[0]][1]] - center - center))); } else { return Geo2DResult(this, false, Geo2DPoint(t[0], p[0], Normalize(pts[m[ids[0]][0]] + pts[m[ids[0]][1]] - center - center)), Geo2DPoint(t[1], p[1], Normalize(pts[m[ids[1]][0]] + pts[m[ids[1]][1]] - center - center))); } default: break; } } return Geo2DResult(); }
其實過程不復雜(我一夜居然能搞定哈哈,這歸功於調試方法的先進性),計算法向量很簡單,就是兩個對角線的疊加方向。。
有幾個注意點:
矩形會作以後,多邊形應該都能搞定了,如三角形,只要考慮一下三邊的方向即向(即判斷是在內仍是在外)。
作完以後,在作反射、折射,這簡單了!由於距離呀交點呀法向量所有求出來了。
至於我爲何要在框架中實現這個效果:一者,可視化+互動;兩者,不同凡響;三者,框架要有例子才能證實好用啊。