数值稳定的角平分算法

时间:2017-04-16 07:57:17

标签: c++ math

是否存在数值稳定的角平分算法?

问题如下:

  • 给出三个向量(2维)A,B,C
  • 找到角度B的平分线(AB和BC之间的角度)

实际上我是按照以下方式计算的:

  • 规范化AB
  • 规范化BC
  • 查找(AB + CD)/ 2f(中点)
  • 平分线是在B和中点之间经过的线路。

我的方法的问题是,当角度几乎为180°(AB几乎平行于BC)时,平分线非常不准确(当然因为中点几乎与B重合)。当前算法是如此不准确,以至于有时得到的平分线几乎与其他2个段之一平行。

是的,没有“强制转换”问题,所有计算都是在单精度浮点中完成的。

5 个答案:

答案 0 :(得分:3)

如果将BA旋转+ 90°并且BC旋转-90°,则可以使用角平分线保持不变。

如果情况稳定,即使BA和BC的点积为正,则使用原始公式。

如果为负数,请为BA (x,y) -> (-y,x)BC (x,y) -> (y,-x)应用轮换,这也会使点积为正。像以前一样继续使用新的载体。

如果你试试这个,你会注意到,在矢量之间的角度-90°现在会发生平分线方向的跳跃。不可能避免这种跳跃,因为连续的平分线在两圈之后才会相同(固定BA和移动C)。

答案 1 :(得分:1)

你可以很简单地找到二等分矢量:

∥BC∥ * BA + ∥BA∥ * BC

但是,对于ABC共线或近似,这也不会在数值上稳定。更好的方法是通过点积找到AB和BC之间的角度。

cos θ = (BA · BC) / (∥BC∥ * ∥BA∥)

即使在共线情况下也会产生正确的角度。

答案 2 :(得分:1)

这不是小事。假设两个边缘向量是a和b:

float2 a = A - B;
float2 b = C - B;
  1. 计算点积float dp = dot( a, b )

  2. 归一化两个向量:

float2 a_norm = normalize( a );
float2 b_norm = normalize( b );
  1. 检查点积的符号位。当dp为非负数时, return normalize( a_norm + b_norm );,您就完成了。

  2. 当点积为负时,输入向量之间的角度为钝角。 在这种情况下,应用朴素的公式会增加数值精度。 需要另一种方式。

float2 c = normalize( a_norm - b_norm );
float dir = dot( a, rotate90( b ) );
return ( dir < 0 ) ? rotate90( c ) : rotate270( c );

请注意,-而不是+,这才是精度的保证。当ab之间的角度大于90°时,a-b之间的角度小于90°,​​长度a_norm - b_norm足够大,可以给出准确的方向。之后,我们只需要按正确的方向旋转90°即可。

P.S。将2D向量旋转90°的倍数是无损操作。 这是rotate90和rotate270函数的伪代码:

float2 rotate90( float2 vec )
{
    return float2( vec.y, -vec.x );
}
float2 rotate270( float2 vec )
{
    return float2( -vec.y, vec.x );
}

答案 3 :(得分:0)

定义:如果A和B是点,则向量(A,B)是从A点到B点的向量。

让我们说O点是我们坐标系的原点。

A点的坐标半径 - 矢量(O,A)的相同

设点M是平分线的中间点,所以你需要:

- 标准化向量(B,A)

- 标准化矢量(B,C)

-vector(B,M)=向量(B,A)+向量(B,C)//从B到中间点的向量

- (可选)您可以将矢量(B,M)与标量相乘以获得更长的矢量/增加B和M之间的距离

-vector(O,M)=向量(O,B)+向量(B,M)//半径向量从O到M

现在中间点M与radius-vector(O,M)具有相同的坐标。

答案 4 :(得分:0)

执行此操作的足够简单的方法有两种格式(但其内容在其他方面相同):

伪代码

// Move A and C to the origin for easier rotation calculations
Aprime=A-B;
Cprime=C-B;
// The counter-clockwise angle between the positive X axis to A'
angle_a = arctan(Aprime.y, Aprimet.x);
// ditto for C'
angle_c = arctan(Cprime.y, Cprime.x);
// The counter-clockwise angle from A' to C'
angle_ac = angle_c - angle_a;
// The counter-clockwise angle from the positive X axis to M'
angle_m = angle_ac/2 + angle_a;
// Construct M' which, like A' and C', is relative to the origin.
Mprime=(cos(angle_m), sin(angle_m));
// Construct M which is relative to B rather than relative to the origin.
M=Mprime+B

英语

  1. 将向量移动到原点
    • A'=A-B
    • B'=B
    • C'=C-B
  2. 从X的正轴到A'的角度为angle_a = arctan(A_y, A_x)
  3. 从X的正轴到C'的角度为angle_c = arctan(C_y, C_x)
  4. A'C'的逆时针角度设为angle_ac = angle_c - angle_a
  5. 从X的正轴到M'的角度为angle_m = angle_ac/2 + angle_a
  6. 从这个角度将M'构造为M' = (cos(angle_m), sin(angle_m))
  7. M构造为M = M' + B

向量BM将角度ABC二等分。

由于存在任意划分,因此此方法没有困难。这是一个图形计算器,以鼓励直观的解决方案:https://www.desmos.com/calculator/xwbno717da