21-Sutherland-Hodgman裁剪算法

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

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值