Pointer UI

SourceEdit
local function raycast(rayPos, rayDir, planePos, planeDir)
  local dot = rayDir:dot(planeDir)
  if math.abs(dot) < .001 then
    return nil
  else
    local distance = (planePos - rayPos):dot(planeDir) / dot
    if distance > 0 then
      return rayPos + rayDir * distance
    else
      return nil
    end
  end
end

local button = {
  text = 'Please click me',
  textSize = .1,
  count = 0,
  position = lovr.math.newVec3(0, 1, -3),
  width = 1.0,
  height = .4,
  hover = false,
  active = false
}

local tips = {}

function lovr.update()
  button.hover, button.active = false, false

  for i, hand in ipairs(lovr.headset.getHands()) do
    tips[hand] = tips[hand] or lovr.math.newVec3()

    -- Ray info:
    local rayPosition = vec3(lovr.headset.getPosition(hand))
    local rayDirection = vec3(quat(lovr.headset.getOrientation(hand)):direction())

    -- Call the raycast helper function to get the intersection point of the ray and the button plane
    local hit = raycast(rayPosition, rayDirection, button.position, vec3(0, 0, 1))

    local inside = false
    if hit then
      local bx, by, bw, bh = button.position.x, button.position.y, button.width / 2, button.height / 2
      inside = (hit.x > bx - bw) and (hit.x < bx + bw) and (hit.y > by - bh) and (hit.y < by + bh)
    end

    -- If the ray intersects the plane, do a bounds test to make sure the x/y position of the hit
    -- is inside the button, then mark the button as hover/active based on the trigger state.
    if inside then
      if lovr.headset.isDown(hand, 'trigger') then
        button.active = true
      else
        button.hover = true
      end

      if lovr.headset.wasReleased(hand, 'trigger') then
        button.count = button.count + 1
        print('BOOP')
      end
    end

    -- Set the end position of the pointer.  If the raycast produced a hit position then use that,
    -- otherwise extend the pointer's ray outwards by 50 meters and use it as the tip.
    tips[hand]:set(inside and hit or (rayPosition + rayDirection * 50))
  end
end

function lovr.draw(pass)
  -- Button background
  if button.active then
    pass:setColor(.4, .4, .4)
  elseif button.hover then
    pass:setColor(.2, .2, .2)
  else
    pass:setColor(.1, .1, .1)
  end
  pass:plane(button.position, button.width, button.height)

  -- Button text (add a small amount to the z to put the text slightly in front of button)
  pass:setColor(1, 1, 1)
  pass:text(button.text, button.position + vec3(0, 0, .001), button.textSize)
  pass:text('Count: ' .. button.count, button.position + vec3(0, .5, 0), .1)

  -- Pointers
  for hand, tip in pairs(tips) do
    local position = vec3(lovr.headset.getPosition(hand))

    pass:setColor(1, 1, 1)
    pass:sphere(position, .01)

    if button.active then
      pass:setColor(0, 1, 0)
    else
      pass:setColor(1, 0, 0)
    end

    pass:line(position, tip)
  end
end