将3D向量在2D平面上旋转

我有两个 Vec3s,摄像机前向和炮塔前向。这两个向量都在不同的平面上,其中相机前向基于自由视角相机,而炮塔前向则由它所坐的坦克,坦克所在的地形等决定。炮塔向上和相机向上很少会匹配。

我的问题如下:我想让炮塔能够使用固定速度(每秒44度)旋转,以便它始终与相机指向的方向相交。如果坦克处于一种奇怪的角度,它无法与相机相交,它应该找到最接近的位置并停留在那里,而不是无限制地抖动。

我无法解决这个问题。我尝试了一些我在网上找到的方法,它们总是产生奇怪的结果。

    local forward    = player.direction:rotate(player.turret, player.up)
    local side       = forward:cross(player.up)
    local projection = self.camera.direction:dot(forward) * forward + self.camera.direction:dot(side) * side
    local angle      = math.atan2(forward.y, forward.x) - math.atan2(projection.y, projection.x)

    if angle ~= 0 then
        local dt = love.timer.getDelta()

        if angle <= turret_speed * dt then
            player.turret_velocity = turret_speed
        elseif angle >= -turret_speed * dt then
            player.turret_velocity = -turret_speed
        else
            player.turret_velocity = 0
            player.turret          = player.turret + angle
        end
    end

图1

点赞
用户2521214
用户2521214

我会以不同的方式完成它:

  1. 在 GCS(全局坐标系统)中获取相机方向向量c

    • 我将 Z 轴作为视角,因此只需从变换矩阵中提取 z 轴
    • 获取更多信息,请查看此处理解变换矩阵
  2. 在 GCS 中获取炮塔方向向量t

    • 和步骤 1 相同
  3. 计算在两个方向中旋转的炮塔方向向量

    • t0=rotation(-44.0deg/s)*t
    • t1=rotation(+44.0deg/s)*t
  4. 现在计算点积

    • a =dot(c,t)
    • a0=dot(c,t0)
    • a1=dot(c,t1)
  5. 确定炮塔旋转

    • 如果 max(a0,a,a1)==a0 rotate(-44.0deg / s) `
    • 如果 max(a0,a,a1)==a1 rotate(+44.0deg / s) `

【注】

  • 这应该会收敛到所需的方向
  • 角度步长应重新调整为与用于更新此的时间间隔匹配
  • 对于子弹 1,2,您可以使用任何常见坐标系,而不仅仅是 GCS
  • 在这种情况下,点积是 cos(向量之间的角度),因为c,t都是单位向量(如果从标准变换矩阵中获取)
  • 所以如果cos(angle)==1,则方向相同
  • 但是您的相机可以沿不同的轴旋转,因此只需找到cos(angle)的最大值
2015-02-10 08:44:52
用户1190664
用户1190664

经过更多的研究和测试,我得到了以下的解决方案。它运作得非常好!

function Gameplay:moved_axisright(joystick, x, y)
    if not self.manager.id then return end

    local turret_speed = math.rad(44)
    local stick        = cpml.vec2(-x, -y)
    local player       = self.players[self.manager.id]

    -- 鼠标和轴控制相机视角
    self.camera:rotateXY(stick.x * 18, stick.y * 9)

    -- 获取相机方向和炮塔方向之间的角度
    local fwd   = cpml.vec2(0, 1):rotate(player.orientation.z + player.turret)
    local cam   = cpml.vec2(1, 0):rotate(math.atan2(self.camera.direction.y, self.camera.direction.x))
    local angle = fwd:angle_to(cam)

    -- 如果炮塔方向不对,进行调整
    if math.abs(angle) > 0 then
        local function new_angle(direction)
            local dt       = love.timer.getDelta()
            local velocity = direction * turret_speed * dt
            return cpml.vec2(0, 1):rotate(player.orientation.z + player.turret + velocity):angle_to(cam)
        end

        -- 将炮塔旋转到正确的方向
        if new_angle(1) < 0 then
            player.turret_velocity = turret_speed
        elseif new_angle(-1) > 0 then
            player.turret_velocity = -turret_speed
        else
            -- 如果旋转一个完整帧会超出范围,将炮塔设置为相机位置
            -- atan2从左边开始,我们还需要添加半个旋转。减去玩家方向以将其转换为本地空间。
            player.turret          = math.atan2(self.camera.direction.y, self.camera.direction.x) + (math.pi * 1.5) - player.orientation.z
            player.turret_velocity = 0
        end
    end

    local direction         = cpml.mat4():rotate(player.turret, { 0, 0, 1 }) * cpml.mat4():rotate(player.orientation.z, { 0, 0, 1 })
    player.turret_direction = cpml.vec3(direction * { 0, 1, 0, 1 })
end
2015-02-10 09:20:35