当前位置:首页 > 通信资讯 > 正文

在上图形学课的时候,学习了扫描线填充算法。不过在完成实验的时候在真正理解了该算法,在此记录一下,如果有表达上的错误,欢迎指正!

扫描线填充算法通过在与图形相交的第(1,2)、(3,4)... 边之间划线不断不断填充图形。因此,在扫描时就需要确定什么时候与图形的某条边相交、划线的时候x的范围是多少以及划线时是从哪个交点画至另一个交点。

结构体如下所示:

扫描线算法填充多边形(扫描线多边形填充算法中,对于扫描线)

为了节省存储的空间,边表项也使用链表结构,将图形中ymin值相同的边链接在同一个边表项后,这样在扫描的时候方便添加。

具体的流程如下:

一、初始化活动边表

1. 统计并初始化表项

2. 将每条边分别链接在表项后

二、 绘制与填充

1. 取出当前与扫描线相交的边

① 取出ymin 大于当前扫描线的y值的边

② 删除ymax 小于等于当前扫描线的边(①②过程可以排除掉与扫描线平行的边)

2. 将取出的边按照左右顺序排序(根据边的最低点的坐标与直线的斜率判断)

3. 划线并直接在原结构上修改边的x值(因为是在一个函数内,修改保存的值仅限于函数内,并不影响main函数中的值)

