xurfer/xurfer/ext/xrfragments/lovr-xrf.lua

125 lines
4.6 KiB
Lua

-- Copyright 2026 Leon van Kammen / coderofsalvation. All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification, are
-- permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, this list of
-- conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list
-- of conditions and the following disclaimer in the documentation and/or other materials
-- provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY AS IS'' AND ANY EXPRESS OR IMPLIED
-- WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-- FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-- The views and conclusions contained in the software and documentation are those of the
-- authors and should not be interpreted as representing official policies, either expressed
-- or implied, of
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, 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, obj, i, metadata)
local model = obj['model']
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 + (obj.x or 0),
y + (obj.y or 0),
z + (obj.z or 0),
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