Как рассчитать координаты стрелки на основе стрелки?

У меня есть линия, основанная на двух (x,y) координатах, которые я знаю. Эта линия имеет начальную и конечную точку. Теперь я хочу добавить стрелку в конечной точке линии.

Я знаю, что стрелка представляет собой равносторонний треугольник, и поэтому каждый угол имеет 60 градусов. Кроме того, я знаю длину одной стороны, которая будет равна 20. У меня также нет одного края треугольника (это конечная точка линии).

Как я могу рассчитать две другие точки треугольника? Я знаю, что должен использовать некоторую тригонометрию, но как?

Ps Конечная точка линии должна быть наконечником стрелки.

6 ответов

Решение

Вам не нужно триг., Просто некоторая векторная арифметика...

Скажем, линия идет от А к В, с передней вершиной стрелки в точке B. Длина стрелки равна h = 10(√3), а ее полуширина равна w = 10. Мы обозначим единичный вектор из A к B как U = (B - A)/|B - A| (т. е. разница, деленная на длину разности) и единичный вектор, перпендикулярный этому, как V = [-Uy, Ux].

Из этих величин вы можете вычислить две задние вершины стрелки как B - hU ± wV.

В C++:

struct vec { float x, y; /* … */ };

void arrowhead(vec A, vec B, vec& v1, vec& v2) {
    float h = 10*sqrtf(3), w = 10;
    vec U = (B - A)/(B - A).length();
    vec V = vec(-U.y, U.x);
    v1 = B - h*U + w*V;
    v2 = B - h*U - w*V;
}

Если вы хотите указать разные углы, вам понадобится триггер. рассчитать разные значения h а также w, Предполагая, что вы хотите стрелку длины h и угла наклона θ, тогда w = h tan(θ/2). На практике, однако, проще всего указать h а также w непосредственно.

Вот пример программы LINQPad, которая показывает, как это сделать:

void Main()
{
    const int imageWidth = 512;
    Bitmap b = new Bitmap(imageWidth , imageWidth , PixelFormat.Format24bppRgb);

    Random r = new Random();
    for (int index = 0; index < 10; index++)
    {
        Point fromPoint = new Point(0, 0);
        Point toPoint = new Point(0, 0);

        // Ensure we actually have a line
        while (fromPoint == toPoint)
        {
            fromPoint = new Point(r.Next(imageWidth ), r.Next(imageWidth ));
            toPoint = new Point(r.Next(imageWidth ), r.Next(imageWidth ));
        }

        // dx,dy = arrow line vector
        var dx = toPoint.X - fromPoint.X;
        var dy = toPoint.Y - fromPoint.Y;

        // normalize
        var length = Math.Sqrt(dx * dx + dy * dy);
        var unitDx = dx / length;
        var unitDy = dy / length;

        // increase this to get a larger arrow head
        const int arrowHeadBoxSize = 10;

        var arrowPoint1 = new Point(
            Convert.ToInt32(toPoint.X - unitDx * arrowHeadBoxSize - unitDy * arrowHeadBoxSize),
            Convert.ToInt32(toPoint.Y - unitDy * arrowHeadBoxSize + unitDx * arrowHeadBoxSize));
        var arrowPoint2 = new Point(
            Convert.ToInt32(toPoint.X - unitDx * arrowHeadBoxSize + unitDy * arrowHeadBoxSize),
            Convert.ToInt32(toPoint.Y - unitDy * arrowHeadBoxSize - unitDx * arrowHeadBoxSize));

        using (Graphics g = Graphics.FromImage(b))
        {
            if (index == 0)
                g.Clear(Color.White);

            g.DrawLine(Pens.Black, fromPoint, toPoint);
            g.DrawLine(Pens.Black, toPoint, arrowPoint1);
            g.DrawLine(Pens.Black, toPoint, arrowPoint2);
        }
    }

    using (var stream = new MemoryStream())
    {
        b.Save(stream, ImageFormat.Png);
        Util.Image(stream.ToArray()).Dump();
    }
}

По сути, вы:

  1. Рассчитать вектор линии стрелки
  2. Нормализовать вектор, т.е. делая его длину 1
  3. Рассчитайте концы наконечников стрел, выполнив:
    1. Сначала от головы на определенное расстояние
    2. Затем перпендикулярно от линии на определенное расстояние

Обратите внимание, что если вы хотите, чтобы линии стрелок имели угол, отличный от 45 градусов, вам придется использовать другой метод.

Программа выше будет рисовать 10 случайных стрелок каждый раз, вот пример:

Пример стрелки

Давайте твоя линия (x0,y0)-(x1,y1)

Вектор обратного направления (dx, dy) = (x0-x1, y0-y1)

Это норма Norm = Sqrt(dx*dx+dy*dy)

Нормализовать это: (udx, udy) = (dx/Norm, dy/Norm)

Повернуть на углы Pi/6 а также -Pi/6

ax = udx * Sqrt(3)/2 - udy * 1/2

ay = udx * 1/2 + udy * Sqrt(3)/2

bx = udx * Sqrt(3)/2 + udy * 1/2

by =  - udx * 1/2 + udy * Sqrt(3)/2

Ваши очки: (x1 + 20 * ax, y1 + 20 * ay) а также (x1 + 20 * bx, y1 + 20 * by)