具体的代码如下所示,使用的库是EasyX(可以在http://www.easyx.cn/下载):

  1. #include "graphics.h"
  2. #include "stdio.h"
  3. #include "conio.h"
  4. #include <stdlib.h>
  5. #include <math.h>
  6. #include <cmath>
  7. #include <iostream>
  8. using namespace std;
  9. #define MAX_VOL 20
  10. //多边形的边的数据结构
  11. typedef struct Edge
  12. {
  13. int y_max, y_min; //该有向边的y坐标的最大值与最小值
  14. double x, deltax; //该有向边的x的最小值以及x的变化的量(1/斜率)
  15. struct Edge* next; //指向下一条边的指针
  16. }Edge;
  17. //活动边表表项
  18. typedef struct TableItem
  19. {
  20. int curr_y; //该表项的y坐标值 ymin
  21. Edge *firstNode; //该表项的首个节点,如果没有,NULL
  22. struct TableItem *next; //指向下一个活动边表表项的指针
  23. }TableItem;
  24. //活动边表结构体
  25. typedef struct Table
  26. {
  27. TableItem *itemHeader; //活动边表的表项header
  28. int item_count; //活动边表表项的个数
  29. }ET;
  30. class Point
  31. {
  32. private:
  33. int x1, x2, y1, y2;
  34. public:
  35. Point(int x1, int y1, int x2, int y2)
  36. {
  37. this->x1 = x1;
  38. this->x2 = x2;
  39. this->y1 = y1;
  40. this->y2 = y2;
  41. }
  42. //返回两个点之中的ymax
  43. int YMax()
  44. {
  45. return (y1 > y2 ? y1 : y2);
  46. }
  47. //返回ymin
  48. int YMin()
  49. {
  50. return (y1 < y2 ? y1 : y2);
  51. }
  52. //返回ymin 端点的x 值
  53. int x()
  54. {
  55. return (y1 < y2 ? x1 : x2);
  56. }
  57. //返回直线的斜率,按照传入的参数的顺序
  58. double KOfLine()
  59. {
  60. return ((y2 - y1)*1.0 / (x2 - x1));
  61. }
  62. };
  63. class Solution
  64. {
  65. public:
  66. //根据多边形初始化活动表
  67. //参数 T 活动边表
  68. //参数edges 用于初始化的边数组
  69. //参数 edge_num 用于初始化的边的个数
  70. void Init(ET &T, Edge *edges, int edge_num)
  71. {
  72. //初始化活动边表结构体
  73. T.item_count = 0;
  74. T.itemHeader = NULL;
  75. int ymins[20]; //存储ymin ,决定活动边表的个数以及表项的内容
  76. T.item_count = TableItemCount(edges, edge_num, ymins);
  77. T.itemHeader = (TableItem*)malloc(sizeof(TableItem));
  78. T.itemHeader->curr_y = ymins[0];
  79. T.itemHeader->firstNode = NULL;
  80. T.itemHeader->next = NULL;
  81. TableItem *p = T.itemHeader; //指向头结点
  82. for (int i = 1; i<T.item_count; ++i)
  83. {
  84. //依次创建活动边表的各个表项,并连接在一起
  85. TableItem *e = (TableItem*)malloc(sizeof(TableItem));
  86. e->curr_y = ymins[i];
  87. e->firstNode = NULL;
  88. e->next = NULL;
  89. p->next = e;
  90. p = e;
  91. }
  92. //按照用于初始化的边数组初始化活动边表
  93. p = T.itemHeader;
  94. for (int j = 0; j < edge_num; ++j) {
  95. this->AppendNode(T, edges[j].y_min, edges[j]);
  96. }
  97. //方法结束
  98. ////////测试区////////////
  99. //cout << "遍历表项。。。。。" << endl;
  100. //p = T.itemHeader;
  101. //while (p != NULL) {
  102. // cout << "当前表项y : " << p->curr_y << endl;
  103. // Edge *ele = p->firstNode;
  104. // while (ele != NULL) {
  105. // cout << "表项中的边: x = " << ele->x << " y_min = " << ele->y_min << " y_max = " << ele->y_max <<
  106. // "deltax = " << ele->deltax << endl;
  107. // ele = ele->next;
  108. // }
  109. // p = p->next;
  110. //}
  111. ////////测试删除结点////////
  112. //p = T.itemHeader;
  113. //int yMax = 0;
  114. //while (yMax < 24) {
  115. // p = T.itemHeader;
  116. // cout << "-------------------------------" << endl;
  117. // cout << "当前y max :" << yMax << endl;
  118. // this->DeleteNode(T, yMax);
  119. // while (p != NULL) {
  120. // cout << "当前表项y : " << p->curr_y << endl;
  121. // Edge *ele = p->firstNode;
  122. // while (ele != NULL) {
  123. // cout << "表项中的边: x = " << ele->x << " y_min = " << ele->y_min << " y_max = " << ele->y_max <<
  124. // "deltax = " << ele->deltax << endl;
  125. // ele = ele->next;
  126. // }
  127. // p = p->next;
  128. // }
  129. // yMax++;
  130. //}
  131. /////////////////////////
  132. }
  133. //用于根据边数组计算需要多少个表项
  134. //表项的个数取决于边的ymin的个数
  135. //返回值 ymin 数组
  136. //返回 item_num 表项的个数
  137. int TableItemCount(Edge *edges, int edge_num, int* ymins)
  138. {
  139. int count = 0;
  140. for (int i = 0; i<edge_num; ++i)
  141. {
  142. if (!isInArray(ymins, edges[i].y_min, count))
  143. {
  144. ymins[count++] = edges[i].y_min;
  145. }
  146. }
  147. //将ymin 升序排序
  148. for (int j = 0; j<count - 1; ++j)
  149. {
  150. for (int k = j + 1; k<count; ++k)
  151. {
  152. if (ymins[k] < ymins[j])
  153. {
  154. int tmp = ymins[k];
  155. ymins[k] = ymins[j];
  156. ymins[j] = tmp;
  157. }
  158. }
  159. }
  160. return count;
  161. }
  162. //判断一个整数是否在整数数组中
  163. bool isInArray(int *array, int e, int array_length)
  164. {
  165. for (int i = 0; i<array_length; ++i)
  166. {
  167. if (array[i] == e)
  168. {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. //传入edges数组,初始化,返回Edge 结构体数组
  175. //因为需要是封闭图形,所以,在边数组中,最后的点的坐标设为起始点的坐标,传入的edge_num 不变
  176. Edge* InitEdges(int *edges, int edge_num)
  177. {
  178. Edge *newEdges = (Edge*)malloc(sizeof(Edge)*edge_num);
  179. int j = 0;
  180. for (int i = 0; i<edge_num; ++i)
  181. {
  182. Point point(edges[2 * i], edges[2 * i + 1], edges[2 * (i + 1)], edges[2 * (i + 1) + 1]);
  183. Edge *newEdge = (Edge*)malloc(sizeof(Edge));
  184. newEdge->x = (double)point.x();
  185. newEdge->y_max = point.YMax();
  186. newEdge->y_min = point.YMin();
  187. newEdge->deltax = 1.0 / point.KOfLine(); // 斜率分之一
  188. newEdge->next = NULL;
  189. newEdges[j++] = *(newEdge);
  190. }
  191. return newEdges;
  192. }
  193. //删除所有的小于ymax 的节点
  194. //参数 curr_ymax 当前扫描线的y值
  195. void DeleteNode(ET &T, int curr_ymax)
  196. {
  197. TableItem *p = T.itemHeader; //指向表项的指针
  198. while (p != NULL) {
  199. Edge *item = p->firstNode; //指向表项的邻接链表的指针
  200. Edge *itempre = p->firstNode; //指向前一个边结点的指针
  201. while (item != NULL) {
  202. if (item->y_max <= curr_ymax) { //删除该结点
  203. T.item_count--; //当前活动边表中的边的个数-1
  204. //判断该结点是否是该链表的头结点
  205. if (item == p->firstNode) {
  206. p->firstNode = (Edge*)malloc(sizeof(Edge));
  207. p->firstNode = item->next;
  208. free(item); //释放该结点
  209. item = p->firstNode; //重新指向firstnode结点
  210. itempre = p->firstNode;
  211. }
  212. else {
  213. itempre->next = item->next; //修改前一个结点的next的值
  214. free(item); //删除该指针
  215. item = itempre->next; //继续向后遍历
  216. }
  217. }//if (item->y_max < curr_ymax)
  218. else {
  219. itempre = item;
  220. item = item->next;
  221. }
  222. }//while (item != NULL)
  223. p = p->next;
  224. }//while (p != NULL)
  225. }
  226. //将指定y值的节点添加到该表项, 该方法插入的顺序取决于调用该方法传入参数的顺序
  227. //该方法将新节点插入到对应表项的邻接链表的末尾
  228. void AppendNode(ET &T, int place_y, Edge &e)
  229. {
  230. ////////测试区//////////
  231. //cout << "In Append , place_y = " << place_y << " e.ymin = " << e.y_min << endl;
  232. //cout << "item count" << T.item_count << endl;
  233. ///////////////////////
  234. TableItem *p = T.itemHeader; //指向活动边表的头结点
  235. //将边e插入到对应的表项
  236. //之后在该表项中按照x的大小确定插入的位置
  237. for (int i = 0; i < T.item_count; ++i) {
  238. if (p->curr_y == e.y_min)
  239. break;
  240. p = p->next;
  241. }
  242. //将边插入到该表项的邻接链表中
  243. Edge *egp = p->firstNode; //egp 指向该表项的首个邻接节点
  244. if (egp == NULL) { //如果该表项还没有节点,直接插入
  245. egp = (Edge*)malloc(sizeof(Edge));
  246. *(egp) = e;
  247. egp->next = NULL;
  248. p->firstNode = egp;
  249. }
  250. else {
  251. Edge *pre = egp;
  252. while (egp != NULL) {
  253. pre = egp;
  254. egp = egp->next;
  255. }
  256. Edge *newedge = (Edge*)malloc(sizeof(Edge));
  257. *(newedge) = e;
  258. pre->next = newedge;
  259. newedge->next = NULL;
  260. }
  261. }
  262. //绘图的方法
  263. void Draw(ET T) {
  264. //首先取出ymin 值小于当前扫描线y 的边
  265. //按照顺序配对
  266. int curr_y = 0, curr_edge_num = 0, curr_gy = graphy(curr_y); //图形坐标的扫描线的y坐标
  267. Edge *currEdges = (Edge*)malloc(sizeof(Edge) * 20); //用于存放指针的数组
  268. //将每条边的记录的x 化为图形上的坐标
  269. TableItem *p = T.itemHeader;
  270. while (p != NULL) {
  271. Edge *q = p->firstNode;
  272. while (q != NULL) {
  273. q->x = graphx(q->x);
  274. q = q->next;
  275. }
  276. p = p->next;
  277. }
  278. for (; curr_y < 30; curr_gy--, curr_y = realy(curr_gy)) {
  279. this->DeleteNode(T, curr_y); //删除当前扫描过的边(ymax 小于 curr_y)
  280. currEdges = this->GetCurrEdges(T, curr_y, curr_edge_num); //获取当前与扫描线相交的边
  281. //对获取到的边进行排序、配对
  282. for (int i = 0; i < curr_edge_num - 1; ++i) {
  283. for (int j = i + 1; j < curr_edge_num; ++j) {
  284. if (this->IsRightTo(currEdges[i], currEdges[j])) {
  285. Edge tmp = currEdges[i];
  286. currEdges[i] = currEdges[j];
  287. currEdges[j] = tmp;
  288. }
  289. }
  290. }
  291. ////
  292. // getchar();
  293. // cout << "------------------------------" << endl;
  294. setcolor(BLUE);
  295. for (int j = 0; j < curr_edge_num / 2; ++j) {
  296. ///
  297. // cout << "line :" << (int)currEdges[2 * j].x << " , " << curr_y << "----->" << (int)currEdges[2 * j + 1].x << " , " << curr_y <<
  298. // " edge_num = " << curr_edge_num << endl;
  299. line((int)currEdges[2 * j].x, curr_gy, (int)currEdges[2 * j + 1].x, curr_gy);
  300. Edge *curr_edge1 = this->GetThisEdge(T, currEdges[2 * j].x, currEdges[2 * j].y_min,
  301. currEdges[2 * j].y_max); //获取当前边的指针,修改x值,保存修改
  302. curr_edge1->x += curr_edge1->deltax;
  303. Edge *curr_edge2 = this->GetThisEdge(T, currEdges[2 * j + 1].x, currEdges[2 * j + 1].y_min,
  304. currEdges[2 * j + 1].y_max);
  305. curr_edge2->x += curr_edge2->deltax;
  306. //line((int)currEdges[2 * j].x, curr_gy, (int)currEdges[2 * j + 1].x, curr_gy); //在两条直线之间划线
  307. //currEdges[2 * j].x += currEdges[2 * j].deltax;
  308. //currEdges[2 * j + 1].x += currEdges[2 * j + 1].deltax; //更新x 的坐标值
  309. }
  310. //////////测试模拟输出划线///////////////
  311. /*cout << "------------------------------------------" << endl;
  312. cout << "curr_y = " << curr_y << endl;
  313. cout << "当前扫描的边的个数 = " << curr_edge_num << endl;
  314. for (int i = 0; i < curr_edge_num / 2; ++i) {
  315. cout << "draw line bwtwen :" << endl;
  316. cout << "直线1 x = " << currEdges[2 * i].x << " ymin = " << currEdges[2 * i].y_min <<
  317. " ymax = " << currEdges[2 * i].y_max << endl;
  318. cout << "直线2 x = " << currEdges[2 * i + 1].x << " ymin = " << currEdges[2 * i + 1].y_min <<
  319. " ymax = " << currEdges[2 * i + 1].y_max << endl;
  320. }*/
  321. ////////////////////////////////////
  322. //在1,2 3,4 ... 边之间划线
  323. //TODO 坐标转换以及划线
  324. }
  325. ///////测试区/////////////////
  326. //cout << "-------------------------------------" << endl;
  327. //cout << "当前取出的边。。。。。。。。。。" << endl;
  328. //cout << "curr_edge_num = " << curr_edge_num << endl;
  329. //for (int i = 0; i < curr_edge_num; ++i) {
  330. // cout << "x = " << currEdges[i].x << " y_min = " << currEdges[i].y_min << " y_max = " << currEdges[i].y_max << endl;
  331. //}
  332. ////////////////////////////////
  333. }
  334. //返回某个边的指针
  335. //可通过此指针修改原结构体中边的x的值
  336. Edge* GetThisEdge(ET T, double x, int y_min, int y_max) {
  337. TableItem *p = T.itemHeader;
  338. while (p != NULL) {
  339. Edge *q = p->firstNode;
  340. while (q != NULL) {
  341. if ((q->x == x) && (q->y_max == y_max) && (q->y_min == y_min)) {
  342. return q;
  343. }
  344. q = q->next;
  345. }
  346. p = p->next;
  347. }
  348. return NULL;
  349. }
  350. //用于坐标转换的函数
  351. double graphx(double x) {
  352. return x * 10 + 100;
  353. }
  354. double realx(double gx) {
  355. return (gx - 100)*1.0 / 10;
  356. }
  357. int graphy(int y) {
  358. return 400 - y * 10;
  359. }
  360. int realy(int gy) {
  361. return (400 - gy) / 10;
  362. }
  363. //绘制坐标系
  364. void DrawCoordinate(int edges[], int edge_num) {
  365. line(100, 100, 100, 400);
  366. line(100, 400, 400, 400);
  367. outtextxy(85, 95, "y↑");
  368. outtextxy(400, 393, "→x");
  369. for (int i = 0; i < 30; ++i) {
  370. if (i % 2 != 0)
  371. continue;
  372. //TODO 字符转换
  373. outtextxy(i * 10 + 100, 390, "|");
  374. char *text = (char*)malloc(sizeof(char) * 10);
  375. itoa(i,text,10);
  376. outtextxy(i * 10 + 100, 410, text);
  377. free(text);
  378. }
  379. for (int j = 0; j < 30; ++j) {
  380. if (j % 2 != 0)
  381. continue;
  382. outtextxy(100, 400 - j * 10, "_");
  383. char *str = (char*)malloc(sizeof(char)*10);
  384. itoa(j,str,10);
  385. outtextxy(100, 400 - j * 10,str);
  386. free(str);
  387. }
  388. //绘制原多边形
  389. for (int k = 0; k < edge_num; ++k) {
  390. setcolor(YELLOW);
  391. int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
  392. x1 = edges[2 * k] * 10 + 100;
  393. y1 = 400 - edges[2 * k + 1] * 10;
  394. x2 = edges[2 * (k + 1)] * 10 + 100;
  395. y2 = 400 - edges[2 * (k + 1) + 1] * 10;
  396. line(x1, y1, x2, y2);
  397. }
  398. }
  399. //获取当前的涉及的扫描线的边
  400. //即取出当前ymin 小于curr_y的边
  401. //通过参数返回取出的边的个数
  402. Edge* GetCurrEdges(ET T, int curr_y, int &edge_num) {
  403. Edge *currEdges = (Edge*)malloc(sizeof(Edge) * 20); //分配最大容量
  404. int i = 0;
  405. TableItem *p = T.itemHeader;
  406. while (p != NULL) {
  407. Edge *q = p->firstNode;
  408. while (q != NULL) {
  409. if (q->y_min <= curr_y) { //等于号很重要,否则会在图形中出现空白区
  410. currEdges[i++] = *q; //将当前边的值取出(不改变原活动表)
  411. }
  412. q = q->next;
  413. }
  414. p = p->next;
  415. }
  416. edge_num = i; //保存取出的边的个数
  417. return currEdges;
  418. }
  419. //判断edge1 是否在edge2 的右边的方法
  420. bool IsRightTo(Edge edge1, Edge edge2) {
  421. if (edge1.x > edge2.x) //如果edge1最低点的x坐标小于edge2的最低点的x的坐标,则edge1在edge2的右边
  422. return true;
  423. else {
  424. if (edge1.x < edge2.x)
  425. return false;
  426. double x_max1 = (edge1.y_max - (edge1.y_min - 1.0 / edge1.deltax*edge1.x))*edge1.deltax;
  427. double x_max2 = (edge2.y_max - (edge2.y_min - 1.0 / edge2.deltax*edge2.x))*edge2.deltax;
  428. if (x_max1 > x_max2)
  429. return true;
  430. }
  431. return false;
  432. }
  433. };
  434. int main()
  435. {
  436. //TODO 测试活动边表初始化
  437. Solution solution;
  438. int edges[] = { 4,18,14,14,26,22,26,10,14,2,4,6,4,18 };
  439. Edge* newEdges = solution.InitEdges(edges, 6);
  440. ET T;
  441. solution.Init(T, newEdges, 6); //初始化活动边表
  442. initgraph(800, 800, SHOWCONSOLE);
  443. solution.DrawCoordinate(edges, 6);
  444. solution.Draw(T);
  445. getchar();
  446. closegraph();
  447. return 0;
  448. }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

原文链接:https://blog.csdn.net/qq_36573282/article/details/78633319

如果您对该产品感兴趣,请填写办理(客服微信:xiaoxiongyidong)

为您推荐:

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。