實驗三 二維圖形變換&裁剪
一、綜述
掌握二維圖形顯示處理的原理、流程和實現方法,包括二維圖形空間建模、基本變換/變換序列、裁剪、視見變換和繪制處理以及簡單的交互控制手段。本實驗是矩形窗口裁剪,算法包括:Cohen-Sutherland裁剪算法,Sutherland多邊形裁剪
二、程序框架
MFC程序:
Ccg2020LMMDrawLineView.h是視圖層的頭文件,負責聲明各種成員變量和成員函數。
Ccg2020LMMDrawLineView.cpp是視圖層的源文件,負責實現裁剪和基本圖形變換等功能。
CgTransControl.h是為窗口面板的按鍵和文本定義成員變量和成員函數。
CgTransControl.cpp是實現面板的功能,例如+x,-x,+y,-y,對比等圖形裁剪輸出展示。
三、模塊設計算法描述
Cohen-Sutherland裁剪算法
1.1 基本思想
對於每條線段P1P2分為三種情況處理:
(1)若P1P2完全在窗口內,則顯示該線段P1P2。
(2)若P1P2明顯在窗口外,則丟棄該線段。
(3)若線段不滿足(1)或(2)的條件,則在交點處把線段分為兩段。其中一段完全在窗口外,可棄之。然后對另一段重復上述處理。
1.2編碼方式
把窗口的邊界延長成直線,窗口平台就分成9個分區,每個區設定一個4位的編碼與之對應。
- 若x<wxl,則D0=1,否則D0=0;
- 若x>wxr,則D1=1,否則D1=0;
- 若y<wyb,則D2=1,否則D2=0;
- 若y>wyt,則D3=1,否則D3=0。
1.3裁剪
- 若P1P2完全在窗口內code1=0,且code2=0,則“取”;
- 若P1P2明顯在窗口外code1&code2≠0,則“棄” ;
- 在交點處把線段分為兩段。其中一段完全在窗口外,可棄之。然后對另一段重復上述處理。
1.4 求交處理
按左、下、右、上的順序求出直線段與窗口邊界的交點,分段處理
Sutherland-Hodgman 多邊形裁剪實現
實現思路很清晰,有大一部分思路是和線段裁剪相似。
- 順序存儲多邊形的頂點;
- 依次用每一條裁剪邊對輸入的頂點序列進行如下處理:
- 若當前頂點可見(在裁剪框內部),則將該頂點加入到輸出頂點序列中(output());對當前頂點和下一頂點構成的線段進行裁剪判斷;
- 如果這條線段邊和裁剪邊界有交點,則將該交點加入到輸出序列中;
- 最后進行封閉檢查,使得輸出序列中的頂點信息的頭和尾是相同的。
四、處理流程
在根據cg2020QB2DTrans程序提供的源碼,自建項目完善補充時,基本按照如下順序理解算法及相關思路並完成實驗要求:
- 基本變換矩陣的實現
- 繪制直線
- 直線2D變換
- 直線裁剪
- 繪制多邊形
- 多邊形2D變換
- 多邊形裁剪
五、運行結果
六、實驗總結
首先,現在對於MFC的相關調用及基礎實現應該比較熟練了,再者通過本次二維圖形變換實驗,學生進一步理解掌握二維圖形顯示處理的原理、流程和實現方法,將上課學習的裁剪方法落地,當然對於包括二維圖形空間建模、基本變換/變換序列、視見變換等簡單的交互控制手段有了清楚的認知。
實驗中遇到的問題及解決如下:
- 在初步補充完成直線裁剪、展示代碼后運行沒有實驗結果(具體指運行時Picture Control沒有內容顯示),在幾次思考代碼沒有問題情況下,請教同學,被告知是在運行界面有進一步操作后才有所展示,理解到控件展示的小窗口被規划到onupdate函數中,了解。
- 重合多邊形輪廓的顏色總和窗口顏色保持一致,非常不便於觀察,在仔細考慮程序思路及打斷點后發現是繪制多邊形函數靠近結束部分,含有窗口輪廓重新繪制代碼,去掉后解決該問題。
- picture control大小規划的不是很合適,使2D圖形展示不是很完整,重新調整控件大小,問題解決。
- 窗口展示的多邊形方向不太對頭,懷疑是點規划時x,y是不是有什么加減沒有定義好,是y的問題應該改為dcRect.bottom - (pDoc->clipPolygon[i].y - pDoc->m_wndLy)*Sy;問題解決
收獲良多,謝謝。
核心代碼
void CalculateMatrix(float transMatrix[3][2])函數的系列變換:
void Ccg2020YBH2DtransView::RotateMatrix(float S, float C, float m[3][2])
{
float temp;
for (int i = 0; i < 3; i++) {
temp = m[i][0] * C - m[i][1] * S;
m[i][1] = m[i][0] * S + m[i][1] * C;
m[i][0] = temp;
}
}
void Ccg2020YBH2DtransView::TranslateMatrix(float Tx, float Ty, float m[3][2])
{
m[2][0] += Tx;
m[2][1] += Ty;
}
void Ccg2020YBH2DtransView::ScaleMatrix(float Sx, float Sy, float m[3][2])
{
m[0][0] *= Sx;
m[1][1] *= Sy;
}
直線、多邊形2D變換及繪制:
void Ccg2020YBH2DtransView::TransLine(CPoint *p1, CPoint *p2, CPoint *tp1, CPoint *tp2,float transMatrix[3][2])
{
tp1->x = (int)(p1->x * transMatrix[0][0] + p1->y * transMatrix[1][0] + transMatrix[2][0]);
tp1->y = (int)(p1->x * transMatrix[0][1] + p1->y * transMatrix[1][1] + transMatrix[2][1]);
tp2->x = (int)(p2->x * transMatrix[0][0] + p2->y * transMatrix[1][0] + transMatrix[2][0]);
tp2->y = (int)(p2->x * transMatrix[0][1] + p2->y * transMatrix[1][1] + transMatrix[2][1]);
}
void Ccg2020YBH2DtransView::DisplayLine(CDC* pDC, CPoint p1, CPoint p2, COLORREF rgbColor)
{
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
CPen newPen;
CPen *oldPen;
CPoint VP1, VP2;
newPen.CreatePen(PS_SOLID, 2, rgbColor);
oldPen = (CPen *)pDC->SelectObject(&newPen);
VP1.x = m_wndWidth / 2 + p1.x;
VP1.y = m_wndHeight / 2 - p1.y;
VP2.x = m_wndWidth / 2 + p2.x;
VP2.y = m_wndHeight / 2 - p2.y;
pDC->MoveTo(VP1);
pDC->LineTo(VP2);
pDC->SelectObject(oldPen);
newPen.DeleteObject();
}
void Ccg2020YBH2DtransView::TransPolygon(int pointNumber, CPoint spPolygon[N],
CPoint transPolygon[N], float transMatrix[3][2])
{
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
for (int i = 0; i < pointNumber; i++) {
transPolygon[i].x = spPolygon[i].x * transMatrix[0][0] +
spPolygon[i].y * transMatrix[1][0] +transMatrix[2][0];
transPolygon[i].y = spPolygon[i].x * transMatrix[0][1] +
spPolygon[i].y * transMatrix[1][1] +transMatrix[2][1];
}
}
void Ccg2020YBH2DtransView::DisplayPolygon(CDC* pDC, int pointNumber,
CPoint transPolygon[N], COLORREF rgbColor)
{
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
CPen newPen;
CPen *oldPen;
CPoint VPolygon[N];
newPen.CreatePen(PS_SOLID, 2, rgbColor);
oldPen = (CPen *)pDC->SelectObject(&newPen);
for (int i = 0; i < pointNumber; i++) {
VPolygon[i].x = m_wndWidth / 2 + transPolygon[i].x;
VPolygon[i].y = m_wndHeight / 2 - transPolygon[i].y;
}
pDC->MoveTo(VPolygon[0]);
for (int i = 1; i < pointNumber; i++) pDC->LineTo(VPolygon[i]);
pDC->SelectObject(oldPen);
newPen.DeleteObject();
}
Cohen-Sutherland裁剪:
// Cohn-Sutherland Subdivision Line Clip
int Ccg2020YBH2DtransView::ClipLine(int *x1, int *y1, int *x2, int *y2)
{
int visible, m_window[4];
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
m_window[0] = pDoc->m_wndLx; m_window[1] = pDoc->m_wndRx;
m_window[2] = pDoc->m_wndRy; m_window[3] = pDoc->m_wndLy;
for (int i = 0; i < 4; i++) { // Along the WIN Border
visible = LineVisible(x1, y1, x2, y2);
if (visible == 1) return 1; // Total Visible
if (visible == 0) return 0; // Total Unvisible
if (LineCross(*x1, *y1, *x2, *y2, i)) {
if (i < 2 && *x2 - *x1) { // Left , Right
float m = (float)(*y2 - *y1) / (*x2 - *x1);
float iy = m * (m_window[i] - *x1) + *y1;
// 根據端點大小,來進行端點的更新,舍棄window框之外的部分
if (i == 0) {
if (*x1 < *x2) {
*x1 = m_window[i];
*y1 = iy;
}
else {
*x2 = m_window[i];
*y2 = iy;
}
}
else {
if (*x1 > *x2) {
*x1 = m_window[i];
*y1 = iy;
}
else {
*x2 = m_window[i];
*y2 = iy;
}
}
}
else if (*y2 - *y1) { // Top Bottom
float m = (float)(*x2 - *x1) / (*y2 - *y1);
float ix = m * (m_window[i] - *y1) + *x1;
// Please fill in the right code below ...
if (i == 2) {
if (*y1 > *y2) {
*x1 = ix;
*y1 = m_window[i];
}
else {
*x2 = ix;
*y2 = m_window[i];
}
}
else {
if (*y1 < *y2) {
*x1 = ix;
*y1 = m_window[i];
}
else {
*x2 = ix;
*y2 = m_window[i];
}
}
}
}
}
return 1;
}
int Ccg2020YBH2DtransView::LineVisible(int *x1, int *y1, int *x2, int *y2)
{
int pcode1, pcode2;
pcode1 = pCode(x1, y1);
pcode2 = pCode(x2, y2);
if (!pcode1 && !pcode2) return 1; // Visible
if ((pcode1&pcode2) != 0) return 0; // Unvisible
if (pcode1 == 0) {
float temp;
temp = *x1; *x1 = *x2; *x2 = temp;
temp = *y1; *y1 = *y2; *y2 = temp;
}
return 2;
}
int Ccg2020YBH2DtransView::pCode(int *x, int *y)
{
int code = 0;
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
if (*x <= pDoc->m_wndLx) code |= 1;
if (*x >= pDoc->m_wndRx) code |= 2;
if (*y >= pDoc->m_wndRy) code |= 4;
if (*y <= pDoc->m_wndLy) code |= 8;
return code;
}
int Ccg2020YBH2DtransView::LineCross(int x1, int y1, int x2, int y2, int i)
{
int visible1, visible2;
visible1 = pVisible(x1, y1, i);
visible2 = pVisible(x2, y2, i);
if (visible1 != visible2) return 1;
else return 0;
}
int Ccg2020YBH2DtransView::pVisible(int x, int y, int i)
{
int visible = 0;
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
switch (i) {
case 0: // Left
if (x >= pDoc->m_wndLx) visible = 1; break;
case 1: // Right
if (x <= pDoc->m_wndRx) visible = 1; break;
case 2: // Top
if (y <= pDoc->m_wndRy) visible = 1; break;
case 3: // Bottom
if (y >= pDoc->m_wndLy) visible = 1; break;
}
return visible;
}
Sutherland-Hodgman 多邊形裁剪:
// Sutherland-Hodgman Polygon Clip
int Ccg2020YBH2DtransView::ClipPolygon(int n, CPoint *tPoints, int *cn, CPoint *cPoints)
{
int Nin, Nout, ix, iy, Sx, Sy;
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
Nin = n;
for (int i = 0; i < 4; i++) { // Along the window border
*cn = 0;
for (int j = 0; j < Nin; j++) { // Scan polygon every point and line.
if (j > 0) {
// 如果存在邊穿過邊界,則更新頂點
if (LineCross(Sx, Sy, tPoints[j].x, tPoints[j].y, i)) {
interSect(Sx, Sy, tPoints[j].x, tPoints[j].y, i, &ix, &iy);
outPut(ix, iy, cn, cPoints);
}
}
//當前頂點可見
Sx = tPoints[j].x;
Sy = tPoints[j].y;
if (pVisible(Sx, Sy, i))
outPut(Sx, Sy, cn, cPoints);
}
Nin = *cn;
if (*cn == 0) return 0;
for (int j = 0; j < Nin; j++) {
tPoints[j].x = cPoints[j].x;
tPoints[j].y = cPoints[j].y;
}
if (cPoints[0].x != cPoints[Nin - 1].x ||
cPoints[0].y != cPoints[Nin - 1].y) {
tPoints[Nin].x = cPoints[Nin].x = cPoints[0].x;
tPoints[Nin].y = cPoints[Nin].y = cPoints[0].y;
Nin++;
*cn = Nin;
}
}
return 1;
}
void Ccg2020YBH2DtransView::interSect(int Sx, int Sy, int Px, int Py,
int i, int *ix, int *iy)
{
Ccg2020YBH2DtransDoc* pDoc = GetDocument();
switch (i) {
case 0: // Left
*ix = pDoc->m_wndLx;
*iy = (Sy - Py) * (pDoc->m_wndLx - Px) / (Sx - Px) + Py;
break;
case 1: // Right
*ix = pDoc->m_wndRx;
*iy = (Sy - Py) * (pDoc->m_wndRx - Px) / (Sx - Px) + Py;
break;
case 2: // Top
*iy = pDoc->m_wndRy;
*ix = (Sx - Px) * (pDoc->m_wndRy - Py) / (Sy - Py) + Px;
break;
case 3: // Bottom
*iy = pDoc->m_wndLy;
*ix = (Sx - Px) * (pDoc->m_wndLy - Py) / (Sy - Py) + Px;
break;
}
}
void Ccg2020YBH2DtransView::outPut(int x, int y, int *cn, CPoint *cPoints)
{
cPoints[*cn].x = x;
cPoints[*cn].y = y;
(*cn)++;
}
加油呀