windows - C ++ Tegn en linje i GDI med et fyldt pilhoved i slutningen

Indlæg af Hanne Mølgaard Plasc

Problem



Kan nogen hjælpe mig med nogle ideer (helst med kode) om hvordan man tegner en linje med et fyldt pilespids i slutningen?


Pilens hoved skal være korrekt orienteret med retningen af ​​linjen. Jeg vil gerne gøre dette i C ++.

Bedste reference


Det tager lidt geometri at finde ud af hjørnerne af trekanten, der danner pilens hoved.


Antag, at linjen går fra P0 til P1, og at vi ønsker spidsen af ​​pilen på P1. For at finde 'baghjørnerne' på pilespidsen ønsker vi at flytte fra spidsen tilbage langs linjen og derefter dreje til venstre og højre for at give pilen en vis bredde.


Dette ville være enkelt, hvis linjen var justeret med x- eller y-aksen. For at håndtere linjer i en hvilken som helst vinkel kan vi konstruere et koordinatsystem, hvis akser er parallelle med og vinkelret på den oprindelige linje. Vi kalder disse akser u og v, med du peger i retningen af ​​linjen og v vinkelret på den.


Nu kan vi starte ved P0 og flytte til hjørnerne ved at træde i retninger bestemt af dig og v, skaleret, uanset hvilken pilens længde og bredde vi ønsker.


I kode:


constexpr int Round(float x) { return static\_cast<int>(x + 0.5f); }

// Draws a line from p0 to p1 with an arrowhead at p1.  Arrowhead is outlined
// with the current pen and filled with the current brush.
void DrawArrow(HDC hdc, POINT p0, POINT p1, int head\_length, int head\_width) {
    ::MoveToEx(hdc, p0.x, p0.y, nullptr);
    ::LineTo(hdc, p1.x, p1.y);

    const float dx = static\_cast<float>(p1.x - p0.x);
    const float dy = static\_cast<float>(p1.y - p0.y);
    const auto length = std::sqrt(dx*dx + dy*dy);
    if (head\_length < 1 || length < head\_length) return;

    // ux,uy is a unit vector parallel to the line.
    const auto ux = dx / length;
    const auto uy = dy / length;

    // vx,vy is a unit vector perpendicular to ux,uy
    const auto vx = -uy;
    const auto vy = ux;

    const auto half\_width = 0.5f * head\_width;

    const POINT arrow[3] =
        { p1,
          POINT{ Round(p1.x - head\_length*ux + half\_width*vx),
                 Round(p1.y - head\_length*uy + half\_width*vy) },
          POINT{ Round(p1.x - head\_length*ux - half\_width*vx),
                 Round(p1.y - head\_length*uy - half\_width*vy) }
        };
    ::Polygon(hdc, arrow, 3);
}


Og en demo (ved hjælp af WTL):


LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL &) {
    PAINTSTRUCT ps;
    BeginPaint(&ps);

    RECT rc;
    GetClientRect(&rc);

    const auto origin = POINT{rc.left + (rc.right - rc.left)/2,
                                rc.top + (rc.bottom - rc.top)/2 };
    const auto pi = 3.1415926f;
    const auto tau = 2.0f*pi;
    const auto cxInch = ::GetDeviceCaps(ps.hdc, LOGPIXELSX);
    const auto radius = 2.0f * cxInch;
    const auto size = Round(0.333f * cxInch);
    for (float theta = 0.0f; theta < tau; theta += tau/12.0f) {
        const auto p1 =
            POINT{Round(origin.x + radius * std::cos(theta)),
                    Round(origin.y + radius * std::sin(theta))};
        DrawArrow(ps.hdc, origin, p1, size, size/3);
    }
    EndPaint(&ps);
    return 0;
}