Я хочу внести свой ответ в C# на основе ответа Марсело Кантоса, так как алгоритм работает очень хорошо. Я написал программу для расчета центроида лазерного луча, спроецированного на матрицу ПЗС. После нахождения центроида рисуется линия угла направления, и мне нужно, чтобы головка стрелки указывала в этом направлении. Поскольку угол рассчитывается, наконечник стрелки должен следовать за углом в любом направлении.

Длина = 10, Полуширина = 10

Длина = 20, полуширина = 10

Этот код дает вам возможность изменять размер наконечника стрелки, как показано на рисунках.

Сначала вам нужна векторная структура со всеми необходимыми перегрузками операторов.

private struct vec
{
    public float x;
    public float y;

    public vec(float x, float y)
    {
        this.x = x;
        this.y = y;
    }

    public static vec operator -(vec v1, vec v2)
    {
        return new vec(v1.x - v2.x, v1.y - v2.y);
    }

    public static vec operator +(vec v1, vec v2)
    {
        return new vec(v1.x + v2.x, v1.y + v2.y);
    }

    public static vec operator /(vec v1, float number)
    {
        return new vec(v1.x / number, v1.y / number);
    }

    public static vec operator *(vec v1, float number)
    {
        return new vec(v1.x * number, v1.y * number);
    }

    public static vec operator *(float number, vec v1)
    {
        return new vec(v1.x * number, v1.y * number);
    }

    public float length()
    {
        double distance;
        distance = (this.x * this.x) + (this.y * this.y);
        return (float)Math.Sqrt(distance);
    }
}

Затем вы можете использовать тот же код, что и Марсело Кантос, но я установил длину и половину ширины переменных наконечника стрелы, чтобы вы могли определить это при вызове функции.

private void arrowhead(float length, float half_width, 
                       vec A, vec B, ref vec v1, ref vec v2)
{
    float h = length * (float)Math.Sqrt(3);
    float w = half_width;
    vec U = (B - A) / (B - A).length();
    vec V = new vec(-U.y, U.x);
    v1 = B - h * U + w * V;
    v2 = B - h * U - w * V;

}

Теперь вы можете вызвать функцию следующим образом:

vec leftArrowHead = new vec();
vec rightArrowHead = new vec();
arrowhead(20, 10, new vec(circle_center_x, circle_center_y), 
    new vec(x_centroid_pixel, y_centroid_pixel),
    ref leftArrowHead, ref rightArrowHead);

В моем коде центр круга - это первое векторное местоположение (стрелка), а centroid_pixel - это второе векторное местоположение (головка стрелки).

Я рисую наконечник стрелки, сохраняя векторные значения в точках для функции graphics.DrawPolygon() в System.Drawings. Код показан ниже:

Point[] ppts = new Point[3];
ppts[0] = new Point((int)leftArrowHead.x, (int)leftArrowHead.y);
ppts[1] = new Point(x_cm_pixel,y_cm_pixel);
ppts[2] = new Point((int)rightArrowHead.x, (int)rightArrowHead.y);

g2.DrawPolygon(p, ppts);

Вы можете найти угол линии.

Vector ox = Vector(1,0);
Vector line_direction = Vector(line_begin.x - line_end.x, line_begin.y - line_end.y);
line_direction.normalize();
float angle = acos(ox.x * line_direction.x + line_direction.y * ox.y);

Затем используйте эту функцию для всех 3 точек, используя найденный угол.

Point rotate(Point point, float angle)
{
    Point rotated_point;
    rotated_point.x = point.x * cos(angle) - point.y * sin(angle);
    rotated_point.y = point.x * sin(angle) + point.y * cos(angle);
    return rotated_point;
}

Предполагая, что верхняя точка наконечника стрелки является концом линии, она будет идеально вращаться и соответствовать линии. Не проверял это =(

Для всех, кто интересуется, @TomP интересовался версией js, так что вот версия javascript, которую я сделал. Он основан на ответах @Patratacus и @Marcelo Cantos. Javascript не поддерживает перегрузку операторов, поэтому он не так чист, как C++ или другие языки. Не стесняйтесь предлагать улучшения.

Я использую Class.js для создания классов.

Vector = Class.extend({
NAME: "Vector",

init: function(x, y)
{
    this.x = x;
    this.y = y;
},

subtract: function(v1)
{
    return new Vector(this.x - v1.x, this.y - v1.y);
},

add: function(v1)
{
    return new Vector(this.x + v1.x, this.y + v1.y);
},

divide: function(number)
{
    return new Vector(this.x / number, this.y / number);
},

multiply: function(number)
{
    return new Vector(this.x * number, this.y * number);
},

length: function()
{
    var distance;
    distance = (this.x * this.x) + (this.y * this.y);
    return Math.sqrt(distance);
}
});

А затем функция, чтобы сделать логику:

var getArrowhead = function(A, B)
{
    var h = 10 * Math.sqrt(3);
    var w = 5;
    var v1 = B.subtract(A);
    var length = v1.length();
    var U = v1.divide(length);
    var V = new Vector(-U.y, U.x);
    var r1 = B.subtract(U.multiply(h)).add(V.multiply(w));
    var r2 = B.subtract(U.multiply(h)).subtract(V.multiply(w));

    return [r1,r2];
}

И вызвать функцию следующим образом:

var A = new Vector(start.x,start.y);
var B = new Vector(end.x,end.y);    
var vec = getArrowhead(A,B);

console.log(vec[0]);
console.log(vec[1]);

Я знаю, что OP не запрашивал какой-либо конкретный язык, но я столкнулся с этим в поисках реализации JS, поэтому я решил опубликовать результат.

Другие вопросы по тегам