Rotate 3D vectors on 2D plane

2019-09-07 00:59发布

问题:

I have two Vec3s, Camera Forward and Turret Forward. Both of these vectors are on different planes where Camera Forward is based on a free-look camera and Turret Forward is determined by the tank it sits on, the terrain the tank is on, etc. Turret Up and Camera Up are rarely ever going to match.

My issue is as follows: I want the turret to be able to rotate using a fixed velocity (44 degrees per second) so that it always converges with the direction that the camera is pointed. If the tank is at a weird angle where it simply cannot converge with the camera, it should find the closest place and sit there instead of jitter around indefinitely.

I cannot for the life of me seem to solve this problem. I've tried several methods I found online that always produce weird results.

    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:

I would do it differently

  1. obtain camera direction vector c in GCS (global coordinate system)

    • I use Z axis as viewing axis so just extract z axis from transform matrix
    • for more info look here understanding transform matrices
  2. obtain turret direction vector t in GCS

    • the same as bullet 1.
  3. compute rotated turret direction vectors in booth directions

    • t0=rotation(-44.0deg/s)*t
    • t1=rotation(+44.0deg/s)*t
  4. now compute the dot products

    • a =dot(c,t)
    • a0=dot(c,t0)
    • a1=dot(c,t1)
  5. determine turret rotation

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

[Notes]

  • this should converge to desired direction
  • the angle step should be resized to match the time interval used for update this
  • you can use any common coordinate system for bullets 1,2 not just GCS
  • in this case the dot product is cos(angle between vectors) because both c,t are unit vectors (if taken from standard transform matrix)
  • so if cos(angle)==1 then the directions are the same
  • but your camera can be rotated in different axis so just find the maximum of cos(angle)


回答2:

After some more research and testing, I ended up with the following solution. It works swimmingly!

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]

    -- Mouse and axis control camera view
    self.camera:rotateXY(stick.x * 18, stick.y * 9)

    -- Get angle between Camera Forward and Turret Forward
    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 the turret is not true, adjust it
    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

        -- Rotate turret into the correct direction
        if new_angle(1) < 0 then
            player.turret_velocity = turret_speed
        elseif new_angle(-1) > 0 then
            player.turret_velocity = -turret_speed
        else
            -- If rotating the turret a full frame will overshoot, set turret to camera position
            -- atan2 starts from the left and we need to also add half a rotation. subtract player orientation to convert to local space.
            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