Window HUD

-- "Second screen experience" demo
-- Click grid on desktop screen to build a scene simultaneously visible in VR space
-- NOTE: does not currently work properly with the desktop driver, since lovr.mirror clears the window
-- Sample contributed by andi mcc

local shader = require 'shader'

-- In order for lovr.mouse to work, and therefore for this example to work,
-- we must be using LuaJIT and we must be using GLFW (ie: we can't be on Android)
if type(jit) == 'table' and lovr.system.getOS() ~= "android" then
  lovr.mouse = require 'mouse'

local mirror = lovr.mirror              -- Backup lovr.mirror before it is overwritten
local font =  -- Font appropriate for screen-space usage

-- Simple 2D triangle mesh
local triangle =
  {{0,-1,0}, {0.75,1,0}, {-0.75,1,0}}, {'vec3:VertexPosition'})

-- Constants
local pixwidth = lovr.system.getWindowWidth()   -- Window pixel width and height
local pixheight = lovr.system.getWindowHeight()
local aspect = pixwidth/pixheight           -- Window aspect ratio
local height = 2                            -- Window width and height in screen coordinates
local width = aspect*2                      -- ( We will pick the coordinate system [[-1,1],[-aspect,aspect]] )
local topmargin = 0.2                       -- Space between top of screen and top of grid
local cells = 7                             -- Number of cells in grid (per side)
local towerscalexz = 2                      -- How wide is one block in 3D space?
local towerscaley = 3                       -- How tall (maximum) is one block in 3D space?

-- Derived constants
local gridheight = (height-topmargin*2)             -- Height of grid
local gridspan = gridheight/2                       -- Half height of grid
local cellheight = gridheight/cells                 -- Height of one grid cell
local cellspan = cellheight/2                       -- Half height of one grid cell
local bannedcell = math.ceil(cells/2)               -- Do not allow clicks at this x,y coordinate
local fontscale = height/pixheight  -- Scale argument to screen-space print() functions

-- Screen-space coordinate system
local matrix = lovr.math.newMat4():orthographic(-aspect, aspect, -1, 1, -64, 64)

-- State: We will store the blocks to draw as a 2D array of heights (nil for no block)
local grid = {}
for x=1,cells do grid[x] = {} end

function lovr.load()
  lovr.handlers['mousepressed'] = function(x,y)
    local inx = x * width / pixwidth - width/2    -- Convert pixel x,y to our coordinate system
    local iny = y * height / pixheight - height/2
    local gridorigin = -gridspan - cellheight     -- Upper left of grid ()
    local gx = (inx - gridorigin) / cellheight    -- Convert coordinate system to grid cells
    local gy = (iny - gridorigin) / cellheight
    local fx = math.floor(gx)
    local fy = math.floor(gy)
    if fx >= 1 and fy >= 1 and fx <= cells and fy <= cells   -- If the click was within the grid
       and not (fx == bannedcell and fy == bannedcell) then  -- and was not the banned center cell
      if grid[fx][fy] then
        grid[fx][fy] = nil                -- toggle off
        grid[fx][fy] = lovr.math.random() -- toggle on (random height)

local function drawGrid(pass)
  -- Draw cell backgrounds (where present)
  for _x=1,cells do for _y=1,cells do
    local gray = grid[_x][_y]
    if gray then
      local x = -gridspan + _x * cellheight - cellspan -- Center of cell
      local y = -gridspan + _y * cellheight - cellspan

      pass:plane(x, y, 0, cellheight, cellheight)
  end end

  -- Draw grid lines
  for c=0,cells do
    local x = -gridspan + c * cellheight
    local y = -gridspan + c * cellheight
    pass:line(-gridspan, y, 0, gridspan, y, 0)
    pass:line(x, -gridspan, 0, x, gridspan, 0)

  -- Draw a red triangle indicating the position and orientation of the headset player
  local x, y, z, angle, ax, ay, az = lovr.headset.getPose()
  -- Flatten the 3-space current rotation of the headset into just its xz axis
  -- Equation from:
  local s = math.sin(angle)
  local c = math.cos(angle)
  local t = 1-c;
  local xzangle = math.atan2(ay*s - ax*az*t , 1 - (ay*ay + az*az) * t);
  pass:translate(x / towerscalexz, z / towerscalexz, 0)
  pass:rotate(-xzangle, 0, 0, 1)

-- Draw HUD overlay
local function mirror(pass) 
  pass:setViewPose(1, mat4())
  pass:setProjection(1, matrix) -- Switch to screen space coordinates
  pass:setDepthTest() -- Depth buffer will be full of garbage after drawing scene

  -- Draw instructions
  pass:text("Instructions: Click the grid to create or remove blocks.", 0, (gridheight+cellheight)/2, 0, fontscale)

-- Draw one block
local function floorbox(pass,_x,_y,gray)
  local x = -gridspan + _x * cellheight - cellspan
  local z = -gridspan + _y * cellheight - cellspan
  local height = gray * towerscaley
  pass:box(x*towerscalexz, height/2, z*towerscalexz, cellheight*towerscalexz, height, cellheight*towerscalexz)

-- Draw 3D scene
local function draw(pass)
  for x=1,cells do for y=1,cells do
    local gray = grid[x][y]
    if gray then floorbox(pass,x,y,gray) end
  end end

  if not lovr.mouse then -- If you can't click, you can't create any blocks
    pass:text('This example only works on a desktop computer.', 0, 1.7, -3, .2)

-- Handle LOVR

-- LOVR expects that if you have both a lovr.mirror and a lovr.draw, it's because you want to draw
-- different things in the headset and the window. So in 0.16, it "clears" before calling lovr.mirror
-- (more accurately, it draws the headset and the window to different back buffers entirely).
-- However, we want OUR window to show the headset contents, and ON TOP of that, we want to draw.
-- So we have to trick LOVR a little bit...

-- Our strategy will be different on Desktop (the simulator) vs other drivers.
local isDesktop = lovr.headset.getDriver() == "desktop"

function lovr.draw(pass)
  draw(pass)       -- Headset contents

  -- On desktop, we define ONLY lovr.draw, no lovr.mirror, and draw the mirror contents inside lovr.draw().
  -- This is because on desktop, a lovr.mirror being present will prevent lovr.draw() from being called at all.
  if isDesktop then
    mirror(pass) -- Mirror contents

-- When headsets are plugged in, we want both a lovr.draw and a lovr.mirror.
if not isDesktop then
  local originalMirror = lovr.mirror -- By default, LOVR will have given us a mirror that displays the headset
  function lovr.mirror(pass)
    originalMirror(pass) -- Headset texture (note: this will fill the depth buffer with z=0)
    mirror(pass)         -- Mirror contents