Basic Thumbsticks

SourceEdit
-- Left thumbstick: move and strafe
-- Right thumbstick: rotate the view horizontally

-- Right grip: switch to snap locomotion mode
-- Left grip: enable flying mode
-- Left trigger: switch orientation to left hand controller (can look around while moving)

local motion = {
  pose = lovr.math.newMat4(), -- Transformation in VR initialized to origin (0,0,0) looking down -Z
  thumbstickDeadzone = 0.4,   -- Smaller thumbstick displacements are ignored (too much noise)
  directionFrom = 'head',     -- Movement can be relative to orientation of head or left controller
  flying = false,
  -- Snap motion parameters
  snapTurnAngle = 2 * math.pi / 12,
  dashDistance = 1.5,
  thumbstickCooldownTime = 0.3,
  thumbstickCooldown = 0,
  -- Smooth motion parameters
  turningSpeed = 2 * math.pi * 1 / 6,
  walkingSpeed = 4,
}

lovr.graphics.setBackgroundColor(0.1, 0.1, 0.1)

function motion.smooth(dt)
  if lovr.headset.isTracked('right') then
    local x, y = lovr.headset.getAxis('right', 'thumbstick')
    -- Smooth horizontal turning
    if math.abs(x) > motion.thumbstickDeadzone then
      local hx, _, hz = lovr.headset.getPosition()
      motion.pose:translate(hx, 0, hz)
      motion.pose:rotate(-x * motion.turningSpeed * dt, 0, 1, 0)
      motion.pose:translate(-hx, 0, -hz)
    end
  end
  if lovr.headset.isTracked('left') then
    local x, y = lovr.headset.getAxis('left', 'thumbstick')
    local direction = vector(lovr.headset.getDirection(motion.directionFrom))
    if not motion.flying then
      direction = vector(direction.x, 0, direction.z)
    end
    -- Smooth strafe movement
    if math.abs(x) > motion.thumbstickDeadzone then
      local strafeVector = quaternion(-math.pi / 2, 0,1,0) * direction
      motion.pose:translate(strafeVector * x * motion.walkingSpeed * dt)
    end
    -- Smooth Forward/backward movement
    if math.abs(y) > motion.thumbstickDeadzone then
      motion.pose:translate(direction * y * motion.walkingSpeed * dt)
    end
  end
end

function motion.snap(dt)
  -- Snap horizontal turning
  if lovr.headset.isTracked('right') then
    local x, y = lovr.headset.getAxis('right', 'thumbstick')
    if math.abs(x) > motion.thumbstickDeadzone and motion.thumbstickCooldown < 0 then
      local angle = -x / math.abs(x) * motion.snapTurnAngle
      local hx, _, hz = lovr.headset.getPosition()
      motion.pose:translate(hx, 0, hz)
      motion.pose:rotate(angle, 0, 1, 0)
      motion.pose:translate(-hx, 0, -hz)
      motion.thumbstickCooldown = motion.thumbstickCooldownTime
    end
  end
  -- Dashing forward/backward
  if lovr.headset.isTracked('left') then
    local x, y = lovr.headset.getAxis('left', 'thumbstick')
    if math.abs(y) > motion.thumbstickDeadzone and motion.thumbstickCooldown < 0 then
      local moveVector = vector(lovr.headset.getDirection('head'))
      if not motion.flying then
        moveVector = vector(moveVector.x, 0, moveVector.z)
      end
      motion.pose:translate(moveVector * (y / math.abs(y) * motion.dashDistance))
      motion.thumbstickCooldown = motion.thumbstickCooldownTime
    end
  end
  motion.thumbstickCooldown = motion.thumbstickCooldown - dt
end

function lovr.update(dt)
  motion.directionFrom = lovr.headset.isDown('left', 'trigger') and 'left' or 'head'
  if lovr.headset.isDown('left', 'grip') then
    motion.flying = true
  elseif lovr.headset.wasReleased('left', 'grip') then
    motion.flying = false
    local _, height = motion.pose:getPosition()
    motion.pose:translate(0, -height, 0)
  end
  if lovr.headset.isDown('right', 'grip') then
    motion.snap(dt)
  else
    motion.smooth(dt)
  end
end

function lovr.draw(pass)
  pass:transform(mat4(motion.pose):invert())
  -- Render hands
  pass:setColor(1,1,1)
  local radius = 0.05
  for _, hand in ipairs(lovr.headset.getHands()) do
    -- Whenever pose of hand or head is used, need to account for VR movement
    local position = motion.pose * vector(lovr.headset.getPosition(hand))
    pass:sphere(position, radius)
  end
  -- Some scenery
  lovr.math.setRandomSeed(0)
  local goldenRatio = (math.sqrt(5) + 1) / 2
  local goldenAngle = (2 - goldenRatio) * (2 * math.pi)
  local k = 1.8
  for i = 1, 500 do
    local r = math.sqrt(i) * k
    local x = math.cos(goldenAngle * i) * r
    local y = math.sin(goldenAngle * i) * r
    if lovr.math.random() < 0.05 then
      pass:setColor(0.5, 0, 0)
    else
      local shade = 0.1 + 0.3 * lovr.math.random()
      pass:setColor(shade, shade, shade)
    end
    pass:cylinder(x, 0, y,  1,0.05, math.pi / 2, 1,0,0)
  end
end