0.補充知識
向量點積:結果等於0, 兩向量垂直; 結果大於0, 兩向量夾角小於90度; 結果小於0, 兩向量夾角大於90度.
直線的參數方程:(x1, y1)和(x2, y2)兩點確定的直線, 其參數方程為x = x1+u(x2-x2); y = y1+u(y2-y1)
1.前言
Liang-Barsky算法是 Cyrus-Beck 算法的特例, 我們先來簡單的了解Cyrus-Beck算法, Cyrus-Beck算法本質是每次通過裁剪窗口(任意凸多邊形, 文章最后會說明為什么凹多邊形不行)的一條邊界來確定待裁剪線段的哪部分應當被留下, 最后, 對所有應該被留下的部分取交集, 便可以求得線段應當留下的部分.舉個例子, 假設多邊形ABCDE, 那么我們每次使用一條邊(AB, BC, …), 延長這條邊和待裁剪的線段, 那么最后兩條直線必定相交或平行, 如果相交, 根據交點確定哪部分被留下, 如果平行, 根據坐標確定哪部分被留下. 而Liang-Barsky算法只是將這個裁剪窗口固定為了一個平行於坐標軸的矩形, 所以Liang-Barsky算法和Cyrus-Beck算法本質是一樣的, 只是Liang-Barskys算法因為擁有更多信息(裁剪窗口是一個平行與坐標軸的矩形), 可以對其中一些步驟進行簡化處理.
2.對於一條邊界, 具體如何確定線段應當留下的部分?
2.1符號說明
AB:邊界直線, 把整個平面划分為兩部分, 我們約定向量AH所在區域稱為內部區域, 另一部分稱為外部區域
AH:邊界直線的法向量, AH=(1,0)
CD:待裁剪線段,C(x1, y1), D(x2, y2),CD的向量表示為 (x1+u(x2-x1), y1+u(y2-y1))(0<=u<=1)
E,F,G:待裁剪線段上三點
2.2判定方法
向量AH*向量AG, 結果大於0, H點處於內部區域
向量AH*向量AF, 結果等於於0, F點位於邊界上
向量AH*向量AE, 結果小於0, E點處於外部區域
所以, 通過AX(X為線段CD上任意一點)與法向量AH的乘積即可判定X點位於內部區域還是外部區域
3.對於任意凸多邊形邊界, 如何確定線段應當留下的部分?
任意凸多邊形邊界和直線邊界沒有本質區別, 如果對於矩形上的所有邊, 點X都滿足屬於這條邊的內部區域, 那么X就在矩形的內部區域.(比如圖中的IG部分)
4.Liang-Barsky的算法流程以及算法中的p和q
4.1算法流程
以AB邊為例, X為CD上任意一點, 矩形邊界的左右上下邊界分別為XL, XR, YT, YB
因為向量AH*向量AX>=0時, X點在AB的內部區域
所以我們現在求解這個不等式:
向量AH*向量AX>=0
向量AH*(向量OX-向量OA)>=0;
由CD的向量表示式可知
(1,0)*(x1+u(x2-x1)-XL, y1+u(y2-y1)-YA)>=0;(我們不用關心YA的具體值, 請不要在此糾結)
x1+u(x2-x1)-XL>=0;
假設x2-x1>0(圖中是大於0, 但是對於任意直線, 可能大於0, 小於0, 等於0)
則u>=(XL-x1)/(x2-x1)時, 點X在直線AB的內部區域
假設x1-x2<0
則u<=(XL-x1)/(X2-X1)時, 點X在直線AB的內部區域
如果x2-x1=0,則簡單判斷x1和XL的大小, 如果x1<XL, 舍去, 否則保留
本步執行完后, 我們得到一個關於u的不等式
同理對矩形另外三條邊做如上處理, 我們得到另外3個不等式
我們現在觀察一下這四個不等式
因為AB邊和IJ邊的法向量x坐標值為-1和1
那么如果AB邊算出的不等式是u>=(XL-x1)/(x2-x1), 則IJ邊算出來一定是u<=(XR-x1)/(x2-x1)(因為IJ邊計算時僅僅是把XL換成XR, 1換成-1, 不理解請自己按照上述步驟計算一下), 我們可以發現, 互相平行的兩條邊算出來的不等式一個是>=那么另一個一定是<=(其實就是一維裁剪, 確定線段的上限和下限).
那么對4條邊分別進行如上處理, 會得到u<=ans1, u<=ans2, u>=ans3, u>=ans4這4個不等式
ans1,ans2為u可能的上限, ans3,ans4為u可能的下限
又因為參數方程實際上是表示一條直線, 而我們需要裁剪的是一條線段, 所以u應當在0和1之間
於是
令umax = min(ans1, ans2, 1);//因為是取交集, 所以我們應在可能的上限中取最小的那個
umin = max(ans3, ans4, 0);
如果umax<umin則沒有線段位於裁剪窗口的內部
否則將參數u帶進參數方程, 求得裁剪之后線段的兩個端點
最后利用端點畫出裁剪結果即可
4.2算法中的p和q
L, R, T, B是對應邊上的一點
(1,0)*(x1+u(x2-x1)-XL, YL) >=0;
(-1,0)*(x1+u(x2-x1)-XR, YR) >=0;
(0,1)*(XB, y1+u(y2-y1)-YB) >=0;
(0,-1)*(XT, y1+u(y2-y1)-YT) >=0;
令p1 = -(x2 - x1); p2 = -p1
p3 = -(y2 - y1); p4 = -p3
上面的不等式變為
x1+u*p2-XL>=0;
-x1+u*p1+XR>=0;
y1+u*p4-YB>=0;
-y1+u*p3+YT>=0;
移項
u*p2>=XL-x1;
u*p1>=x1-XR;
u*p4>=YB-y1;
u*p3>=y1-YT;
同時乘上-1(為了讓不等式組看起來有序)
u*p1<=x1-XL;
u*p2<=XR-x1;
u*p3<=y1-YB;
u*p4<=YT-y1;
令q1 = x1-XL;
...
q4 = YT-y1;
假設p1<0, p3<0
u>=q1/p1
u>=q3/p3
u<=...
u<=...
不等式取交集即可得到u的范圍, 可以看到p和q並沒有明顯的物理意義, 請不要糾結於此
5.Liang-Barsky的c++代碼實現
void LiangBarsky(int x1, int y1, int x2, int y2, int XL, int XR, int YT, int YB) { int ansx1, ansx2, ansy1, ansy2; //三種類型 //平行於y軸 if (x1 - x2 == 0) { if (x1<XL || x1>XR) { return; } else { int ymin = max(YB, min(y1, y2)); int ymax = min(YT, max(y1, y2)); if (ymin <= ymax) { ansx1 = ansx2 = x1; ansy1 = ymin; ansy2 = ymax; } else { return; } } } //平行於x軸 else if (y1 - y2 == 0) { if (y1<YB || y1>YT) { return; } else { int xmin = max(XL, min(x1, x2)); int xmax = min(XR, max(x1, x2)); if (xmin <= xmax) { ansy1 = ansy2 = y1; ansx1 = xmin; ansx2 = xmax; } else { return; } } } //不平行於坐標軸 else { float p1, p2, p3, p4; float q1, q2, q3, q4; p1 = -(x2 - x1); p2 = -p1; p3 = -(y2 - y1); p4 = -p3; q1 = x1 - XL; q2 = XR - x1; q3 = y1 - YB; q4 = YT - y1; float u1, u2, u3, u4; u1 = q1 / p1; u2 = q2 / p2; u3 = q3 / p3; u4 = q4 / p4; float umin, umax; if (p1 < 0) { if (p3 < 0) { umin = max(0, max(u1, u3)); umax = min(1, min(u2, u4)); } else { umin = max(0, max(u1, u4)); umax = min(1, min(u2, u3)); } } else { if (p3 < 0) { umin = max(0, max(u2, u3)); umax = min(1, min(u1, u4)); } else { umin = max(0, max(u2, u4)); umax = min(1, min(u1, u3)); } } if (umin <= umax) { ansx1 = x1 + umin * (x2 - x1); ansx2 = x1 + umax * (x2 - x1); ansy1 = y1 + umin * (y2 - y1); ansy2 = y1 + umax * (y2 - y1); } else return; } //調用函數重繪直線 YourDrawFunc(); return; }
6.教材上的算法流程和解題實例
相信有了上面的基礎, 再回頭看書上對於Liang-Barsky算法的講解, 就不會一頭霧水了.
相關內容在這篇博文中已經寫的很清楚了, 故本文不在贅述.
[計算機圖形學經典算法] Liang-Barsky(梁友棟-Barsky) 算法 (附Matlab代碼): https://blog.csdn.net/soulmeetliang/article/details/79185603
7.從Liang-Barsky算法到Cyrus-Beck算法
7.1為什么凹多邊形無法使用Cyrus-Beck算法?
因為凹多邊形的邊界不一定保證把平面區域分成多邊形內部和外部兩個區域
7.2Cyrus-Beck算法如何求得邊界法向量?
Liang-Barsky算法因為裁剪窗口得特殊性, 每條邊界得法向量我們作為已知量來處理. Cyrus-Beck中需要自己確定法向量, 思路如下: 根據邊界(假定為AB)確定垂直於邊界的法向量(a1, a2), 任取多邊形除了本次邊界上兩點之外的任意一點P, 計算向量AP(BP)*a1, 大於0取a1, 否則取a2
7.3u的上下限對應關系
Liang-Barsky算法中, 我們知道平行的兩條邊一條提供上限則另一條提供下限. 對於更一般的凸多邊形裁剪窗口, 則是與線段延長線相交的兩條邊界延長線對應的邊界. 從不等式來看, xi+u*(xj-xi)中的(xj-xi)決定了得到u的上限還是下限, 可以據此實現上下限的區分.
8.參考資料
[計算機圖形學經典算法] Liang-Barsky(梁友棟-Barsky) 算法 (附Matlab代碼): https://blog.csdn.net/soulmeetliang/article/details/79185603
理解梁友棟-Barsky裁剪算法: https://blog.csdn.net/Daisy__Ben/article/details/51941608
梁友棟-Barsky算法原理: http://www.voidcn.com/article/p-niexnvwo-rv.html