【问题标题】:Calculate target angle based on two 3D Vectors?根据两个 3D 矢量计算目标角度?
【发布时间】:2019-01-23 21:01:51
【问题描述】:

我在 Ruby 中编写了这个函数来查找两个 2D (x,y) 向量之间的目标角度,但现在我想了解如何以类似的方式在 3D 中执行此操作:

def target_angle(point1, point2)
    x1 = point1[0]
    y1 = point1[1]
    x2 = point2[0]
    y2 = point2[1]
    delta_x = x2 - x1
    delta_y = y2 - y1
    return Math.atan2(delta_y, delta_x)
  end

给定一个对象(在这种情况下就像一个子弹),我可以在给定玩家 (x,y) 和鼠标 (x,y) 之间的 target_angle 的情况下射击该对象,例如在子弹更新函数中:

  def update
    wall_collision
    # the angle here is the target angle where point 1 is the player and
    # point 2 is the mouse
    @x += Math.cos(angle)*speed
    @y += Math.sin(angle)*speed
  end

是否有类似的方法来计算 3D 中的目标角度并以与我的更新函数类似的方式使用该角度(在 3D 中射击子弹)?如何使这适用于两个 3D 向量 (x, y, z),其中玩家位置 (x,y,z) 和其他一些远离玩家的任意 3d 点。

【问题讨论】:

标签: math vector 3d angle


【解决方案1】:

我最近发布了一个向量/矩阵 gem,虽然它是用 C 编写的,但我会尝试将它翻译成纯 Ruby。

实际上有几种不同的方法可以计算 3D 空间中两个向量之间的角度。使用典型的acos 函数很常见,但当斜率接近 +/- 1.0 时会出现很大的精度问题,因此最好使用角平分线进行计算。使用acos,即使是叉积的轻微精度误差也会导致更大的角度误差,所以经过一些研究,我发现这种方法总是一致的。

给定 2 个向量,定义为 v1v2

# Normalize vectors
inv = 1.0 / Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z)
n1 = Vector.new(v1.x * inv, v1.y * inv, v1.z * inv)

inv = 1.0 / Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z)
n2 = Vector.new(v2.x * inv, v2.y * inv, v2.z * inv)

ratio = n1.x * n2.x + n1.y * n2.y + n1.z * n2.z


if ratio < 0.0
  x = -n1.x - n2.x
  y = -n1.y - n2.y
  z = -n1.z - n2.z
  length = Math.sqrt(x * x + y * y + z * z)
  theta = Math::PI - 2.0 * Math.asin(length / 2.0)
else
  x = n1.x - n2.x
  y = n1.y - n2.y
  z = n1.z - n2.z
  length = Math.sqrt(x * x + y * y + z * z)
  theta = 2.0 * asin(length / 2.0)
end

# Convert from radians to degrees
angle = theta * (180.0 / Math::PI)

我没有运行/测试此代码,并且我不确定您使用的是什么确切的向量实现,我只是假设一个具有xyz 值的对象来说明这一点.可能会有一些小的改进,例如乘以0.5 而不是除以2.0,因为除法速度较慢,但​​基本前提应该会有所帮助。

这段代码我是从一个 C 项目转换过来的,所以如果你对其他向量函数感兴趣,可以看here(无耻的自我宣传)。

【讨论】:

  • 太棒了,你发布了一个 gem!一旦你有了角度,我也有一个更新功能,有没有办法以与 x 和 y 相同的方式将子弹的 z 位置增加到该点?还是有其他方法可以做到这一点?
  • 由于子弹是直线运动的,你可以简单地计算一次,然后你就可以计算出它的轨迹,因为它不会(不应该?)改变。任何更改,例如玩家移动等,都将由您的世界矩阵处理。
  • 是的,仅在生成子弹时计算一次角度。我的意思是你如何使用子弹x,y,z位置使子弹向点移动?也就是说,我的意思是在我上面的更新函数中是否有类似的方法,但也有 z ?
  • 类似m = Math.sqrt(x1 * x2 + y1 * y2 + z1 * z2) 的东西计算距离,您可以使用它(在归一化向量上)改变位置/速度。这些需要使用您的世界/模型/投影矩阵计算,因为它们还取决于玩家速度/方向、相机等。
  • 如果您的“gem”包含 if 语句,那么您做错了。当点积(你称之为比率)为负时,角度超过 90°,并且 atan2() 函数非常擅长处理这种情况。此外,除法是多余的,因为它抵消了。它只是为了减慢您的代码速度。
【解决方案2】:

[伪代码]

两个向量的点积与它们之间夹角的余弦有关。

COS(angle) = dot(a,b)/( |a|*|b| ) 

两个向量的叉积与它们之间夹角的正弦有关。

SIN(angle) = | cross(a,b) |/( |a|*|b| )

所以正切就是正弦与余弦的比值(分母相互抵消)。

angle = atan2( magnitude(cross(a,b)), dot(a.b) )  % returns angle in radians

注意atan2(Δy,Δx) 的约定。

最后,定义如下函数

magnitude(c) = sqrt(c.x^2+c.y^2+c.z^2)
dot(a,b) = a.x*b.x + a.y*b.y + a.z*b.z
cross(a,b) = [ a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a>x*b.y - a.y*b.x ]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多