参考 https://www.bilibili.com/video/BV1mN4y1c772/
算法原理
代码
// 21-Sutherland-Hodgman裁剪算法
// 参考 https://www.bilibili.com/video/BV1mN4y1c772/
#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <math.h>
#define WINDOW_TEXT L"21-Sutherland-Hodgman裁剪算法"
#define ROUND(d) int(floor(d)+0.5) // 四舍五入
#define INMAX 7 // 最大输入顶点数
#define OUTMAX 12 // 最大输出顶点数
#define LEFT 1
#define RIGHT 2
#define TOP 3
#define BOTTOM 4
struct Point2 // 二维点
{
double x;
double y;
Point2():x(0),y(0) {}
Point2(double x, double y):x(x),y(y) {}
};
struct AET // 有效边表 ActiveEdgeTable
{
AET() :x(0), yMax(0), m(0), pNext(NULL) {};
~AET() {};
double x; // 扫描线与有效边交叉点的x坐标
int yMax; // 边的最大值
double m; // 斜率的倒数 1/k (x的增量)
AET* pNext;
};
struct Bucket // 桶表
{
Bucket() :ScanLine(0), pET(NULL), pNext(NULL) {};
~Bucket() {};
int ScanLine; // 扫描线;
AET* pET; // 桶上的边表指针
Bucket* pNext;
};
// 12-有效边表填充算法
class Fill
{
public:
Fill()
{
PNum = 0;
P = NULL;
pHeadE = NULL;
pCurrentE = NULL;
pEdge = NULL;
pHeadB = NULL;
pCurrentB = NULL;
};
~Fill() {};
void SetPoint(Point2* p, int n) // 初始化顶点和顶点数目
{
P = new Point2[n]; // 创建一维动态数组 转储数组中的顶点
for (int i = 0; i < n; i++)
{
P[i] = p[i];
}
PNum = n; // 记录顶点数量
}
void CreateBucket() // 创建桶表
{
int yMin, yMax; // 最小 最大 扫描线
yMin = yMax = P[0].y;
for (int i = 0; i < PNum; i++) // 查找多边形所覆盖的最小和最大扫描线
{
if (P[i].y < yMin)
{
yMin = P[i].y;
}
if (P[i].y > yMax)
{
yMax = P[i].y;
}
}
for (int y = yMin; y < yMax; y++)
{
if (yMin == y)
{
pHeadB = new Bucket; // 建立桶头
pCurrentB = pHeadB; // 桶当前节点指针
pCurrentB->ScanLine = yMin;
pCurrentB->pET = NULL; // 桶上没有链接边表
pCurrentB->pNext = NULL;
}
else // 其他扫描线
{
pCurrentB->pNext = new Bucket;
pCurrentB = pCurrentB->pNext;
pCurrentB->ScanLine = y;
pCurrentB->pET = NULL;
pCurrentB->pNext = NULL;
}
}
}
void CreateEdge() // 创建边表,即将各边链入到相应的桶节点
{
for (int i = 0; i < PNum; i++)
{
pCurrentB = pHeadB;
int j = (i + 1) % PNum;
if (P[i].y < P[j].y)
{
pEdge = new AET;
pEdge->x = P[i].x;
pEdge->yMax = P[j].y;
pEdge->m = (double)(P[j].x - P[i].x) / ((double)(P[j].y - P[i].y));
pEdge->pNext = NULL;
while (pCurrentB->ScanLine != P[i].y)
{
pCurrentB = pCurrentB->pNext;
}
}
if (P[j].y < P[i].y)
{
pEdge = new AET;
pEdge->x = P[j].x;
pEdge->yMax = P[i].y;
pEdge->m = (double)(P[i].x - P[j].x) / ((double)(P[i].y - P[j].y));
pEdge->pNext = NULL;
while (pCurrentB->ScanLine != P[j].y)
{
pCurrentB = pCurrentB->pNext;
}
}
if (P[j].y != P[i].y)
{
pCurrentE = pCurrentB->pET;
if (pCurrentE == NULL)
{
pCurrentE = pEdge;
pCurrentB->pET = pCurrentE;
}
else
{
while (NULL != pCurrentE->pNext)
{
pCurrentE = pCurrentE->pNext;
}
pCurrentE->pNext = pEdge;
}
}
}
}
void FillPolyong(HDC hdc) // 填充多边形
{
AET* pT1 = NULL;
AET* pT2 = NULL;
pHeadE = NULL;
for (pCurrentB = pHeadB; pCurrentB != NULL; pCurrentB = pCurrentB->pNext)
{
for (pCurrentE = pCurrentB->pET; pCurrentE != NULL; pCurrentE = pCurrentE->pNext)
{
pEdge = new AET;
pEdge->x = pCurrentE->x;
pEdge->yMax = pCurrentE->yMax;
pEdge->m = pCurrentE->m;
pEdge->pNext = NULL;
AddEdge(pEdge);
}
SortEdge();
pT1 = pHeadE;
if (pT1 == NULL)
{
return;
}
while (pCurrentB->ScanLine >= pT1->yMax) // 下闭上开
{
AET* pAETTemp = pT1;
pT1 = pT1->pNext;
delete pAETTemp;
pHeadE = pT1;
if (NULL == pHeadE)
{
return;
}
}
if (pT1->pNext != NULL)
{
pT2 = pT1;
pT1 = pT2->pNext;
}
while (NULL != pT1)
{
if (pCurrentB->ScanLine >= pT1->yMax)// 下闭上开
{
AET* pAETTemp = pT1;
pT2->pNext = pT1->pNext;
pT1 = pT2->pNext;
delete pAETTemp;
}
else
{
pT2 = pT1;
pT1 = pT2->pNext;
}
}
bool bInside = false; // 内外测试标志,初始假 位于跨度外部
int xLeft, xRight; // 跨度的起点和终点坐标
for (pT1 = pHeadE; pT1 != NULL; pT1 = pT1->pNext)
{
if (false == bInside)
{
xLeft = ROUND(pT1->x);
bInside = true;
}
else
{
xRight = ROUND(pT1->x);
for (int x = xLeft; x < xRight; x++) // 左闭又开
{
SetPixel(hdc, x, pCurrentB->ScanLine, RGB(0, 0, 255));
}
bInside = false;
}
}
for (pT1 = pHeadE; pT1 != NULL; pT1 = pT1->pNext) // 边的连续性
{
pT1->x = pT1->x + pT1->m;
}
}
}
void AddEdge(AET* pNewEdge) // 合并边表
{
AET* pCurrentEdge = pHeadE; // 边表头结点
if (NULL == pCurrentEdge) // 若边表为空,则pNewEdge作为边表头结点
{
pHeadE = pNewEdge;
pCurrentEdge = pHeadE;
}
else // 将pNewEdge链接到边表末尾(未排序)
{
while (pCurrentEdge->pNext != NULL)
{
pCurrentEdge = pCurrentEdge->pNext;
}
pCurrentEdge->pNext = pNewEdge;
}
}
void SortEdge() // 边表冒泡排序
{
AET* pT1, * pT2;
int count = 1;
pT1 = pHeadE;
if (NULL == pT1) // 没有边,不需要排序
{
return;
}
if (NULL == pT1->pNext) // 如果该ET表没有在ET表中
{
return; // 桶结点只有一条边,不需要排序;
}
while (NULL != pT1->pNext) // 统计节点的个数
{
count++;
pT1 = pT1->pNext;
}
for (int i = 0; i < count - 1; i++) // 冒泡排序
{
AET** ppPre = &pHeadE; // ppPre记录当面两个节点的前面一个节点,第一次为头节点
pT1 = pHeadE;
for (int j = 0; j < count - 1 - i; j++)
{
pT2 = pT1->pNext;
if ((pT1->x > pT2->x) || ((pT1->x == pT2->x) && (pT1->m > pT2->m))) // 满足条件,则交换当前两个边结点的位置
{
pT1->pNext = pT2->pNext;
pT2->pNext = pT1;
*ppPre = pT2;
ppPre = &(pT2->pNext);// 调整位置为下一次遍历做准备
}
else //不交换当前两个边结点的位置,更新ppPre和pT1
{
ppPre = &(pT1->pNext);
pT1 = pT1->pNext;
}
}
}
}
void ClearMemory() // 清理内存
{
DeleteEdgeTableChain(pHeadE); //删除边表
Bucket* pBucket = pHeadB;
while (NULL != pBucket)// 针对每一个桶
{
Bucket* pBucketTemp = pBucket->pNext;
DeleteEdgeTableChain(pBucket->pET);
delete pBucket;
pBucket = pBucketTemp;
}
pHeadE = NULL;
pHeadB = NULL;
}
void DeleteEdgeTableChain(AET* pAET) // 删除边表
{
while (pAET != NULL)
{
AET* pAETTemp = pAET->pNext;
delete pAET;
pAET = pAETTemp;
}
}
private:
int PNum; // 顶点个数
Point2* P; // 顶点数组
AET* pHeadE, * pCurrentE, * pEdge; // 有效边表结点指针
Bucket* pHeadB, * pCurrentB; // 桶表节点指针
};
class Clip
{
public:
int _wxl; // 窗口左
int _wxr; // 窗口右
int _wyt; // 窗口上
int _wyb; // 窗口下
Point2* _pIn; // 裁剪前数组
Point2* _pOut; // 裁剪后数组
int _outCounter;// 裁剪后顶点个数
public:
Clip()
{
_wxl = -300;
_wxr = 300;
_wyt = 200;
_wyb = -200;
_pIn = NULL;
_pOut = NULL;
_outCounter = 0;
ReadPoint();
};
~Clip()
{
if (_pIn!=NULL)
{
delete[] _pIn;
_pIn = NULL;
}
if (_pOut != NULL)
{
delete[] _pOut;
_pOut = NULL;
}
};
void DrawObject(HDC hdc,bool bPlay)
{
Fill* pFill = new Fill;
if (!bPlay)
{
Point2* P = new Point2[INMAX];
for (int i = 0; i < INMAX; i++)
{
P[i] = Point2(ROUND(_pIn[i].x), ROUND(_pIn[i].y));
}
pFill->SetPoint(P, INMAX); // 设置顶点
pFill->CreateBucket(); // 建立桶表
pFill->CreateEdge(); // 建立边表
pFill->FillPolyong(hdc); // 填充多边形
delete[]P;
}
else
{
ClipPolygon(_pIn,INMAX,LEFT);
ClipPolygon(_pOut,_outCounter,RIGHT);
ClipPolygon(_pOut,_outCounter,BOTTOM);
ClipPolygon(_pOut,_outCounter,TOP);
ClipPolygon(_pOut,_outCounter,LEFT);
Point2* P = new Point2[_outCounter];
for (int i = 0; i < _outCounter; i++)
{
P[i] = Point2(ROUND(_pOut[i].x), ROUND(_pOut[i].y));
}
pFill->SetPoint(P, _outCounter); // 设置顶点
pFill->CreateBucket(); // 建立桶表
pFill->CreateEdge(); // 建立边表
pFill->FillPolyong(hdc); // 填充多边形
delete[]P;
}
delete pFill;
}
void DrawClipWindow(HDC hdc)
{
HPEN newPen = CreatePen(PS_SOLID, 3, RGB(0, 0, 128));
HGDIOBJ oldPen = SelectObject(hdc, newPen);
MoveToEx(hdc, _wxl, _wyb, NULL);
LineTo(hdc, _wxr, _wyb);
LineTo(hdc, _wxr, _wyt);
LineTo(hdc, _wxl, _wyt);
LineTo(hdc, _wxl, _wyb);
SelectObject(hdc, oldPen);
DeletePen(newPen);
}
void ReadPoint() // 读入点表
{
_pIn = new Point2[INMAX]; // 输入 顶点表 数组
_pIn[0].x = 50; _pIn[0].y = 100;
_pIn[1].x = -150; _pIn[1].y = 300;
_pIn[2].x = -250; _pIn[2].y = 50;
_pIn[3].x = -150; _pIn[3].y = -250;
_pIn[4].x = 0; _pIn[4].y = -50;
_pIn[5].x = 100; _pIn[5].y = -250;
_pIn[6].x = 300; _pIn[6].y = 150;
_pOut = new Point2[OUTMAX]; // 输出顶点表数组
for (int i = 0; i < OUTMAX; i++)
{
_pOut[i] = Point2(0,0);
}
}
void ClipPolygon(Point2* out, int length, unsigned int boundary) // 裁剪多边形
{
if (0==length)
{
return;
}
Point2* pTemp = new Point2[length];
for (int i = 0; i < length; i++)
{
pTemp[i] = out[i];
}
Point2 p0, p1, p;// p0-起点 ,p1-终点 ,p-交点
_outCounter = 0;
p0 = pTemp[length-1];
for (int i = 0; i < length; i++)
{
p1 = pTemp[i];
if (Inside(p0, boundary)) // 起点在窗口内
{
if (Inside(p1, boundary)) // 终点在窗口内 属于 内->内
{
_pOut[_outCounter] = p1; // 终点在窗口内
_outCounter++;
}
else // 属于 内->外
{
p = Intersect(p0,p1, boundary); // 求交点
_pOut[_outCounter] = p;
_outCounter++;
}
}
else if (Inside(p1, boundary)) // 终点在窗口内,属于外->内
{
p = Intersect(p0, p1, boundary);
_pOut[_outCounter] = p;
_outCounter++;
_pOut[_outCounter] = p1;
_outCounter++;
}
p0 = p1;
}
delete[] pTemp;
}
bool Inside(Point2 p,unsigned int boundary) // 点在窗口边界内判断函数
{
switch (boundary)
{
case LEFT:
if (p.x>=_wxl)
return true;
break;
case RIGHT:
if (p.x <= _wxr)
return true;
break;
case TOP:
if (p.y <= _wyt)
return true;
break;
case BOTTOM:
if (p.y >= _wyb)
return true;
break;
}
return false;
}
Point2 Intersect(Point2 p0,Point2 p1,unsigned int boundary) // 计算交叉点
{
Point2 pTemp;
double k = (p1.y - p0.y) / (p1.x - p0.x); // 斜率
switch (boundary)
{
case LEFT:
pTemp.x = _wxl;
pTemp.y = k * (pTemp.x - p0.x) + p0.y;
break;
case RIGHT:
pTemp.x = _wxr;
pTemp.y = k * (pTemp.x - p0.x) + p0.y;
break;
case TOP:
pTemp.y = _wyt;
pTemp.x = (pTemp.y - p0.y) / k + p0.x;
break;
case BOTTOM:
pTemp.y = _wyb;
pTemp.x = (pTemp.y - p0.y) / k + p0.x;
break;
}
return pTemp;
}
};
static bool m_Play = false;
static wchar_t* m_str = L"左键 - 裁剪 | 恢复";
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rc;
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_LBUTTONDOWN:
m_Play = !m_Play;
InvalidateRect(hwnd, NULL, FALSE); // FALSE 不重绘背景
return 0;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
// 内存DC
HDC memDC = CreateCompatibleDC(hdc); // 创建兼容DC 画板
HBITMAP newBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom); // 创建画布
HGDIOBJ oldBitmap = SelectObject(memDC, newBitmap); // 将画布选入画板
FillRect(memDC, &rc, (HBRUSH)(COLOR_WINDOW + 1));
DrawText(memDC, m_str, wcslen(m_str), &rc, NULL);
SetMapMode(memDC, MM_ANISOTROPIC);
SetWindowExtEx(memDC, rc.right, -rc.bottom, NULL);
SetViewportExtEx(memDC, rc.right, -rc.bottom, NULL);
SetViewportOrgEx(memDC, rc.right / 2, rc.bottom / 2, NULL);
// 绘制图形
{
Clip clip;
clip.DrawClipWindow(memDC);
clip.DrawObject(memDC, m_Play);
}
// 内存dc复制到设备
BitBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, memDC, ROUND(-rc.right / 2), ROUND(-rc.bottom / 2), SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(newBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"CAG";
WNDCLASS wc = { };
wc.style = CS_HREDRAW | CS_VREDRAW; // 重新绘制整个工作区
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
WINDOW_TEXT, // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
总结