参考 https://www.bilibili.com/video/BV1AP411n7xb
算法原理 - 可见侧与不可见侧
算法原理 - 直线参数方程
算法原理 – 裁剪条件
算法原理 – 直线段与窗口边界不平行
算法原理 – 直线段与窗口边界平行-垂直线
算法原理 – 直线段与窗口边界平行-水平线
算法设计
原文案例
源码
// 20-Liang-Barsky算法
// 参考 https://www.bilibili.com/video/BV1AP411n7xb
#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <math.h>
#define WINDOW_TEXT L"20-Liang-Barsky算法"
#define ROUND(d) int(floor(d)+0.5) // 四舍五入
struct Point2 // 二维点
{
double x;
double y;
Point2() :x(0), y(0) {}
Point2(double x, double y) :x(x), y(y) {}
};
class ClipLine
{
public:
int wxl; //窗口左
int wxr; //窗口右
int wyt; //窗口上
int wyb; //窗口下
public:
ClipLine()
{
wxl = -300;
wxr = 300;
wyt = 100;
wyb = -100;
};
~ClipLine() {};
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 LiangBarsky(Point2& P0, Point2& P1) // Liang-Barsky 裁剪算法
{
double x0 = P0.x;
double y0 = P0.y;
double x1 = P1.x;
double y1 = P1.y;
double t0 = 0.0;
double t1 = 1.0;
double deltaX = P1.x - P0.x;
if (Clipt(-deltaX, x0 - wxl, t0, t1)) // i=1 左边界 p1 = -deltaX, q1 = x0 - wxl
{
if (Clipt(deltaX, wxr - x0, t0, t1)) // i=2 右边界 p2 = deltaX, q2 = wxr - x0
{
double deltaY = P1.y - P0.y;
if (Clipt(-deltaY, y0 - wyb, t0, t1)) // i=3 下边界 p3 = -deltaY, q3 = y0 - wyb
{
if (Clipt(deltaY, wyt - y0, t0, t1)) // i=4 上边界 p4 = deltaY, q4 = wyt - y0
{
if (t1 < 1.0) // 计算裁剪后的直线段的终点
{
P1.x = x0 + t1 * deltaX; // 重新计算直线段的终点坐标
P1.y = y0 + t1 * deltaY; // x=x0+t(x1-x0)
}
if (t1 > 0.0)// 计算裁剪后的直线段的起点
{
P0.x = x0 + t0 * deltaX; // 重新计算直线段的终点坐标
P0.y = y0 + t0 * deltaY; // x=x0+t(x1-x0)
}
}
}
}
}
}
bool Clipt(double p,double q,double&t0,double &t1) // 裁剪测试
{
double r;
bool bAccept = true; // 接受
if (p < 0) // 直线段从窗口边界的不可见侧到可见侧,计算起点处的t0
{
r = q / p;
if (r > t1)
bAccept = false; // 拒绝
else if (r > t0)
t0 = r;
}
else if (p > 0) // 直线段从窗口边界的可见侧到不可见侧,计算终点处的t1
{
r = q / p;
if (r < t0)
bAccept = false; // 拒绝
else if (r < t1)
t1 = r;
}
else // 平行窗口边界的直线,p=0
{
if (q < 0) // 直线段在窗口外可直接删除
{
bAccept = false; // 拒绝
}
}
return bAccept;
}
};
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, TRUE);
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);
// 绘制图形
{
ClipLine clip;
clip.DrawClipWindow(memDC);
Point2 p0(-400, -200);
Point2 p1(400, 200);
if (m_Play)
{
clip.LiangBarsky(p0, p1); // 裁剪直线
}
MoveToEx(memDC, ROUND(p0.x), ROUND(p0.y), NULL);
LineTo(memDC, ROUND(p1.x), ROUND(p1.y));
}
// 内存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;
}
总结