Загрузка данных


#include <windows.h>
#include <vector>
#include <cmath>
#include <cstring>

struct Pt
{
    double x, y;
};

using Poly = std::vector<Pt>;


// =====================================================
// ГЛОБАЛЬНЫЕ ДАННЫЕ
// =====================================================

static Poly g_arrow;

static bool g_visible = true;

static RECT g_clip =
{
    150, 100,
    550, 430
};

static bool  g_dragging = false;
static POINT g_dragStart;


// =====================================================
// СОЗДАНИЕ ФИГУРЫ
// =====================================================

static void BuildArrow()
{
    g_arrow =
    {
        {180, 220},
        {340, 220},
        {340, 180},

        {520, 265},

        {340, 350},
        {340, 310},

        {180, 310}
    };
}


// =====================================================
// ГЕОМЕТРИЯ
// =====================================================

static double Cross(Pt a, Pt b, Pt c)
{
    return
        (b.x - a.x) * (c.y - a.y) -
        (b.y - a.y) * (c.x - a.x);
}

static bool Intersect(
    Pt p1,
    Pt p2,
    Pt e1,
    Pt e2,
    Pt& out)
{
    double dx = p2.x - p1.x;
    double dy = p2.y - p1.y;

    double ex = e2.x - e1.x;
    double ey = e2.y - e1.y;

    double d = dx * ey - dy * ex;

    if (fabs(d) < 1e-9)
        return false;

    double t =
        ((e1.x - p1.x) * ey -
         (e1.y - p1.y) * ex) / d;

    out.x = p1.x + t * dx;
    out.y = p1.y + t * dy;

    return true;
}


// =====================================================
// ОТСЕЧЕНИЕ ОДНОЙ ГРАНЬЮ
// =====================================================

static Poly ClipByEdge(
    const Poly& poly,
    Pt e1,
    Pt e2)
{
    Poly out;

    int n = (int)poly.size();

    for (int i = 0; i < n; ++i)
    {
        Pt cur = poly[i];
        Pt nxt = poly[(i + 1) % n];

        double cc = Cross(e1, e2, cur);
        double cn = Cross(e1, e2, nxt);

        bool curInside = cc >= 0;
        bool nxtInside = cn >= 0;

        if (curInside)
        {
            out.push_back(cur);

            if (!nxtInside)
            {
                Pt p;
                Intersect(cur, nxt, e1, e2, p);

                out.push_back(p);
            }
        }
        else if (nxtInside)
        {
            Pt p;
            Intersect(cur, nxt, e1, e2, p);

            out.push_back(p);
        }
    }

    return out;
}


// =====================================================
// АЛГОРИТМ ОТСЕЧЕНИЯ
// =====================================================

static Poly WeilerAtherton(
    const Poly& subject,
    RECT clip)
{
    double l = clip.left;
    double r = clip.right;
    double t = clip.top;
    double b = clip.bottom;

    Poly res = subject;

    res = ClipByEdge(res, {l, b}, {l, t});
    res = ClipByEdge(res, {l, t}, {r, t});
    res = ClipByEdge(res, {r, t}, {r, b});
    res = ClipByEdge(res, {r, b}, {l, b});

    return res;
}


// =====================================================
// РИСОВАНИЕ МНОГОУГОЛЬНИКА
// =====================================================

static void DrawPoly(
    HDC hdc,
    const Poly& poly,
    COLORREF color,
    bool fill)
{
    if (poly.size() < 2)
        return;

    std::vector<POINT> pts(poly.size());

    for (size_t i = 0; i < poly.size(); ++i)
    {
        pts[i].x = (LONG)poly[i].x;
        pts[i].y = (LONG)poly[i].y;
    }

    HPEN pen =
        CreatePen(PS_SOLID, 2, color);

    HBRUSH brush =
        fill
        ? CreateSolidBrush(color)
        : (HBRUSH)GetStockObject(NULL_BRUSH);

    HPEN oldPen =
        (HPEN)SelectObject(hdc, pen);

    HBRUSH oldBrush =
        (HBRUSH)SelectObject(hdc, brush);

    Polygon(hdc, pts.data(), (int)pts.size());

    SelectObject(hdc, oldPen);
    SelectObject(hdc, oldBrush);

    DeleteObject(pen);

    if (fill)
        DeleteObject(brush);
}


// =====================================================
// РИСОВАНИЕ СЦЕНЫ
// =====================================================

