【问题标题】:Angle between two vectors两个向量之间的角度
【发布时间】:2013-08-17 06:30:27
【问题描述】:

所以,我试图在 Delphi 中获得两个 TPoints 之间的角度,结果比我预期的要难。我得到的结果我无法解释(似乎是“度数”部分的一些问题,或者 ArcTan2 没有以我预期的形式返回一个总和。 - Delpi-v7:

function Modulo(x,y:Extended): Extended;
var d: Extended;
begin
  d := x / y;
  Result := (d - floor(d)) * y;
end;

function Degrees(Rads: Extended): Extended;
begin
  Result := Rads*(180/Pi);
end;

function GetPointAngle(P1, P2: TPoint): Extended;
begin
  Result := Modulo(Degrees(ArcTan2(-(P1.Y - P2.Y), P1.X - P2.X)) - 90, 360);
end;

然而,当我将代码移植到 Python 或在另一个 Pascal 变体中测试它时,上述 工作。但现在,它似乎返回了一个静态的总和(如果我“移动”第二个 TPoint,则不会改变)。

如果您想知道;我创建“模”函数只是因为“mod”运算符中使用的除法运算符舍入为 0,而不是向下舍入(因此负数不起作用)。

编辑:我注意到当p 远离另一点c(反之亦然)时,从GetPointAngle() 返回的值(角度)会增加,即使TPoint (p) 被拖动沿第二个 TPoint (c) 的 X 轴。

编辑

你们已经超越了自己,我看了大部分答案,似乎很难选择最佳答案!既然你们写的都很详细,我会用同样的细节来处理所有的事情:-)

另外:我在最初的帖子中没有分享的是,我的函数被导出为 DLL,以便从另一个 pascal 解释器(与 delphi 兼容)访问。

最终解决方案(已更改):

GetPointAngle(P1, P2: TPoint) 收件人:GetPointAngle(const P1, P2: TPoint)

^ 我不明白声明常量的必要性......

【问题讨论】:

  • “两点之间的角度”是什么意思?您是指两个向量之间的角度吗?
  • 没有“两点之间的角度”这样的东西
  • 我的错误,在一组坐标之间。
  • 两点之间没有夹角。您需要两条线来定义一个角度。我猜你想要垂直线和两点之间的线之间的角度。如我回答的第二部分所述。
  • 除非两点之间的距离真的很大,否则没有必要使用Extended。此外,Extended 是 Delphi 特定的值类型,考虑到您从 DLL 导出此例程,我强烈建议使用 Double

标签: delphi math


【解决方案1】:

我假设您要计算相对于在这两点之间形成的线的 X 轴的角度。

对于这种情况,以下公式适用:

Tan(a) = (P2.Y - P1.Y) / (P2.X - P1.X)

翻译为:

a = ArcTan((P2.Y - P1.Y) / (P2.X - P1.X))

当两个点的X坐标相同时,这显然会导致EDivByZero异常,所以你必须自己处理。此外,ArcTan 导致角度在 0°..90° 范围内(即 0..π/2),因此忽略正确的象限,而ArcTan2 导致角度在 -180°..180° 范围内.将结果添加 360° 以将负角度转换为正角度:

function AngleOfLine(const P1, P2: TPoint): Double;
begin
  if P2.X = P1.X then
    if P2.Y > P1.Y then
      Result := 90
    else
      Result := 270
  else
    Result := RadToDeg(ArcTan2(P2.Y - P1.Y, P2.X - P1.X));
  if Result < 0 then
    Result := Result + 360;
end;

结果:

  A := AngleOfLine(Point(10, 10), Point(20, 10)); // 0
  A := AngleOfLine(Point(10, 10), Point(20, 20)); // 45
  A := AngleOfLine(Point(10, 10), Point(10, 20)); // 90
  A := AngleOfLine(Point(10, 10), Point(0, 20));  // 135
  A := AngleOfLine(Point(10, 10), Point(0, 10));  // 180
  A := AngleOfLine(Point(10, 10), Point(0, 0));   // 225
  A := AngleOfLine(Point(10, 10), Point(10, 0));  // 270
  A := AngleOfLine(Point(10, 10), Point(20, 0));  // 315

现在,这是相对于世界坐标系的,默认情况下它的正 Y 轴指向上方。如果要将结果转换为 Y 轴正向下指向的设备坐标系,则从 360° 中减去结果:

Result := 360 - Result;

更新:

似乎ArcTan2 负责除以零(即使在 D7 中,尽管有文档),所以例程变得更加简单:

function AngleOfLine(const P1, P2: TPoint): Double;
begin
  Result := RadToDeg(ArcTan2((P2.Y - P1.Y),(P2.X - P1.X)));
  if Result < 0 then
    Result := Result + 360;
end;

编辑:

我注意到,当p 远离另一点c 时,从GetPointAngle() 返回的值会增加(反之亦然)。

这取决于。查看上图,如果第二个点沿 x 轴进一步移动,则角度会减小。如果第二个点沿 y 轴进一步移动,则角度会增加。当然,这取决于两个点都在哪个象限。

