wip: href clicks
This commit is contained in:
parent
0d8c32a444
commit
3219addf1b
6 changed files with 184 additions and 50 deletions
|
|
@ -14,7 +14,7 @@ local gltf = {
|
|||
local ecs = api.ecs
|
||||
ecs.add( ecs.world, {
|
||||
x = 0,
|
||||
y = -7,
|
||||
y = 0,
|
||||
z = 0,
|
||||
model = model
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ local sample = require "sample"
|
|||
|
||||
return {
|
||||
name = "kitchensink",
|
||||
enabled = true,
|
||||
enabled = false,
|
||||
|
||||
init = function() end,
|
||||
|
||||
|
|
|
|||
96
src/ext/xrfragments/lovr-xrf.lua
Normal file
96
src/ext/xrfragments/lovr-xrf.lua
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
local json = require("json")
|
||||
local xrf = {}
|
||||
|
||||
xrf.traverseNodesContaining = function(key,obj,cb)
|
||||
local metadata = json.decode( obj['model']:getMetadata() )
|
||||
-- Blender stores extras in single-object glb's to 'mesh' but multi-object glb's to 'nodes'
|
||||
-- hence node_or_mesh
|
||||
local keys = {'nodes','meshes'}
|
||||
|
||||
for k, tkey in pairs(keys) do
|
||||
local i = 1
|
||||
local tree = metadata[tkey]
|
||||
xrf.traverse( tree, function(node)
|
||||
if node['extras'] ~= nil then
|
||||
if node['extras'][key] ~= nil then
|
||||
cb(node,obj['model'], i, metadata)
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
xrf.getNodeFromMesh = function(mesh,metadata, i)
|
||||
local node
|
||||
if mesh['mesh'] == nil then
|
||||
local extras = mesh['extras']
|
||||
node = metadata['nodes'][i]
|
||||
node['extras'] = extras
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
xrf.calcDimensions = function(bbox)
|
||||
local w = math.abs( bbox['maxx'] - bbox['minx'] )
|
||||
local h = math.abs( bbox['maxy'] - bbox['miny'] )
|
||||
local d = math.abs( bbox['maxz'] - bbox['minz'] )
|
||||
return w, h, d
|
||||
end
|
||||
|
||||
xrf.calcCenteredWorldPosition = function(node, model, bbox)
|
||||
-- adjust for meshes which don't have origin in their center
|
||||
local x, y, z = model:getNodePosition(node['name'], 'root') -- worldcoords
|
||||
x = x + ( ( bbox['maxx'] + bbox['minx']) /2 )
|
||||
y = y + ( ( bbox['maxy'] + bbox['miny']) /2 )
|
||||
z = z + ( ( bbox['maxz'] + bbox['minz']) /2 )
|
||||
return x, y, z
|
||||
end
|
||||
|
||||
xrf.calcScale = function(node)
|
||||
return {
|
||||
node['scale'] and node['scale'][1] or 1,
|
||||
node['scale'] and node['scale'][2] or 1,
|
||||
node['scale'] and node['scale'][3] or 1,
|
||||
}
|
||||
end
|
||||
|
||||
xrf.makeClickable = function( physicsWorld, cb)
|
||||
return function(node_or_mesh,model, i, metadata)
|
||||
local node = node_or_mesh
|
||||
if node['mesh'] == nil then node = xrf.getNodeFromMesh(node_or_mesh, metadata, i) end
|
||||
print("making node " .. node['name'] .. " clickable")
|
||||
local meshindex = 1 + node['mesh']
|
||||
local mesh = model:getMesh( meshindex )
|
||||
local minx, maxx, miny, maxy, minz, maxz = mesh:getBoundingBox()
|
||||
local bbox = {maxx=maxx, minx=minx, maxy=maxy, miny=miny, maxz=maxz, minz=minz}
|
||||
local w, h, d = xrf.calcDimensions( bbox )
|
||||
local x, y, z = xrf.calcCenteredWorldPosition( node, model, bbox )
|
||||
local scale = xrf.calcScale(node)
|
||||
-- we cannot use newMeshCollider because storage type of modelmesh is not cpu
|
||||
local collider = physicsWorld:newBoxCollider(
|
||||
x, y, z,
|
||||
scale[1] * (w + 0.01), -- add 0.01 to avoid 0
|
||||
scale[2] * (h + 0.01), -- in case of
|
||||
scale[3] * (d + 0.01) -- planes e.g.
|
||||
)
|
||||
collider:setUserData( {name = node['name'], node = node, onclick = cb })
|
||||
end
|
||||
end
|
||||
|
||||
-- utility function to traverse recursive table
|
||||
xrf.traverse = function(arr, cb, key)
|
||||
if key == nil then key = 'children' end
|
||||
for k, child in pairs(arr) do
|
||||
if type(child) == 'table' then
|
||||
cb(child)
|
||||
if type(child[key]) == 'table' then
|
||||
if child[key][1] then
|
||||
xrf.traverse( child[key], cb, key )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return xrf
|
||||
|
|
@ -1,60 +1,33 @@
|
|||
local api = ...
|
||||
local xrf
|
||||
local xrf = require("ext/xrfragments/lovr-xrf")
|
||||
local xrfsystem
|
||||
|
||||
return {
|
||||
enabled = true,
|
||||
init = function()
|
||||
-- create a (ecs) system which detects add/remove entities
|
||||
local ecs = api.ecs
|
||||
xrf = ecs.system({
|
||||
xrfsystem = ecs.system({
|
||||
|
||||
filter = ecs.filter('x&y&z'),
|
||||
|
||||
onAdd = function(self,obj)
|
||||
function scanHrefs()
|
||||
local tree = api.json.decode( obj['model']:getMetadata() )
|
||||
api.util.traverse( tree['nodes'], function(obj)
|
||||
if obj['extras'] ~= nil then
|
||||
if obj['extras']['href'] ~= nil then
|
||||
print(obj['name'] .. ".href => " .. obj['extras']['href'] )
|
||||
|
||||
-- create collider
|
||||
-- The "trick" is to set collider:setUserData on creation, to something unique that will act as some sort of identifier for your object. So when a hit is detected from world:raycast you get to know which object this collider is attached to. Hope it makes sense!
|
||||
|
||||
--To achieve this in Lua, you can create a metatable that uses the __newindex metamethod to intercept assignments to the table's fields. Whenever x is updated, the onUpdate function will be called with the table itself as the argument.
|
||||
--Here's how you can implement it:
|
||||
--
|
||||
--
|
||||
--foo = { x = 0, y = 0, z = 0, onUpdate = function(self) print("Updated!") end }
|
||||
--
|
||||
---- Create a metatable
|
||||
--local mt = {
|
||||
-- __newindex = function(t, key, value)
|
||||
-- rawset(t, key, value) -- Set the value
|
||||
-- if key == "x" then
|
||||
-- t.onUpdate(t) -- Call onUpdate if x is updated
|
||||
-- end
|
||||
-- end
|
||||
--}
|
||||
--
|
||||
---- Set the metatable for foo
|
||||
--setmetatable(foo, mt)
|
||||
|
||||
end
|
||||
xrf.traverseNodesContaining('href', obj,
|
||||
xrf.makeClickable( api.ecs.worldPhysics,
|
||||
function(obj, collider)
|
||||
print(obj['name'] .. ".href => " .. obj['extras']['href'] )
|
||||
api.ecs.add( ecs.world, {collider = collider})
|
||||
end
|
||||
end)
|
||||
end
|
||||
if obj['model'] ~= nil then
|
||||
pcall(scanHrefs)
|
||||
end
|
||||
end
|
||||
)
|
||||
)
|
||||
end,
|
||||
|
||||
onRemove = function(self,obj)
|
||||
|
||||
end
|
||||
|
||||
})
|
||||
ecs.addSystem( ecs.world, xrf )
|
||||
ecs.addSystem( ecs.world, xrfsystem )
|
||||
|
||||
end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ api.iui = iui
|
|||
api.backend = backend
|
||||
local ecs = api.ecs
|
||||
|
||||
-- ecs systems
|
||||
local interactionsUpdater
|
||||
local renderer
|
||||
|
||||
--- @type Texture
|
||||
local envTex
|
||||
|
||||
|
|
@ -45,7 +49,7 @@ function lovr.update(dt)
|
|||
lovr.event.quit()
|
||||
end
|
||||
|
||||
ecs.update( ecs.world, pass, ecs.filter('updatesystem') )
|
||||
ecs.update( ecs.world, dt, ecs.filter('updatesystem') )
|
||||
|
||||
iui.beginFrame(dt)
|
||||
|
||||
|
|
@ -83,8 +87,22 @@ function lovr.draw(pass)
|
|||
if api.iui.idiom == "vr" then
|
||||
pass:setClear(0.5, 0.5, 0.5)
|
||||
end
|
||||
|
||||
api.exec(api.ext, "draw",pass)
|
||||
ecs.update( ecs.world, pass, ecs.filter('drawsystem') )
|
||||
|
||||
-- Dot
|
||||
if selectedBox then
|
||||
pass:setColor(0, 0, 1)
|
||||
pass:sphere(hitpoint, .01)
|
||||
end
|
||||
|
||||
-- Laser pointer
|
||||
local hand = vec3(lovr.headset.getPosition('hand/left/point'))
|
||||
local direction = quat(lovr.headset.getOrientation('hand/left/point')):direction()
|
||||
pass:setColor(1, 1, 1)
|
||||
pass:line(hand, selectedBox and hitpoint or (hand + direction * 50))
|
||||
|
||||
if api.mainWindow then
|
||||
api.mainWindow:draw(pass)
|
||||
end
|
||||
|
|
@ -100,18 +118,23 @@ 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, button=button}
|
||||
end
|
||||
|
||||
function lovr.mousepressed(x, y, button)
|
||||
backend.mousepressed(x, y, button)
|
||||
interactionsUpdater['mouse']['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("ja")
|
||||
end
|
||||
|
||||
function lovr.wheelmoved(x, y)
|
||||
backend.wheelmoved(x, y)
|
||||
interactionsUpdater['mouse']['wheel'] = {x=x, y=y}
|
||||
end
|
||||
|
||||
function lovr.keypressed(key, scancode, isRepeat)
|
||||
|
|
@ -131,8 +154,8 @@ local initECS = function(ecs)
|
|||
ecs.world = ecs.world()
|
||||
|
||||
-- lovr.draw => ecs.drawsystem (render-logic thread)
|
||||
local renderer = ecs.processingSystem()
|
||||
renderer.filter = ecs.requireAny('model') -- add more types when needed
|
||||
renderer = ecs.processingSystem()
|
||||
renderer.filter = ecs.requireAny('model')
|
||||
renderer.drawsystem = true
|
||||
function renderer:process(obj, pass)
|
||||
if obj['model'] ~= nil then
|
||||
|
|
@ -149,14 +172,53 @@ local initECS = function(ecs)
|
|||
ecs.addSystem( ecs.world, renderer )
|
||||
|
||||
-- lovr.update => ecs updatesystem (game-logic thread)
|
||||
local update = ecs.processingSystem()
|
||||
update.updatesystem = true
|
||||
update.filter = ecs.requireAll('update') -- *TODO* might need filter later
|
||||
function update:process(obj, dt)
|
||||
local updater = ecs.processingSystem()
|
||||
updater.updatesystem = true
|
||||
updater.filter = ecs.requireAll('update')
|
||||
function updater:process(obj, dt)
|
||||
print_r(obj['data'])
|
||||
end
|
||||
ecs.addSystem( ecs.world, update )
|
||||
ecs.addSystem( ecs.world, updater )
|
||||
|
||||
end
|
||||
|
||||
function initInteractions(ecs)
|
||||
|
||||
ecs.worldPhysics = lovr.physics.newWorld(0, 0, 0)
|
||||
interactionsUpdater = ecs.processingSystem()
|
||||
interactionsUpdater.updatesystem = true
|
||||
interactionsUpdater.filter = ecs.requireAll('collider')
|
||||
-- collider vars
|
||||
interactionsUpdater.mouse = { released = false}
|
||||
interactionsUpdater.hitpoint = lovr.math.newVec3()
|
||||
interactionsUpdater.selectedBox = nil
|
||||
|
||||
|
||||
function interactionsUpdater:process(obj, dt)
|
||||
ecs.worldPhysics:update(dt)
|
||||
|
||||
local ox, oy, oz = lovr.headset.getPosition('hand/left/point')
|
||||
local dx, dy, dz = quat(lovr.headset.getOrientation('hand/left/point')):direction():mul(50):unpack()
|
||||
local collider, shape, x, y, z = ecs.worldPhysics:raycast(ox, oy, oz, ox + dx, oy + dy, oz + dz)
|
||||
|
||||
if collider then
|
||||
if lovr.headset.isDown(hand, 'trigger') then
|
||||
local node = collider:getUserData()
|
||||
selectedBox = collider
|
||||
hitpoint:set(x, y, z)
|
||||
print( "collide with " .. node['name'] .. " => " .. node['extras']['href'] )
|
||||
print("buttondown")
|
||||
end
|
||||
if lovr.headset.wasReleased(hand, 'trigger') or interactionsUpdater['mouse']['released'] then
|
||||
print('click!')
|
||||
end
|
||||
end
|
||||
-- reset mouse
|
||||
mouse['released'] = false
|
||||
mouse['pressed'] = false
|
||||
end
|
||||
ecs.addSystem( ecs.world, interactionsUpdater )
|
||||
end
|
||||
|
||||
initECS(api.ecs)
|
||||
initInteractions(api.ecs)
|
||||
|
|
|
|||
|
|
@ -20,4 +20,7 @@ if lovr ~= nil then
|
|||
api.exec( api.ext, 'init')
|
||||
end
|
||||
|
||||
api.browser.to("https://coderofsalvation.codeberg.page/xrfragment-haxe/example/assets/example.glb?bar=1&f=2#foo")
|
||||
local url='https://coderofsalvation.codeberg.page/xrfragment-haxe/example/assets/example.glb?bar=1&f=2#foo'
|
||||
--local url = 'https://codeberg.org/coderofsalvation/xrfragment/raw/branch/main/assets/template/website/website.glb'
|
||||
--local url = 'https://codeberg.org/coderofsalvation/xrfragment/raw/branch/main/assets/simple-a.glb'
|
||||
api.browser.to(url)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue