Cyrus-Beck裁剪算法及OpenGL实践

  恩..接着就是Cyrus-Beck算法。这个算法比之前的Cohen-Sutherland算法厉害,处理任意凸多边形对线段的裁剪。自然,这个算法也比Cohen-Sutherland算法复杂不少。

  首先,是线段与多边形相交的情况:

  我们把定义向量c = (C - A),而线段AC是射线A + ct的一部分。那么t取0和1就是线段AC。我们将射线与多边形的每条边求出相交时的t。取tin = max(0, tin),tout = max(tout, 1)。最终会获得一个区间[tin,tout]就是经多边形裁剪后的线段。通过A+ct即可还原出裁剪后的线段。

  那么怎么判断射线与多边形某一边相交时是射入还是射出呢,这就得先求出多边形的每条边的法向量了。规定法向量全部朝向多边形的外部。那么射线与该边的法向量的点积小于0,即夹角大于90°,肯定是射入;点积大于0,即夹角小于90°,则是射出。

  于是问题就是怎么求出每条边的法向量。由于我们规定了多边形是凸的,并且法向量均指向外部,那么法向量与跟它相邻的边的夹角一定是在90°~180°之间,即点积小于0。故我们只要任意求出一个向量与这条边垂直,然后算它与下一条边的点积,如果大于零就把它反转方向即可。

  一个例子如下:

 

接下就是代码了:

  1 #include <GL/gl.h>
  2 #include <GL/glu.h>
  3 #include <GL/glut.h>
  4 #include <iostream>
  5 #include <vector>
  6 #include <cmath>
  7 using namespace std;
  8 
  9 struct Point2D
 10 {
 11     float _x, _y;
 12     Point2D()
 13     {
 14         _x = 0.0f;
 15         _y = 0.0f;
 16     }
 17     Point2D(const Point2D& p)
 18     {
 19         _x = p._x;
 20         _y = p._y;
 21     }
 22     Point2D(float xx, float yy)
 23     {
 24         _x = xx;
 25         _y = yy;
 26     }
 27     Point2D& operator=(const Point2D& p)
 28     {
 29         _x = p._x;
 30         _y = p._y;
 31         return *this;
 32     }
 33     Point2D& operator+(const Point2D& p)
 34     {
 35         Point2D temp;
 36         temp._x = _x + p._x;
 37         temp._y = _y + p._y;
 38         return temp;
 39     }
 40     Point2D& operator-(const Point2D& p)
 41     {
 42         Point2D temp(_x - p._x, _y - p._y);
 43         return temp;
 44     }
 45     float operator*(const Point2D& p)
 46     {
 47         return _x * p._x + _y * p._y;
 48     }
 49 
 50     Point2D operator*(const float k)
 51     {
 52         return Point2D(_x * k, _y * k);
 53     }
 54 
 55     float length()
 56     {
 57         return sqrtf(_x * _x + _y * _y);
 58     }
 59 
 60     void InverseDir()
 61     {
 62         _x = -_x;
 63         _y = -_y;
 64     }
 65 };
 66 
 67 struct Line2D
 68 {
 69     Point2D _start;
 70     Point2D _end;
 71     float _length;
 72 
 73     Line2D() : _start(), _end()
 74     {
 75         _length = 0.0f;
 76     }
 77     Line2D(const Point2D& start, const Point2D& end) : _start(start), _end(end)
 78     {
 79     }
 80     Line2D(const Line2D& line) : _start(line._start), _end(line._end)
 81     {}
 82 
 83     float length()
 84     {
 85         _length = (_end - _start).length();
 86     }
 87 
 88     Line2D& operator = (const Line2D& line)
 89     {
 90         _start = line._start;
 91         _end = line._end;
 92     }
 93 
 94     Point2D GetVector()
 95     {
 96         return _end - _start;
 97     }
 98 };
 99 
100 struct Rect
101 {
102     float _left;
103     float _right;
104     float _up;
105     float _down;
106 
107     float width()
108     {
109         return _right - _left;
110     }
111     float height()
112     {
113         return _down - _up;
114     }
115 };
116 
117 struct Polygon
118 {
119     int _num;//Num of lines, not points
120     Point2D* points;
121     Point2D* norms;
122 
123     Polygon()
124     {
125         _num = 0;
126     }
127     Polygon(vector<Point2D> p)
128     {
129         Set(p);
130     }
131     ~Polygon()
132     {
133         delete[] points;
134     }
135 
136     void Clear()
137     {
138         delete[] points;
139     }
140 
141     void Set(vector<Point2D> p)
142     {
143         _num = p.size();
144         points = new Point2D[_num];
145         for(int i = 0; i < _num; ++i)
146             points[i] = p[i];
147 
148         norms = new Point2D[_num];
149         ComputeNormals();
150     }
151 
152     Line2D GetLine(int index)
153     {
154         Line2D temp;
155         if(index >= 0 && index < _num - 1)
156         {
157             temp._start = points[index];
158             temp._end = points[index + 1];
159         }
160         else if(index == _num - 1)
161         {
162             temp._start = points[index];
163             temp._end = points[0];
164         }
165         return temp;
166     }
167 
168     Point2D GetNormal(int index)
169     {
170         Point2D temp;
171         if(index >= 0 && index < _num)
172         {
173             temp = norms[index];
174         }
175         return temp;
176     }
177 
178     void ComputeNormals()
179     {
180         for(int i = 0; i < _num; ++i)
181         {
182             Line2D now = GetLine(i);
183             Line2D next;
184             if(i == _num - 1)
185                 next = GetLine(0);
186             else
187                 next = GetLine(i + 1);
188 
189             Point2D v = now.GetVector();
190             Point2D vn = next.GetVector();
191             Point2D norm;
192             if(v._x != 0)
193             {
194                 norm._y = 1;
195                 norm._x = (-v._y) / v._x;
196             }
197             else//x and y couldn't be zero at same time
198             {
199                 norm._x = 1;
200                 norm._y = (-v._x) / v._y;
201             }
202 
203             if(norm * vn > 0)
204                 norm.InverseDir();
205             norms[i] = norm;
206         }
207     }
208 
209     const Point2D& GetPoint(int index)
210     {
211         if(index >= 0 && index <= _num)
212             return points[index];
213         return Point2D();
214     }
215 };
216 
217 /*
218 Global Varibles
219 */
220 const int SCREEN_WIDTH = 800;
221 const int SCREEN_HEIGHT = 600;
222 Point2D g_Start;
223 Point2D g_End;
224 Line2D src;
225 Line2D dest;
226 bool acc;
227 bool buildpoly = true;
228 Polygon g_Poly;
229 int g_Count;
230 std::vector<Point2D> g_V;
231 
232 int Cyrus_Beck(Line2D& src, Line2D& dest, Polygon& poly)
233 {
234     float tin = 0.0f, tout = 1.0f;
235     Point2D&& vec = src.GetVector();
236 
237     for(int i = 0; i < poly._num; ++i)
238     {
239         Line2D&& line = poly.GetLine(i);
240         Point2D&& norm = poly.GetNormal(i);
241         float nc = vec * norm;
242         if(nc == 0)
243             continue; 
244         else
245         {
246             float hit = (line._start - src._start) * norm / nc;
247             if(nc > 0)//out
248                 tout = min(tout, hit);
249             else
250                 tin = max(tin, hit);
251         }
252     }
253 
254     if(tin <= tout)
255     {
256         dest._start = src._start + vec * tin;
257         dest._end = src._start + vec * tout;
258     }
259 
260     return tin > tout;
261 }
262 
263 void myInit()
264 {
265     /*
266     Output Info
267     */
268     std::vector<Point2D> v;
269     v.emplace_back();
270     g_Count = 0;
271     acc = false;
272 
273     glClearColor((float)0x66 / 0x100, (float)0xcc / 0x100, 1.0, 0.0);
274     glColor3f(0.0f, 0.0f, 0.0f);//Map Color Black
275     glPointSize(1.0);
276     glMatrixMode(GL_PROJECTION);
277     
278     glLoadIdentity();
279     gluOrtho2D(0.0, (GLdouble)SCREEN_WIDTH, (GLdouble)SCREEN_HEIGHT, 0.0);
280     glViewport(0.0, SCREEN_WIDTH, 0.0, SCREEN_HEIGHT);
281 }
282 
283 void myMouse(int button, int state, int x, int y)
284 {
285     if(buildpoly)
286     {
287         if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
288         {
289             //build over
290             g_Poly.Set(g_V);
291             g_V.clear();
292             buildpoly = false;
293             cout << "Build Poly Over.";
294             glutPostRedisplay();
295         }
296         if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
297         {
298             g_V.emplace_back(x, y);
299             cout << "Add Point: (" << x << ", " << y << ").\n";
300             glutPostRedisplay();
301         }
302         return;
303     }
304 
305     if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
306     {
307         buildpoly = true;
308         g_Count = 0;
309         glutPostRedisplay();
310         return;
311     }
312 
313     if(button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
314         return;
315 
316     cout << "MyMouse Called with " << x << ", " << y << endl;
317     switch(g_Count)
318     {
319     case 0:
320     {
321         ++g_Count;
322         g_Start._x = x;
323         g_Start._y = y;
324         src._start = g_Start;
325     }break;
326     case 1:
327     {
328         ++g_Count;
329         g_End._x = x;
330         g_End._y = y;
331         src._end = g_End;
332         acc = !Cyrus_Beck(src, dest, g_Poly);
333         if(!acc)
334         {
335             cout << "Refused.\n";
336         }
337         else
338             cout << "Accept.\n";
339 
340         glutPostRedisplay();
341     }break;
342     case 2:
343     {
344         g_Start._x = x;
345         g_Start._y = y;
346         src._start = g_Start;
347         g_Count = 1;
348     }break;
349     }    
350 }
351 
352 void myDisplay()
353 {
354     glClear(GL_COLOR_BUFFER_BIT);
355 
356     Point2D temp;
357     if(buildpoly)
358     {
359         glColor3f(0.0f, 0.0f, 0.0f);//Poly
360         glPointSize(3.0);
361         glBegin(GL_LINE_STRIP);
362 
363         for(int i = 0; i < g_V.size(); ++i)
364             glVertex2d(g_V[i]._x, g_V[i]._y);
365 
366         glEnd();
367     }
368     else
369     {
370         glColor3f(0.0f, 0.0f, 0.0f);//Poly
371         glPointSize(3.0);
372         glBegin(GL_LINE_STRIP);
373 
374         for(int i = 0; i < g_Poly._num; ++i)
375         {
376             temp = g_Poly.GetPoint(i);
377             glVertex2d(temp._x, temp._y);
378         }
379         temp = g_Poly.GetPoint(0);
380         glVertex2d(temp._x, temp._y);
381 
382         glEnd();
383 
384         if(g_Count == 2)
385         {
386             //Draw Line
387             glColor3f(1.0f, 0.0f, 0.0f);//Normal Line, Red
388             glPointSize(2.0);
389             glBegin(GL_LINES);
390               glVertex2d(src._start._x, src._start._y);
391               glVertex2d(src._end._x, src._end._y);
392             cout << "\nDraw Line\n";
393             if(acc)
394             {
395                 //Draw Cutted Line
396                 glColor3f(0.0f, 1.0f, 0.0f);//Normal Line, Green
397                 glPointSize(3.0);
398                   glVertex2d(dest._start._x, dest._start._y);
399                   glVertex2d(dest._end._x, dest._end._y);
400                 cout << "\nDraw CutLine\n";
401             }
402             glEnd();
403         }
404     }
405 
406     
407 
408     //glutSwapBuffers();
409     glFlush();
410     //cout << "Render Over\n";
411 }
412 
413 int main(int argc, char* argv[])
414 {
415     glutInit(&argc, argv);
416     //glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
417     glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
418     glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT);
419     glutInitWindowPosition(0, 0);
420     glutCreateWindow("Cyrus_Beck");
421     glutDisplayFunc(myDisplay);
422     glutMouseFunc(myMouse);
423   
424     myInit();
425     glutMainLoop();
426 
427     return 0;
428 }
Cyrus_Beck_with_GL

  数据结构新增加了一个Polygon,里面有两个Point2D数组,分别是多边形的顶点以及法向量。结构中的ComputeNormals计算出法向量。

  算法集中在Cyrus_Beck函数中,该函数接收一条线段以及一个多边形,返回裁剪后的线段,并返回是否有效。其实这个函数看起来很简单,只有短短30行。不过涉及到了一些向量知识,还必须提前计算出法向量。所以还是比Cohen_Sutherland复杂一些。

  

  代码剩下部分则是为了用OpenGL实践Cyrus_Sutherland算法的效果。

  程序运行后,先用鼠标左键选取一系列的点构建一个多边形,当然必须是凸多边形,不然算法会有问题。然后鼠标右键完成构建。接下来就是不断的用鼠标左键选取线段的起点和终点进行裁剪了。

  运行效果如下:

 

  此外,还有几种裁剪算法:

  我只尝试实现了前两种,后两种很没有接触,以后有机会应该会尝试一下。

posted @ 2016-08-15 21:38  囧rz  阅读(4452)  评论(0编辑  收藏  举报