3

所以,我试图在 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 不会改变)。

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

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

编辑

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

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

最后的解决方案(更改):

GetPointAngle(P1, P2: TPoint)至:GetPointAngle(const P1, P2: TPoint)

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

4

3 回答 3

7

我假设您要计算相对于在这两个点之间形成的线的 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;

编辑:

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

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

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

于 2013-03-24T08:51:58.790 回答
4

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

在此处输入图像描述

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

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;

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

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

在此处输入图像描述

仍然可以表示为两个向量之间的角度。第一个向量是 p 2 -p 1,另一个是水平方向的向量 (0, 1)。把这两个喂进去AngleBetweenVectors,你就有答案了。如果要测量垂直角度,则可以使用相同的想法。

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

于 2013-03-24T08:27:24.570 回答
1

以下代码使用 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.
于 2013-03-24T09:53:13.253 回答