C ++ Нарисуйте линию в GDI с заполненным наконечником стрелки в конце - PullRequest
3 голосов
/ 12 ноября 2010

Может ли кто-нибудь помочь мне с некоторыми идеями (желательно с кодом) о том, как нарисовать линию с заполненной стрелкой в ​​конце?

Головка стрелки должна быть правильно ориентирована в направлении линии.Я хочу сделать это в C ++.

1 Ответ

2 голосов
/ 08 апреля 2017

Требуется небольшая геометрия, чтобы определить углы треугольника, которые образуют наконечник стрелки.

Предположим, что линия идет от P0 до P1, и нам нужен конец стрелки на P1. Чтобы найти «задние углы» стрелки, мы хотим отойти от вершины назад вдоль линии, а затем повернуть влево и вправо, чтобы придать стрелке некоторую ширину.

Это было бы просто, если бы линия была выровнена по оси x или y. Чтобы обрабатывать линии под любым углом, мы можем построить систему координат, оси которой параллельны и перпендикулярны исходной линии. Мы назовем эти оси u и v, где u указывает в направлении линии, а v перпендикулярно ей.

Теперь мы можем начать с P0 и двигаться к углам, шагая в направлениях, определенных u и v, масштабируемых для любой длины и ширины наконечника стрелы, которые мы хотим.

В коде:

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);
}

И демоверсия (с использованием 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;
}
...