1. 程式人生 > 實用技巧 >二維圖形變換&裁剪

二維圖形變換&裁剪

實驗三 二維圖形變換&裁剪

一、綜述

掌握二維圖形顯示處理的原理、流程和實現方法,包括二維圖形空間建模、基本變換/變換序列、裁剪、視見變換和繪製處理以及簡單的互動控制手段。本實驗是矩形視窗裁剪,演算法包括: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 多邊形裁剪實現

實現思路很清晰,有大一部分思路是和線段裁剪相似。

  1. 順序儲存多邊形的頂點;
  2. 依次用每一條裁剪邊對輸入的頂點序列進行如下處理:
  • 若當前頂點可見(在裁剪框內部),則將該頂點加入到輸出頂點序列中(output());對當前頂點和下一頂點構成的線段進行裁剪判斷;
  • 如果這條線段邊和裁剪邊界有交點,則將該交點加入到輸出序列中;
  1. 最後進行封閉檢查,使得輸出序列中的頂點資訊的頭和尾是相同的。

四、處理流程

在根據cg2020QB2DTrans程式提供的原始碼,自建專案完善補充時,基本按照如下順序理解演算法及相關思路並完成實驗要求:

  1. 基本變換矩陣的實現
  2. 繪製直線
  3. 直線2D變換
  4. 直線裁剪
  5. 繪製多邊形
  6. 多邊形2D變換
  7. 多邊形裁剪

五、執行結果

六、實驗總結

​ 首先,現在對於MFC的相關呼叫及基礎實現應該比較熟練了,再者通過本次二維圖形變換實驗,學生進一步理解掌握二維圖形顯示處理的原理、流程和實現方法,將上課學習的裁剪方法落地,當然對於包括二維圖形空間建模、基本變換/變換序列、視見變換等簡單的互動控制手段有了清楚的認知。

實驗中遇到的問題及解決如下:

  1. 在初步補充完成直線裁剪、展示程式碼後執行沒有實驗結果(具體指執行時Picture Control沒有內容顯示),在幾次思考程式碼沒有問題情況下,請教同學,被告知是在執行介面有進一步操作後才有所展示,理解到控制元件展示的小視窗被規劃到onupdate函式中,瞭解。
  2. 重合多邊形輪廓的顏色總和視窗顏色保持一致,非常不便於觀察,在仔細考慮程式思路及打斷點後發現是繪製多邊形函式靠近結束部分,含有視窗輪廓重新繪製程式碼,去掉後解決該問題。
  3. picture control大小規劃的不是很合適,使2D圖形展示不是很完整,重新調整控制元件大小,問題解決。
  4. 視窗展示的多邊形方向不太對頭,懷疑是點規劃時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)++;
}

加油呀