From f11b4201970ba354211ed8071600c02e6fedfb3d Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Tue, 7 Jul 2026 17:48:47 +0200 Subject: [PATCH] gaze-based controls --- xurfer/ecs.lua | 10 ++ xurfer/ext/gazecontrol/main.lua | 77 ++++++--- xurfer/ext/gazecontrol/reticle_hover.png | Bin 0 -> 14871 bytes xurfer/ext/gazecontrol/reticle_idle.png | Bin 14871 -> 4884 bytes xurfer/ext/skeleton/main.lua | 8 +- xurfer/ext/startupscene/main.lua | 9 +- xurfer/main.lua | 12 +- xurfer/runtime/lovr/main.lua | 200 ++++++++++++++--------- xurfer/util.lua | 24 ++- 9 files changed, 222 insertions(+), 118 deletions(-) create mode 100644 xurfer/ext/gazecontrol/reticle_hover.png diff --git a/xurfer/ecs.lua b/xurfer/ecs.lua index 131318d..364e754 100644 --- a/xurfer/ecs.lua +++ b/xurfer/ecs.lua @@ -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 diff --git a/xurfer/ext/gazecontrol/main.lua b/xurfer/ext/gazecontrol/main.lua index 7902364..941cc0d 100644 --- a/xurfer/ext/gazecontrol/main.lua +++ b/xurfer/ext/gazecontrol/main.lua @@ -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, diff --git a/xurfer/ext/gazecontrol/reticle_hover.png b/xurfer/ext/gazecontrol/reticle_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..f1fb1b30dbc6bedfe1de43d88fff382ef9679f1b GIT binary patch literal 14871 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 JPDHLkV1mfgB;Wu5 literal 0 HcmV?d00001 diff --git a/xurfer/ext/gazecontrol/reticle_idle.png b/xurfer/ext/gazecontrol/reticle_idle.png index f1fb1b30dbc6bedfe1de43d88fff382ef9679f1b..36e004080f206975c505048484084a268e6024cb 100644 GIT binary patch literal 4884 zcmeHLeN+=y7N0;R0)?Wb<@gE4At>b?eHyprW|eDz#Quj&^nRs7ETgJs?VVb>B?*Tzk$Q&e{IQ%elNa z_ucz@_x;{|mrPcrr%jFs8yW^dP=qDboB{fBL83yy-%b0Et%M*+G(F2=w^+tO1A#6a zC|-gjf(;N!k8zMB7zzfujX)wLLC|2(ZviqG=mmR0hDrvA{byi2tYdr#jK_A^2>wdo z8v**GKrk?l2K^Zz%!+aUw`c>ZgCmWjHsb~FPDZ-7a-`zRnsheRtcRCW$&$eYOW7hm@E-Rn1-zkg=>w)vs-dIa}PS%p>w^4P~@&EjWS zr{N^)R1g$vqZK}ej?O;;padhUq zv@CL70coJ*2@}GMJ{$lzX`YbzoP{nA?lZ|nTpWxAH6oXZCVYWOJ_}@963fvtwL+~x z;bb3Eg2^X@$&4Ij$1}_+9T4EjBzN$<8%GeY*Q@ZV6fBpIC=CV!f?^1U!N3Ccl)8Ar z2fI9R0z?4AOnXR8ywfDgn+xDllSMVwBg zr-z46E&(7N0sX9pC#%#=BN?=ZE#^o%xrBD{aXld@vdiCH%oU2;p-6--q@BRj16Eb` zT2i=|T^<4j`Ha&odI7R~S@Mj%o2*{32{Yn$dIkdAyKsA1ckW#@23A%pZf411Av}xO zBp24lDVAg?TpXfyJ*KzY46vHiYGE~MKw&~<$6%CJsW7eDZc|}c4=9Vv!xJu&7N7vQ zf&nsJ7y;{Oih$KR!T{U!1`4M2B%!m@1feE%Js_rY49H5NuxC^P6a}CtC2b&7 z8VV*TogG$dluDSeQA!xK(WKU<(rIiMDMC>sp2BiY0vspfB=Tv*?aCJ?1j6yebc;!j zDbQ|BdLhBvfrCjtjd2zGx+k(2C!NU?0-H*m9y91Mtx|=7tt-{tM%gsy0huUZiWOFz z5sn21n*qcULY@KuQ4jWli)EDMvRJmzB=4%tUDH-@ohX7Q%%G|R(5`Bo)mg3IKrtK* zgfGBZS&Ffj{x56c^2m(99vpcp%cnUlgGU8Q|$;3;66J%fu9-@S% z#5e&~V2X4Qu6!C)kB)Th)H6Sm3R(?mM=@}IdRncA)q0eK^#q}TQM-y%+6gtPQEPgm zdssW~B{(`UAMgmc0_iFKACrw04=TR5wbwxlrvM0pQ5}q8|4kUuv0+508J#;eA}?@a z6b*V58Q>Stf#L;fA<|V0J2(@{&M(+H&f*tb0YLXJ(kFiV>FTGePYm?QxIer4>FN^$ zeKPLPuKycdVcoA&v?_ZW;8iQcHg&QY@L5es* zB)(kZ-`RVTsYw80m3D zmUZvLQ-2-f*)x2_w!QrLF)fcx7pjJ5G~PK@v#pGKbz|FuRuDwrnqs!uTz4_0vaD&> zvwNHIq3^cez0L1SI8c-G+{Gk)+;Hy1_EGP*-!H1XW6|L@DUwv5^uyQT5O+&9O! z-sRMlBd;`mG-Qbfc~bFR#@xdCU0LhO?BA_9Q8BDGjX#;T($yud-*bG`r4?7sF1*p?Rk70AgbUkCJU4TS z=M?*IR3yl=LpEGITN_t9wY)C;d3!zAbZ=p^+BJV161sMjkMf1CKl5Y3kTZ$FsZGtM zK>v8v%eQw(c52$DhHwqg#jVH21RESsdC2+;j=7?y9sRYSVHW%(5A<(setWpOd}-RE zmf|B7s%1BKuY(GpmhVRn(~Z^|BHCPQSxNJQt^Z6Z`B?6#l;~pzI$k=cX}+VX$y|I@ z;$EI|W9N=(n{|?HC9N|mkq_sdzjdHKuU_IOC*L?bamSz|TG^wT*zrG1SDb3yd1;C6 x+uTR3&oBR!`)JHa$Ct_@M)31aUs^-%B8lT(&Y}AE)8-3)mgF?^{z<=G{2#hKnp^+? literal 14871 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 JPDHLkV1mfgB;Wu5 diff --git a/xurfer/ext/skeleton/main.lua b/xurfer/ext/skeleton/main.lua index 4137ef2..f189739 100644 --- a/xurfer/ext/skeleton/main.lua +++ b/xurfer/ext/skeleton/main.lua @@ -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() diff --git a/xurfer/ext/startupscene/main.lua b/xurfer/ext/startupscene/main.lua index a4f3e76..1d53077 100644 --- a/xurfer/ext/startupscene/main.lua +++ b/xurfer/ext/startupscene/main.lua @@ -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) diff --git a/xurfer/main.lua b/xurfer/main.lua index 168d96c..659b026 100755 --- a/xurfer/main.lua +++ b/xurfer/main.lua @@ -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 diff --git a/xurfer/runtime/lovr/main.lua b/xurfer/runtime/lovr/main.lua index b2bf969..5e70354 100644 --- a/xurfer/runtime/lovr/main.lua +++ b/xurfer/runtime/lovr/main.lua @@ -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) diff --git a/xurfer/util.lua b/xurfer/util.lua index ae8724e..c3ec80c 100644 --- a/xurfer/util.lua +++ b/xurfer/util.lua @@ -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