static void DrawScene(HWND hwnd)
{
    PAINTSTRUCT ps;

    HDC hdc = BeginPaint(hwnd, &ps);

    RECT rc;
    GetClientRect(hwnd, &rc);

    HDC mem = CreateCompatibleDC(hdc);

    HBITMAP bmp =
        CreateCompatibleBitmap(
            hdc,
            rc.right,
            rc.bottom);

    HBITMAP oldBmp =
        (HBITMAP)SelectObject(mem, bmp);

    // фон

    HBRUSH bg =
        CreateSolidBrush(RGB(0, 0, 0));

    FillRect(mem, &rc, bg);

    DeleteObject(bg);

    // окно отсечения

    {
        HPEN pen =
            CreatePen(
                PS_DASH,
                1,
                RGB(70, 70, 70));

        HPEN oldPen =
            (HPEN)SelectObject(mem, pen);

        SelectObject(
            mem,
            GetStockObject(NULL_BRUSH));

        Rectangle(
            mem,
            g_clip.left,
            g_clip.top,
            g_clip.right,
            g_clip.bottom);

        SelectObject(mem, oldPen);

        DeleteObject(pen);
    }

    // фигура

    if (g_visible)
    {
        DrawPoly(
            mem,
            g_arrow,
            RGB(255,255,255),
            false);

        Poly clipped =
            WeilerAtherton(
                g_arrow,
                g_clip);

        if (!clipped.empty())
        {
            DrawPoly(
                mem,
                clipped,
                RGB(255,0,0),
                true);
        }
    }

    // текст

    SetBkMode(mem, TRANSPARENT);

    SetTextColor(
        mem,
        RGB(200,200,200));

    HFONT font =
        CreateFontA(
            16,
            0,
            0,
            0,
            FW_NORMAL,
            FALSE,
            FALSE,
            FALSE,
            DEFAULT_CHARSET,
            OUT_DEFAULT_PRECIS,
            CLIP_DEFAULT_PRECIS,
            CLEARTYPE_QUALITY,
            DEFAULT_PITCH,
            "Consolas");

    HFONT oldFont =
        (HFONT)SelectObject(mem, font);

    const char* hint =
        "LMB - move | Del - delete | R - reset | Esc - exit";

    TextOutA(
        mem,
        10,
        rc.bottom - 24,
        hint,
        (int)strlen(hint));

    SelectObject(mem, oldFont);

    DeleteObject(font);

    // вывод

    BitBlt(
        hdc,
        0,
        0,
        rc.right,
        rc.bottom,
        mem,
        0,
        0,
        SRCCOPY);

    SelectObject(mem, oldBmp);

    DeleteObject(bmp);
    DeleteDC(mem);

    EndPaint(hwnd, &ps);
}


// =====================================================
// ОКОННАЯ ПРОЦЕДУРА
// =====================================================

static LRESULT CALLBACK WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wp,
    LPARAM lp)
{
    switch (msg)
    {
        case WM_PAINT:
        {
            DrawScene(hwnd);
            return 0;
        }

        case WM_LBUTTONDOWN:
        {
            g_dragging = true;

            g_dragStart =
            {
                (short)LOWORD(lp),
                (short)HIWORD(lp)
            };

            SetCapture(hwnd);

            return 0;
        }

        case WM_MOUSEMOVE:
        {
            if (g_dragging)
            {
                int dx =
                    (short)LOWORD(lp) -
                    g_dragStart.x;

                int dy =
                    (short)HIWORD(lp) -
                    g_dragStart.y;

                for (auto& p : g_arrow)
                {
                    p.x += dx;
                    p.y += dy;
                }

                g_dragStart =
                {
                    (short)LOWORD(lp),
                    (short)HIWORD(lp)
                };

                InvalidateRect(
                    hwnd,
                    NULL,
                    FALSE);
            }

            return 0;
        }

        case WM_LBUTTONUP:
        {
            g_dragging = false;

            ReleaseCapture();

            return 0;
        }

        case WM_KEYDOWN:
        {
            if (wp == VK_DELETE)
            {
                g_visible = false;

                InvalidateRect(
                    hwnd,
                    NULL,
                    FALSE);
            }

            if (wp == 'R')
            {
                BuildArrow();

                g_visible = true;

                InvalidateRect(
                    hwnd,
                    NULL,
                    FALSE);
            }

            if (wp == VK_ESCAPE)
            {
                PostQuitMessage(0);
            }

            return 0;
        }

        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }

    return DefWindowProc(
        hwnd,
        msg,
        wp,
        lp);
}


// =====================================================
// ТОЧКА ВХОДА
// =====================================================

int main()
{
    HINSTANCE hInst =
        GetModuleHandleA(NULL);

    BuildArrow();

    WNDCLASSEXA wc = {};

    wc.cbSize = sizeof(wc);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInst;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =
        (HBRUSH)(COLOR_WINDOW + 1);

    wc.lpszClassName = "WA_Demo";

    RegisterClassExA(&wc);

    HWND hwnd =
        CreateWindowExA(
            0,
            "WA_Demo",
            "Weiler-Atherton Clipping",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            750,
            520,
            NULL,
            NULL,
            hInst,
            NULL);

    ShowWindow(hwnd, SW_SHOW);

    UpdateWindow(hwnd);

    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}