跟我一起学图形编程
作者:姚明 联系方式:alanvincentmail@gmail.com 2011年1月26日 17:42:15
点,线,面,我们这节课学学面的填充和生成。千万不要忽略这节课的重要性,我们平时看到的3维图形,一般是由三角网格构成,比如,一个人,一座山,一张桌子。网格被填充了颜色后,就是我们看到的栩栩如生的样子。但是,它们不是被简单的填充单一的颜色,被填充的颜色是经过纹理,光照,材质等运算后得到的结果,这是一个复杂的过程,专业术语称其为象素着色。这些内容,以后的课程我们慢慢深入学习。在这里,我们只用随机颜色的线条简单的填充整个多边形表面。
理论:
填充的算法大概分为两种思路,第一种,扫描线法,想象一下,从一个封闭多边形外面一点开始,画一直线,与多边形相交,通过检测扫描线上每点的状态,就能区分出,多边形外部和内部的点。第二种,种子算法,先取多边形内部任意一点做种子,由这个种子,向左右,上下扩散,最终填充整个多边形内部。我认为,扫描线算法适用于,多边形各顶点值已知,即边界已知情况下的填充。种子算法适用于,边界未知情况,例如,屏幕上有多个多边形重叠区域的填充。详细的算法细节请参考点击下载的相关章节。下面给出扫描线算法的实现代码。
内容:
1//-----------------------------------------------------------------------------------------------
2// 功能: 填充多边形
3//
4// 参数: lpPoints: 指向顶点坐标数组的指针,数组类型为POINT,多边形由它们顺次封闭连接得到
5// nCount: 顶点的个数
6// nColor: 填充的颜色 默认为黑色
7// DC: 设备句柄
8//
9// 返回: 无返回值
10//
11// 说明: 可以是边相交的多边形
12//
13// 创建(修改): 2011-1-13 16:31 姚明
14//-----------------------------------------------------------------------------------------------
15void FillPolygon(LPPOINT lpPoints, int nCount, int nColor /**//*=0*/,HDC &DC)
16{
17 // 边结构数据类型
18 typedef struct Edge
19 {
20 int ymax; // 边的最大y坐标
21 float x; // 与当前扫描线的交点x坐标
22 float dx; // 边所在直线斜率的倒数
23 struct Edge *pNext; // 指向下一条边
24 } Edge, *LPEdge;
25
26 int i = 0, j = 0, k = 0;
27 int y0 = 0, y1 = 0; // 扫描线的最大和最小y坐标
28 LPEdge pAET = NULL; // 活化边表头指针
29 LPEdge *pET = NULL; // 边表头指针
30
31 pAET = new Edge; // 初始化表头指针,第一个元素不用
32 pAET->pNext = NULL;
33
34 // 获取y方向扫描线边界
35 y0 = y1 = lpPoints[0].y;
36 for (i = 1; i < nCount; i++)
37 {
38 if (lpPoints[i].y < y0)
39 y0 = lpPoints[i].y;
40 else if (lpPoints[i].y > y1)
41 y1 = lpPoints[i].y;
42 }
43 if (y0 >= y1)
44 return ;
45
46 // 初始化边表,第一个元素不用
47 pET = new LPEdge[y1 - y0 + 1];
48 for (i = 0; i <= y1 - y0; i++)
49 {
50 pET[i] = new Edge;
51 pET[i]->pNext = NULL;
52 }
53
54 for (i = 0; i < nCount; i++)
55 {
56 j = (i + 1) % nCount; // 组成边的下一点
57 if (lpPoints[i].y != lpPoints[j].y)
58 // 如果该边不是水平的则加入边表
59 {
60 LPEdge peg; // 指向该边的指针
61 LPEdge ppeg; // 指向边指针的指针
62
63 // 构造边
64 peg = new Edge;
65 k = (lpPoints[i].y > lpPoints[j].y) ? i : j;
66 peg->ymax = lpPoints[k].y; // 该边最大y坐标
67 k = (k == j) ? i : j;
68 peg->x = (float)lpPoints[k].x; // 该边与扫描线焦点x坐标
69 if (lpPoints[i].y != lpPoints[j].y)
70 peg->dx = (float)(lpPoints[i].x - lpPoints[j].x) / (lpPoints[i].y -
71 lpPoints[j].y);
72 // 该边斜率的倒数
73 peg->pNext = NULL;
74
75 // 插入边
76 ppeg = pET[lpPoints[k].y - y0];
77 while (ppeg->pNext)
78 ppeg = ppeg->pNext;
79 ppeg->pNext = peg;
80 } // end if
81 } // end for i
82
83 // 扫描
84 for (i = y0; i <= y1; i++)
85 {
86 LPEdge peg0 = pET[i - y0]->pNext;
87 LPEdge peg1 = pET[i - y0];
88 if (peg0)
89 // 有新边加入
90 {
91 while (peg1->pNext)
92 peg1 = peg1->pNext;
93 peg1->pNext = pAET->pNext;
94 pAET->pNext = peg0;
95 }
96
97 // 按照x递增排序pAET
98 peg0 = pAET;
99 while (peg0->pNext)
100 {
101 LPEdge pegmax = peg0;
102 LPEdge peg1 = peg0;
103 LPEdge pegi = NULL;
104
105 while (peg1->pNext)
106 {
107 if (peg1->pNext->x > pegmax->pNext->x)
108 pegmax = peg1;
109 peg1 = peg1->pNext;
110 }
111 pegi = pegmax->pNext;
112 pegmax->pNext = pegi->pNext;
113 pegi->pNext = pAET->pNext;
114 pAET->pNext = pegi;
115 if (peg0 == pAET)
116 peg0 = pegi;
117 }
118
119 // 遍历活边表,画线
120 peg0 = pAET;
121 while (peg0->pNext)
122 {
123 if (peg0->pNext->pNext)
124 {
125 Bresenham((int)peg0->pNext->x, i, (int)peg0->pNext->pNext->x, i, DC);
126 peg0 = peg0->pNext->pNext;
127 }
128 else
129 break;
130 }
131
132 // 把ymax=i的节点从活边表删除并把每个节点的x值递增dx
133 peg0 = pAET;
134 while (peg0->pNext)
135 {
136 if (peg0->pNext->ymax < i + 2)
137 {
138 peg1 = peg0->pNext;
139 peg0->pNext = peg0->pNext->pNext; //删除
140 delete peg1;
141 continue;
142 }
143 peg0->pNext->x += peg0->pNext->dx; //把每个节点的x值递增dx
144 peg0 = peg0->pNext;
145 }
146 }
147
148 // 删除边表
149 for (i = 0; i < y1 - y0; i++)
150 if (pET[i])
151 delete pET[i];
152
153 if (pAET)
154 delete pAET;
155 if (pET)
156 delete []pET;
157}
分析:
以下是被修改后的WM_TIMER消息代码
1 case WM_TIMER:
2 GetClientRect (hwnd, &rect) ;
3 if(rect.right <=0 || rect.bottom<=0) return 0; //窗口最小化后结束绘制
4 hdc = GetDC (hwnd) ;
5 hdcMem = CreateCompatibleDC(NULL); //创建内存设备环境
6 hBitmap = CreateCompatibleBitmap(hdc,
7 rect.right, rect.bottom); //创建内存设备环境相关的位图
8 SelectObject(hdcMem, hBitmap); //选择位图对象到内存设备环境
9
10 for(int i=0;i<12;i++) // 循环次数必须是偶数,否则最后一根线无法形成闭合区域
11 {
12// COLORREF crColor = RGB(rand()%256,rand()%256,rand()%256); //随机产生点的颜色值
13// SetPixel (hdc, x, y, crColor) ; //在显示设备环境中绘制点
14// SetPixel (hdcMem, x, y, crColor) ;//在内存设备环境中绘制点
15
16 static int xTemp = -1;
17 static int yTemp = -1;
18
19 x = rand()%rect.right; //随机产生点的X坐标
20 y = rand()%rect.bottom; //随机产生点的Y坐标
21
22 Bresenham(rect.right/2,rect.bottom/2,x,y,hdcMem);
23
24 if(xTemp != -1 && yTemp != -1)
25 {
26 Bresenham(x,y,xTemp,yTemp,hdcMem);
27
28 POINT pts[3]; //填充区域的3个顶点
29 pts[0].x = rect.right/2;
30 pts[0].y = rect.bottom/2;
31 pts[1].x = x;
32 pts[1].y = y;
33 pts[2].x = xTemp;
34 pts[2].y = yTemp;
35 FillPolygon(pts , 3 , 0 ,hdcMem);
36
37 xTemp = -1;
38 yTemp = -1;
39
40 }else
41 {
42 xTemp = x;
43 yTemp = y;
44 }
45 }
46
47 BitBlt(hdc,0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY); //将内存设备环境中的数据传到显示设备环境显示
48 DeleteObject(hBitmap); //释放位图对象
49 DeleteDC (hdcMem) ; //释放内存设备环境
50 ReleaseDC (hwnd, hdc) ; //释放显示设备环境
51 return 0 ;
posted on 2011-01-26 17:34
姚明 阅读(1730)
评论(0) 编辑 收藏 引用 所属分类:
原创教程