2026-06-08 11:04:52 +02:00
|
|
|
-- 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
|
|
|
|
|
|
2026-06-07 14:48:52 +02:00
|
|
|
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
|
2026-06-12 11:27:12 +02:00
|
|
|
cb(node,obj, i, metadata)
|
2026-06-07 14:48:52 +02:00
|
|
|
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)
|
2026-06-12 11:27:12 +02:00
|
|
|
return function(node_or_mesh, obj, i, metadata)
|
|
|
|
|
local model = obj['model']
|
2026-06-07 14:48:52 +02:00
|
|
|
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(
|
2026-06-12 11:27:12 +02:00
|
|
|
x + (obj.x or 0),
|
|
|
|
|
y + (obj.y or 0),
|
|
|
|
|
z + (obj.z or 0),
|
2026-06-07 14:48:52 +02:00
|
|
|
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
|