如何根据箭头计算箭头的坐标?

时间:2012-04-25 12:58:25

标签: algorithm

我有一条基于我知道的两个(x,y)坐标的线。该行有一个起点和终点。现在我想在该行的终点添加一个箭头。

我知道箭头是等边三角形,因此每个角度都有60度。另外,我知道一边的长度,即20。我也没有三角形的边缘(这是直线的终点)。

如何计算三角形的其他两个点?我知道我应该使用一些三角函数,但是如何?

P.S。该线的终点应该是箭头的尖端。

6 个答案:

答案 0 :(得分:9)

你不需要触发器,只需要一些矢量算术......

假设线从A到B,箭头的前顶点在B处。箭头的长度是h = 10(√3),它的半宽是w = 10.我们将表示单位矢量从A到B为U =(B - A)/ | B - A | (即,差值除以差值的长度),与此垂直的单位矢量为V = [-U y ,U x ]。

根据这些数量,您可以将箭头的两个后顶点计算为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;
}

如果要指定不同的角度,则需要一些触发。计算hw的不同值。假设你想要一个长度为h和尖角θ的箭头,那么w = h tan(θ/ 2)。但实际上,最简单的方法是直接指定hw

答案 1 :(得分:8)

这是一个示例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. 然后从线垂直出一定距离
  4. 请注意,如果您希望箭头线的角度与45度不同,则必须使用其他方法。

    上面的程序每次都会绘制10个随机箭头,这里有一个例子:

    arrow example

答案 2 :(得分:3)

我们的行是(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)

答案 3 :(得分:2)

我想根据Marcelo Cantos的回答在C#中提供答案,因为算法运行得非常好。我写了一个程序来计算投射在CCD阵列上的激光束的质心。找到质心后,绘制方向角线,我需要指向该方向的箭头。由于计算了角度,箭头必须沿任何方向跟随角度。

Length = 10, Half-Width = 10

Length = 20, Half-Width = 10

enter image description here

此代码可让您灵活地更改箭头大小,如图所示。

首先,您需要带有所有必要运算符重载的向量结构。

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

然后你可以使用Marcelo Cantos给出的相同代码,但我制作了箭头变量的长度和半宽度,以便你可以在调用函数时定义它。

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是第二个矢量位置(箭头)。

我通过将矢量值存储在System.Drawings中的graphics.DrawPolygon()函数的点来绘制箭头。代码如下所示:

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

答案 4 :(得分:1)

你可以找到线的角度。

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

假设箭头的上部是线的末端,它将完美地旋转并适合线。 没试过它=(

答案 5 :(得分:1)

对于任何有兴趣的人,@ 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实现,所以我想我会发布结果。