我在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 点远离播放器。
我最近为这些东西发布了一个向量/矩阵 gem,虽然它是用 C 语言编写的,但我会尝试将其翻译成纯 Ruby。
实际上,有几种不同的方法可以计算 3D 空间中两个向量之间的角度。使用典型的acos
函数很常见,但当斜率接近 +/- 1.0 时,精度问题很大,因此最好使用角度平分线进行计算。即使是交叉乘积的光精度误差也会导致使用acos
的角度误差更大,所以经过一番研究,我发现这种方法总是一致的。
给定 2 个定义为v1
和v2
的向量:
# 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)
我没有运行/测试这段代码,我不确定你使用的确切矢量实现是什么,我只是假设一个对象具有x
、y
和z
值作为说明点。可能会有一些小的改进,例如乘以0.5
而不是除以2.0
,因为除法较慢,但基本前提应该希望有所帮助。
我从一个 C 项目转换了这段代码,所以如果你对其他向量函数感兴趣,请看这里(无耻的自我广告(。
[伪代码]
两个向量的点积与它们之间角度的余弦有关。
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 ]