29-三次B样条曲线算法

参考: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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值