此外,您的代码否定ArcTan2 的第一个参数,并从结果中再减去90°。我不知道你的意思是什么以及是否是故意的,但这可能是意外结果的根源。

【讨论】:

  • 您在这里解释数学的工作做得很好,尽管您在没有解释的情况下从 atan 切换到 atan2。对此进行扩展会很好。但是,答案中的代码与问题中的代码相同,以水平为模,问题以垂直为模。至少 SLACKY 应该清楚如何推理这个问题。当一个问题被问到错过了关键信息,然后提问者消失了,真是太可惜了!
  • @David 谢谢,我添加了更多解释。事实上,一个可怜的 OP 并没有扩展他的问题。这可能是无知。我曾经问过一个问题,确信我已经付出了一切。第二天:好吧,最好还是把它删掉吧。 ;)
  • 否定第一个参数是顺时针而不是逆时针。从垂直而不是水平中减去 90 个度量。
  • 我对“消失”部分感到抱歉。我会做一些测试,为什么我的初始代码/数学失败仍然不清楚,从我所看到的来看,它没有错(?)。我从另一个 pascal-interpetor 导出了要访问的函数,这可能是函数失败的原因。
【解决方案2】:

我想你正在寻找的是两个向量之间的角度。也就是图中的θ:

代数点积在几何上可以表示为 1,v2> = |v1||v 2|cosθ。这可以重新排列以找到 θ = cos-11,v2>/(|v1||v2|).

function DotProduct(const v1, v2: TPoint): Integer;
begin
  Result := v1.X*v2.X + v1.Y*v2.Y;
end;

function Magnitude(const v: TPoint): Double;
begin
  Result := Sqrt(Sqr(v.X)+Sqr(v.Y));
end;

function AngleBetweenVectors(const v1, v2: TPoint): Double;
var
  Magv1, Magv2: Double;
begin
  Magv1 := Magnitude(v1);
  Magv2 := Magnitude(v2);
  if abs(Magv1*Magv2)=0.0 then
    Result := 0.0
  else
    Result := ArcCos(EnsureRange(DotProduct(v1,v2)/(Magv1*Magv2), -1.0, 1.0));
end;

返回一个以弧度为单位的角度。您可以使用 Math 单位中的 RadToDeg() 将其转换为度数。

现在,解释你的问题的另一种方法是你想取两个点并在它们之间形成一条线。然后找到那条线和水平线之间的角度,比如说。如图所示:

仍然可以表示为两个向量之间的角度。第一个向量是p2-p1,另一个是水平方向的向量,(0, 1)。将这两个输入AngleBetweenVectors,您就有了答案。如果你想测量垂直的角度,那么你可以使用相同的想法。

希望这里有足够的内容供您解决问题,无论它实际上是什么。

【讨论】:

  • 提示:通过选择“圆弧”并在放置圆弧时按住 Alt 来创建圆弧。然后使用黄色选择器使其基本上是 360°。然后确保将其原点放置在两条线的交点处。最后,使用黄色标记将弧线调整为仅位于两条线之间。这种方法的一个问题是弧对象will occupy a lot of space 的不可见部分,但由于您只创建文档的PNG 屏幕截图,因此不会有问题。
  • @AndreasRejbrand 谢谢。如您所知,我对 Word 的了解不如对开发工具的熟悉。
  • 嗯,从我的超级用户问题或我的long report 关于使用 Word 编写物理教科书可以看出,Word 肯定可以是 PITA。
【解决方案3】:

以下代码在 Delphi 7 和 FPC 2.7.1 中返回相同的结果,并且看起来是正确的。
所以主要问题是:我们期待什么以及我们拥有什么?

program Project2;

{$APPTYPE CONSOLE}
uses
    Math;

{.$define speed}

function CalcAngle(const lx, ly: extended): extended; {$ifdef speed} inline; {$endif}
begin
    Result := RadToDeg(ArcTan2(ly, lx));
end;

function Modulo(x, y: extended): extended; {$ifdef speed} inline; {$endif}
var
    d: extended;
begin
    d := x / y;
    Result := (d - floor(d)) * y;
end;

function Degrees(Rads: Extended): Extended;
begin
  Result := Rads*(180/Pi);
end;

function Modulo2(x: extended): extended; {$ifdef speed} inline; {$endif}
begin
    if x < 0 then
        Result := 360 + x
    else
        Result := x;
end;

function GetPointAngle(const lx, ly: integer): Extended;
begin
    Result := Modulo(Degrees(ArcTan2(ly, lx)) - 90, 360);
end;

procedure OutTest(const lx, ly: extended);
var
    a: extended;
begin
    a := CalcAngle(lx, ly);
    Writeln(
        a: 10: 4,
        Modulo(a - 90, 360):10:4,
        GetPointAngle(round(lx), round(ly)):10:4);
end;

begin
    OutTest(2, 0);
    OutTest(0, 2);
    OutTest(-2, 2);
    OutTest(-2, -2);
    OutTest(2, 3);
    OutTest(100, 2);
    Readln;
end.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-17
    相关资源
    最近更新 更多