gaze-based controls
This commit is contained in:
parent
0930bd7544
commit
f11b420197
9 changed files with 222 additions and 118 deletions
|
|
@ -30,4 +30,14 @@ ecs.clear = function()
|
|||
api.world.commit()
|
||||
end
|
||||
|
||||
ecs.extension = {
|
||||
name = "ecs",
|
||||
enabled = true,
|
||||
index = 300,
|
||||
init = function(api) ecs.init(api) end,
|
||||
draw = function(pass)
|
||||
ecs.update( api.world, pass, ecs.filterDraw )
|
||||
end
|
||||
}
|
||||
|
||||
return ecs
|
||||
|
|
|
|||
|
|
@ -5,49 +5,82 @@ local gctl
|
|||
|
||||
gctl = {
|
||||
|
||||
name = "gctl",
|
||||
enabled = true,
|
||||
name = "gazecontrol",
|
||||
enabled = (lovr ~= nil),
|
||||
index = api.ext.max * 0.9, -- important: render last! (renderOrder)
|
||||
texture = {},
|
||||
material = nil,
|
||||
dtmax = 1,
|
||||
dt = 0,
|
||||
visor = {
|
||||
rotate = { x = 0, y = 0, z = 0},
|
||||
scaleTo = 0.1,
|
||||
scale = 0.1,
|
||||
scaleSpeed = 0.005,
|
||||
walkingSpeed = 2,
|
||||
texture = false,
|
||||
textures = {}
|
||||
},
|
||||
|
||||
init = function()
|
||||
--lovr.mouse.setRelativeMode(true)
|
||||
gctl.initVisor()
|
||||
gctl.extendECS()
|
||||
api.player.matrix = lovr.math.newMat4()
|
||||
end,
|
||||
|
||||
|
||||
load = function()
|
||||
gctl.texture.idle = lovr.graphics.newTexture("ext/gazecontrol/reticle_idle.png")
|
||||
end,
|
||||
|
||||
initVisor = function()
|
||||
-- add visor object
|
||||
gctl.visor = {
|
||||
rotate = { x = 0, y = 0, z = 0},
|
||||
scale = 0.1,
|
||||
texture = gctl.texture.idle
|
||||
}
|
||||
local visor = gctl.visor
|
||||
visor.textures.idle = lovr.graphics.newTexture("ext/gazecontrol/reticle_idle.png")
|
||||
visor.textures.hover = lovr.graphics.newTexture("ext/gazecontrol/reticle_hover.png")
|
||||
visor.texture = visor.textures.idle
|
||||
end,
|
||||
|
||||
update = function(dt, input)
|
||||
local visor = gctl.visor
|
||||
if input.pointer.hover then
|
||||
visor.texture = visor.textures.hover
|
||||
if visor.scale == visor.scaleTo then
|
||||
visor.scale = 0.05
|
||||
end
|
||||
else
|
||||
visor.texture = visor.textures.idle
|
||||
visor.scale = visor.scaleTo
|
||||
end
|
||||
|
||||
if input.pointer.wheel then -- move forward/backward via scrollwheel
|
||||
print( input.pointer.wheel.y)
|
||||
local direction = vector(lovr.headset.getDirection('head'))
|
||||
if not api.player.flying then direction = vector(direction.x, 0, direction.z) end
|
||||
-- move forward/backward via scrollwheel
|
||||
api.player.matrix:translate(direction * (input.pointer.wheel.y * visor.walkingSpeed) )
|
||||
end
|
||||
end,
|
||||
|
||||
draw = function(pass,dt)
|
||||
-- *TODO* render this last (as it needs to be rendered after skybox & floor)
|
||||
local posx, posy, posz = lovr.headset.getPosition('head')
|
||||
local pos = vector( lovr.headset.getPosition('head') ) + vector( api.player.matrix:getPosition() )
|
||||
local dirx, diry, dirz = lovr.headset.getDirection('head')
|
||||
local distance = 1
|
||||
local x = posx + distance * dirx
|
||||
local y = posy + distance * diry
|
||||
local z = posz + distance * dirz
|
||||
local x = pos.x + distance * dirx
|
||||
local y = pos.y + distance * diry
|
||||
local z = pos.z + distance * dirz
|
||||
local angle, ax, ay, az = lovr.headset.getOrientation(device)
|
||||
local rot = gctl.visor.rotate
|
||||
local scale = gctl.visor.scale
|
||||
pass:setMaterial(gctl.texture.idle)
|
||||
pass:setShader() --'unlit')
|
||||
pass:setBlendMode('alpha')
|
||||
pass:setColor(1, 1, 1)
|
||||
local visor = gctl.visor
|
||||
local rot = visor.rotate
|
||||
local scale = visor.scale
|
||||
if scale < visor.scaleTo then
|
||||
scale = scale + visor.scaleSpeed
|
||||
visor.scale = scale
|
||||
end
|
||||
pass:setMaterial(visor.texture)
|
||||
pass:setColor(1, 1, 1, 1)
|
||||
pass:setCullMode('back')
|
||||
--pass:setFaceCull()
|
||||
--pass:setDepthWrite(true)
|
||||
pass:setBlendMode("alpha", "alphamultiply")
|
||||
--pass:setColorWrite(true)
|
||||
pass:plane(x, y, z, scale, scale, angle, ax, ay, az)
|
||||
end,
|
||||
|
||||
|
|
|
|||
BIN
xurfer/ext/gazecontrol/reticle_hover.png
Normal file
BIN
xurfer/ext/gazecontrol/reticle_hover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 4.8 KiB |
|
|
@ -9,11 +9,9 @@ return {
|
|||
--api.ext.skeleton.extendECS()
|
||||
end,
|
||||
|
||||
update = function(dt) end,
|
||||
|
||||
load = function() end,
|
||||
|
||||
draw = function(pass,dt) end,
|
||||
update = function(dt,input) end,
|
||||
load = function() end,
|
||||
draw = function(pass,dt) end,
|
||||
|
||||
-- ECS API docs: https://bakpakin.github.io/tiny-ecs/doc
|
||||
extendECS = function()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ local shader
|
|||
return {
|
||||
name = "startupscene",
|
||||
enabled = (lovr ~= nil),
|
||||
index = api.ext.max * 0.1,
|
||||
|
||||
init = function() end,
|
||||
|
||||
|
|
@ -51,13 +52,15 @@ return {
|
|||
|
||||
pass:setColor(1, 1, 1)
|
||||
pass:setMaterial(envTex)
|
||||
pass:setDepthWrite(false)
|
||||
pass:draw(envTex, 0, 0, 0, 512, math.pi * 0.5, 1, 0, 0)
|
||||
|
||||
-- floor
|
||||
--pass:setMaterial()
|
||||
pass:setMaterial()
|
||||
pass:setDepthWrite(true)
|
||||
pass:setShader()
|
||||
pass:setCullMode('front')
|
||||
pass:setBlendMode('alpha')
|
||||
--pass:setCullMode('front')
|
||||
--pass:setBlendMode('alpha')
|
||||
pass:setMaterial()
|
||||
pass:setColor(0.8,0.8,0.8,1)
|
||||
pass:plane(0, 0, 0, 500, 500, math.pi * 0.5, 1, 0, 0, "fill", 5, 5)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ api = {
|
|||
ecs = ecs,
|
||||
world = ecs.world(),
|
||||
protocol = {},
|
||||
player = { x = 0, y = 0, z = 0, ox = 0, oy = 1, oz = 0, oangle = 0, flying = true },
|
||||
ext = { -- all extensions are loaded here from disk at runtime
|
||||
ecs = ecs.extension,
|
||||
max = 10000, -- max plugins (used for sorting)
|
||||
exec = function(...) util.exec(api.ext,...) end -- util function to call func on each extension
|
||||
},
|
||||
i = 1
|
||||
|
|
@ -39,14 +42,11 @@ api = {
|
|||
|
||||
api = util.merge( api, runtime.api ) -- overlay api onto lovr/love-compatible runtime api
|
||||
|
||||
require( runtime.path .. "/main")
|
||||
|
||||
util.loaddir( "ext", api, api.ext )
|
||||
util.loaddir( "media", api, api.media )
|
||||
|
||||
ecs.init(api)
|
||||
api.ext.exec('init')
|
||||
require( runtime.path .. "/main")
|
||||
|
||||
api.ext.exec('init', api)
|
||||
|
||||
-- load urls passed on the cli
|
||||
foreach( arg, function(k,uri)
|
||||
if uri:find(".") and k ~= 0 and k ~= -1 then
|
||||
|
|
|
|||
|
|
@ -28,9 +28,30 @@ local ecs = api.ecs
|
|||
ecs.filterDraw = ecs.requireAll('drawthread')
|
||||
ecs.filterUpdate = ecs.requireAll('updatethread')
|
||||
|
||||
-- sort drawcalls of extensions
|
||||
api.ext.lovr = {
|
||||
drawcalls = {},
|
||||
enabled = true,
|
||||
name = "lovr",
|
||||
index = api.ext.max-1,
|
||||
init = function()
|
||||
api.ext.lovr.sortDrawCalls()
|
||||
end,
|
||||
|
||||
sortDrawCalls = function()
|
||||
local drawcalls = api.ext.lovr.drawcalls
|
||||
foreach( api.ext, function(k,v)
|
||||
if type(v) == 'table' and v.draw ~= nil then
|
||||
table.insert(drawcalls, v)
|
||||
end
|
||||
end)
|
||||
table.sort( drawcalls, function(a,b) return a.index < b.index end)
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
-- ecs systems
|
||||
local interactionsUpdater
|
||||
local interactions
|
||||
|
||||
--- @type Texture
|
||||
local envTex
|
||||
|
|
@ -40,8 +61,7 @@ local mainWindow
|
|||
|
||||
function lovr.load()
|
||||
if launch.mode == "desktop" then
|
||||
backend.mouse = require "lib.lovr-mouse"
|
||||
|
||||
--backend.mouse = require "lib.lovr-mouse"
|
||||
lovr.system.setKeyRepeat(true)
|
||||
end
|
||||
|
||||
|
|
@ -50,7 +70,10 @@ function lovr.load()
|
|||
api.ext.exec("load")
|
||||
|
||||
if iui.idiom == "vr" then
|
||||
lovr.headset.setPassthrough("opaque")
|
||||
local modes = lovr.headset.getPassthroughModes()
|
||||
local mode = "opaque"
|
||||
if modes.blend then mode = "blend" end
|
||||
lovr.headset.setPassthrough( mode )
|
||||
|
||||
mainWindow = backend.worldWindow.new()
|
||||
api.mainWindow = mainWindow
|
||||
|
|
@ -66,7 +89,7 @@ function lovr.update(dt)
|
|||
-- In desktop mode, we use IUI's standard window API to fill the screen.
|
||||
iui.beginWindow(lovr.system.getWindowDimensions())
|
||||
|
||||
api.ext.exec("update", dt)
|
||||
api.ext.exec("update", dt, interactions.event )
|
||||
|
||||
iui.endWindow()
|
||||
|
||||
|
|
@ -76,7 +99,7 @@ function lovr.update(dt)
|
|||
if mainWindow:beginFrame() then
|
||||
iui.beginWindow(mainWindow.w, mainWindow.h)
|
||||
|
||||
api.ext.exec("update", dt)
|
||||
api.ext.exec("update", dt, interactions.event )
|
||||
|
||||
iui.endWindow()
|
||||
|
||||
|
|
@ -88,6 +111,10 @@ function lovr.update(dt)
|
|||
end
|
||||
|
||||
function lovr.draw(pass)
|
||||
--api.player.matrix:setPosition( api.player.x, api.player.y, api.player.z )
|
||||
--api.player.matrix.setOrientation( api.player.x, api.player.y, api.player.z )
|
||||
pass:transform(mat4(api.player.matrix):invert())
|
||||
|
||||
if iui.idiom == "desktop" then
|
||||
backend.graphics.pass = pass
|
||||
|
||||
|
|
@ -97,27 +124,28 @@ function lovr.draw(pass)
|
|||
pass:setClear(0.2, 0.2, 0.2)
|
||||
end
|
||||
|
||||
api.ext.exec("draw",pass)
|
||||
ecs.update( api.world, pass, ecs.filterDraw )
|
||||
-- call draw() function of extensions (sorted by .index)
|
||||
foreach( api.ext.lovr.drawcalls, function(k,ext) ext.draw(pass) end)
|
||||
|
||||
-- Dot
|
||||
if interactionsUpdater.selectedBox then
|
||||
pass:setColor(0, 0, 1)
|
||||
pass:sphere( interactionsUpdater.hitpoint, .01)
|
||||
end
|
||||
|
||||
-- Laser pointers
|
||||
local hands = {"left","right"}
|
||||
foreach( hands, function(k,side)
|
||||
local hand = vec3(lovr.headset.getPosition('hand/' .. side .. '/point'))
|
||||
local direction = quat(lovr.headset.getOrientation('hand/' .. side .. '/point')):direction()
|
||||
pass:setColor(1, 1, 1)
|
||||
pass:line(hand, interactionsUpdater.selectedBox and interactionsUpdater.hitpoint or (hand + direction * 50))
|
||||
end)
|
||||
|
||||
if api.mainWindow then
|
||||
api.mainWindow:draw(pass)
|
||||
end
|
||||
-- -- Dot
|
||||
--if interactions.selectedBox then
|
||||
-- pass:setColor(0, 0, 1)
|
||||
-- pass:sphere( interactions.hitpoint, .01)
|
||||
--end
|
||||
--
|
||||
---- Laser pointers
|
||||
--local hands = {"left","right"}
|
||||
--foreach( hands, function(k,side)
|
||||
-- local hand = vec3(lovr.headset.getPosition('hand/' .. side .. '/point'))
|
||||
-- local direction = quat(lovr.headset.getOrientation('hand/' .. side .. '/point')):direction()
|
||||
-- pass:setColor(1, 1, 1)
|
||||
-- pass:line(hand, interactions.selectedBox and interactions.hitpoint or (hand + direction * 50))
|
||||
--end)
|
||||
|
||||
--if api.mainWindow then
|
||||
-- api.mainWindow:draw(pass)
|
||||
--end
|
||||
return false
|
||||
end
|
||||
|
||||
|
|
@ -127,59 +155,62 @@ function lovr.recenter()
|
|||
end
|
||||
end
|
||||
|
||||
if launch.mode == "desktop" then
|
||||
function lovr.mousemoved(x, y, dx, dy)
|
||||
backend.mousemoved(x, y, dx, dy)
|
||||
interactionsUpdater.mouse.moved = {x=x, y=y}
|
||||
end
|
||||
function lovr.mousemoved(x, y, dx, dy)
|
||||
backend.mousemoved(x, y, dx, dy)
|
||||
interactions.pointer.moved = {x=x, y=y}
|
||||
end
|
||||
|
||||
function lovr.mousepressed(x, y, button)
|
||||
backend.mousepressed(x, y, button)
|
||||
interactionsUpdater.mouse.pressed = {x=x, y=y, button=button}
|
||||
end
|
||||
function lovr.mousepressed(x, y, button)
|
||||
backend.mousepressed(x, y, button)
|
||||
interactions.pointer.pressed = {x=x, y=y, button=button}
|
||||
end
|
||||
|
||||
function lovr.mousereleased(x, y, button)
|
||||
backend.mousereleased(x, y, button)
|
||||
interactionsUpdater.mouse.released = {x=x, y=y, button=button}
|
||||
print("mouserelease")
|
||||
end
|
||||
function lovr.mousereleased(x, y, button)
|
||||
backend.mousereleased(x, y, button)
|
||||
interactions.pointer.released = {x=x, y=y, button=button}
|
||||
end
|
||||
|
||||
function lovr.wheelmoved(x, y)
|
||||
backend.wheelmoved(x, y)
|
||||
interactionsUpdater.mouse.wheel = {x=x, y=y}
|
||||
end
|
||||
function lovr.wheelmoved(x, y)
|
||||
backend.wheelmoved(x, y)
|
||||
interactions.pointer.wheel = {x=x, y=y}
|
||||
print_r(interactions.pointer.wheel)
|
||||
end
|
||||
|
||||
function lovr.keypressed(key, scancode, isRepeat)
|
||||
backend.keypressed(key, scancode, isRepeat)
|
||||
interactionsUpdater.key.pressed = {key=key,scancode=scancode,isRepeat=isRepeat}
|
||||
end
|
||||
function lovr.keypressed(key, scancode, isRepeat)
|
||||
backend.keypressed(key, scancode, isRepeat)
|
||||
interactions.key.pressed = {key=key,scancode=scancode,isRepeat=isRepeat}
|
||||
end
|
||||
|
||||
function lovr.keyreleased(key, scancode)
|
||||
backend.keyreleased(key, scancode)
|
||||
interactionsUpdater.key.released = {key=key,scancode=scancode}
|
||||
end
|
||||
function lovr.keyreleased(key, scancode)
|
||||
backend.keyreleased(key, scancode)
|
||||
interactions.key.released = {key=key,scancode=scancode}
|
||||
end
|
||||
|
||||
function lovr.textinput(text)
|
||||
backend.textinput(text)
|
||||
interactionsUpdater.input = text
|
||||
end
|
||||
function lovr.textinput(text)
|
||||
backend.textinput(text)
|
||||
interactions.input = text
|
||||
end
|
||||
|
||||
function initInteractions(ecs)
|
||||
|
||||
ecs.worldPhysics = lovr.physics.newWorld(0, 0, 0)
|
||||
interactionsUpdater = ecs.processingSystem()
|
||||
interactionsUpdater.updatethread = true
|
||||
interactionsUpdater.filter = function(obj) return true end -- always run
|
||||
interactions = ecs.processingSystem()
|
||||
interactions.updatethread = true
|
||||
interactions.filter = function(obj) return true end -- always run
|
||||
-- collider vars
|
||||
interactionsUpdater.mouse = { released = false, pressed = false, moved = false, wheel = false}
|
||||
interactionsUpdater.key = { released = false, pressed = false }
|
||||
interactionsUpdater.input = ""
|
||||
interactionsUpdater.hitpoint = lovr.math.newVec3()
|
||||
interactionsUpdater.selectedBox = nil
|
||||
interactionsUpdater.clear = function(o) foreach( o, function(k,v) o[k] = false end ) end
|
||||
interactions.pointer = { released = false, pressed = false, moved = false, hover = false, wheel = false}
|
||||
interactions.key = { released = false, pressed = false }
|
||||
interactions.input = ""
|
||||
interactions.hitpoint = vector()
|
||||
interactions.selectedBox = nil
|
||||
interactions.clearTable = function(o) foreach( o, function(k,v) o[k] = false end ) end
|
||||
interactions.clear = function()
|
||||
interactions.clearTable( interactions.pointer )
|
||||
interactions.clearTable( interactions.key )
|
||||
interactions.input = false
|
||||
end
|
||||
|
||||
function interactionsUpdater:process(obj, dt)
|
||||
function interactions:process(obj, dt)
|
||||
ecs.worldPhysics:update(dt)
|
||||
|
||||
local castsrc = 'hand/left/point'
|
||||
|
|
@ -189,30 +220,43 @@ function initInteractions(ecs)
|
|||
|
||||
local direction = quat(lovr.headset.getOrientation(castsrc)):direction()
|
||||
if direction ~= nil then
|
||||
local dx, dy, dz = direction:mul(50):unpack()
|
||||
local dx, dy, dz = vector( direction * 50 ):unpack()
|
||||
local collider, shape, x, y, z = ecs.worldPhysics:raycast(ox, oy, oz, ox + dx, oy + dy, oz + dz)
|
||||
|
||||
if collider then
|
||||
interactions.pointer.hover = true
|
||||
local node = collider:getUserData()
|
||||
interactions.selectedBox = collider
|
||||
interactions.hitpoint.x = x
|
||||
interactions.hitpoint.y = y
|
||||
interactions.hitpoint.z = z
|
||||
|
||||
for i, hand in ipairs(lovr.headset.getHands()) do
|
||||
if lovr.headset.isDown(hand, 'trigger') then
|
||||
print("buttondown")
|
||||
interactions.pointer.pressed = true
|
||||
end
|
||||
if lovr.headset.wasReleased(hand, 'trigger') or interactionsUpdater['mouse']['released'] then
|
||||
print("cast: mousedown!")
|
||||
local node = collider:getUserData()
|
||||
interactionsUpdater.selectedBox = collider
|
||||
interactionsUpdater.hitpoint:set(x, y, z)
|
||||
if node.onclick ~= nil then node.onclick(node,collider) end
|
||||
if lovr.headset.wasReleased(hand, 'trigger') then
|
||||
interactions.pointer.released = true
|
||||
end
|
||||
end
|
||||
if interactions.pointer.released and node.onclick ~= nil then
|
||||
node.onclick(node,collider)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- reset stuff
|
||||
interactionsUpdater.clear( interactionsUpdater.mouse )
|
||||
interactionsUpdater.clear( interactionsUpdater.key )
|
||||
interactionsUpdater.input = false
|
||||
-- backup & reset stuff
|
||||
interactions.event = {
|
||||
input = util.clone(interactions.input),
|
||||
pointer = util.clone(interactions.pointer),
|
||||
key = util.clone(interactions.key),
|
||||
hitpoint = { x = interactions.hitpoint.x, y = interactions.hitpoint.y, z = interactions.z },
|
||||
--head = { x = ox, y = oy, z = oz, direction = direction},
|
||||
collider = collider
|
||||
}
|
||||
-- clear interactions
|
||||
interactions.clear()
|
||||
end
|
||||
ecs.addSystem( api.world, interactionsUpdater )
|
||||
ecs.addSystem( api.world, interactions )
|
||||
end
|
||||
|
||||
function initCleanups(ecs)
|
||||
|
|
|
|||
|
|
@ -15,10 +15,26 @@ util.loaddir = function( path, api, target)
|
|||
if api.filesystem.isFile(ext) then
|
||||
print("[i] loading '" .. v .. "'")
|
||||
target[ v ] = api.filesystem.load( ext )(api)
|
||||
if target[ v ].index == nil then target[v].index = api.ext.max / 2 end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
util.clone = function(orig)
|
||||
local orig_type = type(orig)
|
||||
local copy
|
||||
if orig_type == 'table' then
|
||||
copy = {}
|
||||
for orig_key, orig_value in next, orig, nil do
|
||||
copy[util.clone(orig_key)] = util.clone(orig_value)
|
||||
end
|
||||
setmetatable(copy, util.clone(getmetatable(orig)))
|
||||
else -- number, string, boolean, etc
|
||||
copy = orig
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
util.cmdExist = function(cmd)
|
||||
if api.runtime.os == 'unixy' then cmd = cmd .. " 1>/dev/null 2>/dev/null" end
|
||||
local stdout, retstr, retcode = os.execute(cmd)
|
||||
|
|
@ -27,7 +43,7 @@ end
|
|||
|
||||
util.exec = function(obj, fn,a,b,c,d,e,f)
|
||||
foreach( obj,
|
||||
when('enabled',true, util.call(fn,a,b,c,d,e,f) )
|
||||
when('enabled', true, util.call(fn,a,b,c,d,e,f) )
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -198,9 +214,9 @@ end
|
|||
-- LUA SYNTAX SUGAR (from here on )
|
||||
--
|
||||
|
||||
function foreach(table, f)
|
||||
if table ~= nil then
|
||||
for k, v in pairs(table) do f(k, v) end
|
||||
function foreach(t, f, sort)
|
||||
if t ~= nil then
|
||||
for k, v in pairs(t) do f(k, v) end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue