参考:https://www.bilibili.com/video/BV1bd4y187JM
B样条基函数
基函数周期性
基函数定义域t取值范围3~4之间
最终基函数表达式 t规范到0~1
三次B样条曲线
算法设计
完整代码
// 29-三次B样条曲线算法
// 参考 https://www.bilibili.com/video/BV1bd4y187JM
#define UNICODE
#include <Windows.h>
#include <Windowsx.h>
#include <string>
#include <math.h>
#define WINDOW_TEXT L"29-三次B样条曲线算法"
#define ROUND(d) int(floor(d)+0.5) // 四舍五入
#define PI 3.1415926
struct Point2 // 二维点
{
double x;
double y;
double w; // 齐次坐标
Point2() :x(0), y(0), w(1) {}
Point2(double x, double y) :x(x), y(y), w(1) {}
friend Point2 operator + (Point2 pt0, Point2 pt1)
{
return Point2(pt0.x + pt1.x, pt0.y + pt1.y);
}
friend Point2 operator * (Point2 pt, double n)
{
return Point2(pt.x * n, pt.y * n);
}
friend Point2 operator * (double n, Point2 pt)
{
return Point2(pt.x * n, pt.y * n);
}
};
class BSplineCurves // 样条曲线
{
public:
Point2* P; // 顶点数组
int _ptNumber; // 顶点个数
public:
BSplineCurves()
{
P = NULL;
_ptNumber = -1;
};
~BSplineCurves() {};
void ReadPoint( Point2 * p,int ptNumber)
{
P = p;
_ptNumber = ptNumber;
}
void DrawBSplineCurves(HDC hdc) // 绘制曲线
{
HPEN redPen = CreatePen(PS_SOLID, 2, RGB(200, 0, 0));
HPEN greenPen = CreatePen(PS_SOLID, 5, RGB(0, 200, 0));
Point2 sPt; // 曲线上的点
sPt.x = ROUND((P[0].x + 4.0 * P[1].x + P[2].x) / 6); // t=0起点x坐标
sPt.y = ROUND((P[0].y + 4.0 * P[1].y + P[2].y) / 6); // t=0起点y坐标
HGDIOBJ oldPen = SelectObject(hdc, greenPen);
Ellipse(hdc, sPt.x - 5, sPt.y - 5, sPt.x + 5, sPt.y + 5);
MoveToEx(hdc,sPt.x,sPt.y,NULL);
double tStep = 0.01; // 步长
for (int i = 3; i < _ptNumber; i++) // 6段三次样条曲线
{
SelectObject(hdc, redPen);
for (double t = 0; t <= 1.0; t += tStep)
{
double F03 = (-t * t * t + 3 * t * t - 3 * t + 1) / 6; // 计算F0,3(t)
double F13 = (3 * t * t * t - 6 * t * t + 4) / 6; // 计算F1,3(t)
double F23 = (-3 * t * t * t + 3 * t * t + 3 * t + 1) / 6; // 计算F2,3(t)
double F33 = t * t * t / 6; // 计算F3,3(t)
sPt = P[i - 3] * F03 + P[i - 2] * F13 + P[i - 1] * F23 + P[i] * F33;
LineTo(hdc, sPt.x, sPt.y);
}
SelectObject(hdc, greenPen);
Ellipse(hdc, sPt.x - 5, sPt.y - 5, sPt.x + 5, sPt.y + 5);
}
SelectObject(hdc, oldPen);
DeletePen(redPen);
DeletePen(greenPen);
}
void DrawControlGrid(HDC hdc,int ctrlPtNum,HWND hwnd) // 绘制控制线
{
MoveToEx(hdc,P[0].x, P[0].y,NULL );
for (int i = 0; i < _ptNumber; i++)
{
LineTo(hdc, P[i].x, P[i].y);
Ellipse(hdc, P[i].x - 5, P[i].y - 5, P[i].x + 5, P[i].y + 5);
}
}
};
// 顶点
Point2 m_P[9] = {
Point2(-600,-50),
Point2(-500,200),
Point2(-160,250),
Point2(-250,-300),
Point2(160,-200),
Point2(200,200),
Point2(600,180),
Point2(700,-60),
Point2(500,-200)
};
wchar_t* m_str = L"移动顶点修改曲线形状!";
bool m_bLBtnDown = false; // 左键按下
bool m_bMove = false; // 鼠标移动
int m_CtrlPtNum = -1; // 控制顶点编号
int m_ClientWidth = 0; // 客户区宽度
int m_ClientHeight = 0; // 客户区高度
BSplineCurves m_BSpline; // 曲线
Point2 Convert(Point2 point) // 屏幕坐标转为设备坐标
{
return Point2(point.x - m_ClientWidth / 2,-(point.y - m_ClientHeight / 2));
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
static double alpha;
static double beta;
int x;
int y;
Point2 curPt;
wchar_t text[1024] = {};
m_BSpline.ReadPoint(m_P, 9); // 读取数据
switch (uMsg)
{
case WM_LBUTTONDOWN: // 鼠标按下
m_bLBtnDown = true;
m_bMove = true;
return 0;
case WM_LBUTTONUP: // 鼠标抬起
m_bLBtnDown = false;
m_bMove = false;
return 0;
case WM_MOUSEMOVE:
x = GET_X_LPARAM(lParam); // 窗口指针位置 X
y = GET_Y_LPARAM(lParam); // 窗口指针位置 Y
if (true == m_bMove)
{
if (m_CtrlPtNum==-1)
{
m_CtrlPtNum = 0;
}
else // !!!更新顶点位置
{
m_P[m_CtrlPtNum] = Convert(Point2(x, y));
}
}
m_CtrlPtNum = -1;
curPt = Convert(Point2(x, y)); // 当前设备坐标位置
for (int i = 0; i < 9; i++)
{
// 距离点超过 5 像素
if ((curPt.x - m_P[i].x) * (curPt.x - m_P[i].x) +(curPt.y - m_P[i].y) * (curPt.y - m_P[i].y) < 50)
{
m_CtrlPtNum = i;
SetCursor(LoadCursor(NULL,IDC_SIZEALL));
InvalidateRect(hwnd, NULL, FALSE); //窗口无效 产生一个 WM_PAINT 消息
wsprintf(text, L"29-三次B样条曲线算法 顶点: %d (%d , %d ) ", m_CtrlPtNum, (int)curPt.x, (int)curPt.y);
SetWindowText(hwnd, text); // 设置窗口标题
break;
}
}
if (!m_bLBtnDown)
{
SetWindowText(hwnd, L"29-三次B样条曲线算法"); // 设置窗口标题
}
return 0;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
m_ClientWidth= rect.right; // 保存客户端大小
m_ClientHeight= rect.bottom; // 保存客户端大小
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, rect.right, rect.bottom, NULL);
SetViewportExtEx(hdc, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(hdc, rect.right / 2, rect.bottom / 2, NULL);
// 内存DC
HDC memDC = CreateCompatibleDC(hdc); // 创建兼容DC 画板
HBITMAP newBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); // 创建画布
HGDIOBJ oldBitmap = SelectObject(memDC, newBitmap); // 将画布选入画板
FillRect(memDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
DrawText(memDC, m_str, wcslen(m_str), &rect, NULL);
SetMapMode(memDC, MM_ANISOTROPIC);
SetWindowExtEx(memDC, rect.right, rect.bottom, NULL);
SetViewportExtEx(memDC, rect.right, -rect.bottom, NULL);
SetViewportOrgEx(memDC, rect.right / 2, rect.bottom / 2, NULL);
// 绘制图形
{
m_BSpline.DrawBSplineCurves(memDC); //绘制曲线
m_BSpline.DrawControlGrid(memDC, m_CtrlPtNum, hwnd); //绘制控制线
}
// 内存dc复制到设备
BitBlt(hdc, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), rect.right, rect.bottom, memDC, ROUND(-rect.right / 2), ROUND(-rect.bottom / 2), SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(newBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
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;
}