show fediportals in dir.html + convert plugin now works with X3D

This commit is contained in:
Leon van Kammen 2026-08-11 17:47:40 +02:00
parent f61b1ed4ab
commit 257b9f56e1
15 changed files with 272 additions and 78 deletions

View file

@ -10,7 +10,7 @@
foo = pkgs.mkShell { foo = pkgs.mkShell {
# nativeBuildInputs is usually what you want -- tools you need to run # nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [ nativeBuildInputs = with pkgs.buildPackages; [
assimp
]; ];
shellHooks = '' shellHooks = ''

View file

@ -57,6 +57,7 @@ app.opts = require('config')(app)
-- load system plugins (in specific order for admin panel) -- load system plugins (in specific order for admin panel)
app.plugin.global = app.addPlugin('plugin/global') app.plugin.global = app.addPlugin('plugin/global')
app.plugin.diskscan = app.addPlugin('plugin/diskscan') app.plugin.diskscan = app.addPlugin('plugin/diskscan')
app.plugin.convert = app.addPlugin('plugin/convert')
app.plugin.moderation = app.addPlugin('plugin/moderation') app.plugin.moderation = app.addPlugin('plugin/moderation')
app.plugin.xrfragments = app.addPlugin('plugin/xrfragments') app.plugin.xrfragments = app.addPlugin('plugin/xrfragments')
app.plugin.janusxr = app.addPlugin('plugin/janusxr') app.plugin.janusxr = app.addPlugin('plugin/janusxr')

View file

@ -278,11 +278,10 @@ img.icon{ display:none; }
padding:10px 20px; padding:10px 20px;
overflow:clip; overflow:clip;
} }
.info .tag{
margin-bottom:7px;
}
b.tag{ b.tag{
margin-bottom:7px;
line-height:22px;
display:inline-block; display:inline-block;
background: #77f; background: #77f;
color: white; color: white;

BIN
src/img/button-88x31.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "https://www.web3d.org/specifications/x3d-3.0.dtd">
<X3D version="3.0" profile="Immersive" xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" xsd:noNamespaceSchemaLocation="https://www.web3d.org/specifications/x3d-3.0.xsd">
<head>
<meta name="filename" content="test_world.x3d" />
<meta name="generator" content="Blender 2.78 (sub 0)" />
</head>
<Scene>
<NavigationInfo headlight="true"
visibilityLimit="0.0"
type='"EXAMINE", "ANY"'
avatarSize="0.25, 1.75, 0.75"
/>
<Background DEF="WO_World"
groundColor="0.051 0.051 0.051"
skyColor="0.051 0.051 0.051"
/>
<Transform DEF="Cube_TRANSFORM"
translation="0.000000 1.000000 0.000000"
scale="5.000000 5.000000 1.000000"
rotation="0.000000 0.707107 0.707107 3.141593"
>
<Transform DEF="Cube_ifs_TRANSFORM"
translation="0.000000 0.000000 0.000000"
scale="1.000000 1.000000 1.000000"
rotation="1.000000 0.000000 0.000000 0.000000"
>
<Group DEF="group_ME_Cube">
<Shape>
<Appearance>
<Material DEF="MA_Material"
diffuseColor="0.500 0.500 1.000"
specularColor="0.401 0.401 0.401"
emissiveColor="0.000 0.000 0.000"
ambientIntensity="0.333"
shininess="0.098"
transparency="0.000"
/>
</Appearance>
<IndexedFaceSet solid="true"
coordIndex="0 3 2 1 -1 4 5 6 7 -1 0 1 5 4 -1 1 2 6 5 -1 2 3 7 6 -1 3 0 4 7 -1 "
>
<Coordinate DEF="coords_ME_Cube"
point="1.000000 1.000000 -1.000000 1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 -1.000000 1.000000 -1.000000 1.000000 0.999999 4.000000 0.999999 -1.000001 4.000000 -1.000000 -1.000000 4.000000 -1.000000 1.000000 4.000000 "
/>
</IndexedFaceSet>
</Shape>
</Group>
</Transform>
</Transform>
</Scene>
</X3D>

Binary file not shown.

View file

@ -95,41 +95,22 @@ div#xrecosystem{
<h3>What is it</h3> <h3>What is it</h3>
An open, seamless XR web built from simple URLs and directories, without gatekeepers.<br> An open, seamless XR web built from simple URLs and directories, without gatekeepers.<br>
XRForge transforms standard local or remote file directories into interconnected 3D spaces. Users can seamlessly "surf" spatial webs (plural!), turning everyday directories into 3D portals. XRForge transforms standard local or remote file directories into interconnected 3D spaces. Users can seamlessly "surf" spatial webs (plural!), turning everyday directories into 3D portals.<br>
<!--
Supported fileformats:
{if true then
foreach( app.plugin.janusxr.ext.object, function(k,ext)
print("<b class='tag'>" .. ext .. '</b>')
end)
foreach( app.plugin.global.ext, function(k,ext)
print("<b class='tag'>" .. ext .. '</b>')
end)
end}
-->
<br> <br>
<br> <br>
<div class="margin1">
<pre class="secondary">
┌───────────────────────────────────┐ ┌──────────────────────────────────────────┐
│ local disk │ │ Surf via 3D portals │
├───────────────────────────────────┤ └──────────────────────────────────────────┘
│ │
│ /myverse │ https://my.org/myverse
│ /elearnings │ https://my.org/myverse/elearnings
│ /elearning1/1.glb │ https://my.org/myverse/elearning/elearning1
│ /elearning2/2.glb │ https://my.org/myverse/elearning/elearning2
│ │
│ ┌──────────────────────────────┐ │
│ │ github / webdav / dropbox etc│ │
│ │ │ │
│ │ /mygames │ │ https://my.org/myverse/mygames
│ │ /biggusgateWebXR │ │ https://my.org/myverse/mygames/biggusgate
│ │ /spasca │ │ https://my.org/myverse/mygames/spasca
│ │ /otherxrforge │ │ https://my.org/myverse/otherxrforge
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │/manyfold │ │archive.org│ │ │ https://my.org/myverse/manyfold
│ │ └───────────┘ └───────────┘ │ │ https://my.org/myverse/myarchive
│ │ │ │
│ │ +40 integrations! │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────┘
</pre>
</div>
<h3>Why people want XRForge</h3> <h3>Why people want XRForge</h3>
<div> <div>

View file

@ -37,6 +37,29 @@ end}
]]) ]])
end end
end} end}
{if parent.parent == nil then
for i=1,5 do
local urls = app.opts[ 'plugin_federation_url_' .. i]
if #urls > 0 then
local title = urls:gsub(' .*','')
local fediurl = '/?url=' .. EscapeFragment(title)
local thumburl = urls:gsub('.* ','')
local icon = 'list-tree'
local thumbnail = ''
if #thumburl > 0 then thumbnail = 'style="background-image: url(' .. EscapeFragment(thumburl) .. ');"' end
print([[
<div class="tile">
<a href="]] .. fediurl .. [[">
<div class="img" ]] .. thumbnail .. [[></div>
<div class="text">
<img src="img/]] .. icon .. [[.svg" class="icon ]] .. icon .. [["/>&nbsp;<a href="]] .. fediurl .. [[">]] .. title .. [[</a>
</div>
</a>
</div>
]])
end
end
end}
</div> </div>
${footer} ${footer}

View file

@ -16,7 +16,8 @@ return {
if urlpath == "/" if urlpath == "/"
or urlpath:match("^"..rootpath) or urlpath:match("^"..rootpath)
and not urlpath:gsub(".*/",""):match("%.") then and not urlpath:gsub(".*/",""):match("%.")
and not req.param.url then
local redirect = false local redirect = false
if not app.cache.url[ urlhash ] or util.count(req.params) > 0 then if not app.cache.url[ urlhash ] or util.count(req.params) > 0 then
@ -25,13 +26,14 @@ return {
foreach( app.cache.items, function(k,v) foreach( app.cache.items, function(k,v)
if req.authenticated or app.plugin.moderation.isAllowed(v) then if req.authenticated or app.plugin.moderation.isAllowed(v) then
if v.parent then if v.parent then
--trace( "path = " .. util.stripPath(v.path) .. " " .. trace( "path = " .. util.stripPath(v.path) .. " " ..
-- "parent = " .. util.stripPath(v.parent.path) .. " " .. "parent = " .. util.stripPath(v.parent.path) .. " " ..
-- "matchpath = " .. util.stripPath(matchpath) "matchpath = " .. util.stripPath(matchpath)
--) )
if match(v.path, matchpath) then -- show parent folder if any if match(v.path, matchpath) then -- show parent folder if any
local parent = app.cache.items[ v.parent.id ] local parent = app.cache.items[ v.parent.id ]
if v.portal then if v.portal then
trace("-- portal redirect")
redirect = v.portal.url redirect = v.portal.url
end end
if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed
@ -41,15 +43,20 @@ return {
end end
-- add items from dir -- add items from dir
if match(v.parent.path, matchpath) and (v.dir or v.view3D) then if match(v.parent.path, matchpath) then
if v.dir or v.view3D then
trace("-- dir item: " .. v.path )
local parent = app.cache.items[ v.parent.id ] local parent = app.cache.items[ v.parent.id ]
res.items[ v.id ] = v res.items[ v.id ] = v
res.parent = parent res.parent = parent
end end
end end
end end
end
end) end)
local data = util.merge({ local data = util.merge({
url = req.param.url, -- query hint to render janus.url in iframe (index.html)
html_items='', html_items='',
items = {}, items = {},
parent=res.parent parent=res.parent
@ -66,7 +73,7 @@ return {
local port = ':' .. GetPort() local port = ':' .. GetPort()
if port == 80 then port = '' end if port == 80 then port = '' end
data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url data.url = GetScheme() .. '://' .. GetHost() .. port .. v.url
if v.janusxr then if v.janusxr and v.janusxr.jml then
-- what to do here? no generated portals? -- what to do here? no generated portals?
data.janusxr = app.tpl( util.readfile(v.path), data ) data.janusxr = app.tpl( util.readfile(v.path), data )
end end

View file

@ -8,7 +8,6 @@
<br/> <br/>
<br/> <br/>
<table width="100%" class="footer"> <table width="100%" class="footer">
<tr> <tr>
<td class="col3"> <td class="col3">
@ -23,7 +22,8 @@
<a href="/about">About</a><br/> <a href="/about">About</a><br/>
</td> </td>
<td class="col3"> <td class="col3">
<a href="https://codeberg.org/coderofsalvation/xrforge">Sourcecode</a> <a href="https://codeberg.org/coderofsalvation/xrforge">Sourcecode</a> <br>
<img src="/img/button-88x31.gif" style="display:inline-block"/>
</td> </td>
</tr> </tr>
</table> </table>

View file

@ -9,8 +9,10 @@ end}
<assetshader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"></assetshader> <assetshader id="xrforgeportal" src="${opts.baseurl}shader/xrforgeportal.shadertoy.frag" shadertype="shadertoy"></assetshader>
{if items and #items > 0 then {if items and #items > 0 then
for i,v in pairs(items) do for i,v in pairs(items) do
if #v.thumbnail > 0 then
print('\t<assetimage src="' .. v.thumbnail .. '" id="img' .. i .. '"/>') print('\t<assetimage src="' .. v.thumbnail .. '" id="img' .. i .. '"/>')
end end
end
end} end}
{if showfediportals then {if showfediportals then
for i=1,5 do for i=1,5 do
@ -30,17 +32,20 @@ end}
]]) ]])
else else
local room = app.opts.plugin_janusxr_portalroom local room = app.opts.plugin_janusxr_portalroom
if item and item.janusxr and item.janusxr.room then
room = item.janusxr.room
end
if room == 'random' then room = app.plugin.janusxr.getRandomRoom() end if room == 'random' then room = app.plugin.janusxr.getRandomRoom() end
print(' <room skybox="true" showavatar="false" use_local_asset="' .. room .. '">') print(' <room skybox="true" showavatar="false" use_local_asset="' .. room .. '">')
end} end}
<light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" /> <light js_id="dan9-janus:light/light_cone_angle=0-6" pos="0 3 2" collision_trigger="false" />
{if true then {if true then
function addPortal(id,x,v,url,i,z,thumbprefix,rotation,scale) function addPortal(id,x,v,url,i,z,thumb,rotation,scale)
print('\t\t<object js_id="' .. id .. '_' .. i .. '" pos="' .. x .. ' 0 ' .. z .. '" rotation="' .. rotation .. '">') print('\t<object js_id="' .. id .. '_' .. i .. '" pos="' .. x .. ' 0 ' .. z .. '" rotation="' .. rotation .. '">')
print('\t\t <link url="' .. url .. '" thumb_id="' .. thumbprefix .. i .. '" title_col="0.4 0.37 0.4" pos="0 0 0.001" draw_glow="true" scale="3 3 0.1" />') print('\t <link url="' .. url .. '" ' .. thumb .. ' title_col="0.4 0.37 0.4" pos="0 0 0.001" draw_glow="true" scale="3 3 0.1" />')
print('\t\t <link url="' .. url .. '" shader_id="xrforgeportal" pos="0 0 0.003" draw_glow="false" scale="3 3 0.1" />') print('\t <link url="' .. url .. '" shader_id="xrforgeportal" pos="0 0 0.003" draw_glow="false" scale="3 3 0.1" />')
print('\t\t <text text="' .. v.title .. '" pos="0 3.4 0" col="1 1 1" font_scale="false" scale="' .. scale .. '" />') print('\t <text text="' .. v.title .. '" pos="0 3.4 0" col="1 1 1" font_scale="false" scale="' .. scale .. '" />')
print('\t\t</object>') print('\t</object>')
end end
end} end}
<text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" emissive="0 0 0" roughness="0.3" metalness="0" /> <text js_id="location" pos="0 6 -5" billboard="y" col="#BBFFFF" scale="10 10 1" text="${url}" emissive="0 0 0" roughness="0.3" metalness="0" />
@ -52,9 +57,13 @@ end}
local width = 3.2 local width = 3.2
local center = -((#items*width)/1.3) + (i*width) local center = -((#items*width)/1.3) + (i*width)
local url = v.url local url = v.url
local thumb = ' shader_id="defaultportal"'
if v.thumbnail then
thumb = ' thumb_id="img' .. i .. '" '
end
if v.dir then url = url .. "/" end if v.dir then url = url .. "/" end
if v.redirect then url = v.redirect end -- dont wrap standalone glb's in janusxr if v.redirect then url = v.redirect end -- dont wrap standalone glb's in janusxr
addPortal('dir',center, v, url,i, -4, 'img', '0 0 0', '0.2 0.2 0.2') addPortal('dir',center, v, url,i, -4, thumb, '0 0 0', '0.2 0.2 0.2')
end end
end} end}
{if showfediportals and not item then {if showfediportals and not item then
@ -74,7 +83,8 @@ end}
for i,fediurl in pairs(portals) do for i,fediurl in pairs(portals) do
local width = 3.2 local width = 3.2
local center = -((#portals*width)/1.3) + (i*width) local center = -((#portals*width)/1.3) + (i*width)
addPortal('federated',center, {title=fediurl}, fediurl,i, 4, 'federated', '0 180 0', '0.1 0.1 0.1') local thumb = ' image_id="federated' .. i .. '"'
addPortal('federated',center, {title=fediurl}, fediurl,i, 4, thumb, '0 180 0', '0.1 0.1 0.1')
end end
end} end}
<object id="cube" rotation="0 0 0" position="0 0 0" scale="2 0.1 2"/> <!-- something to stand on --> <object id="cube" rotation="0 0 0" position="0 0 0" scale="2 0.1 2"/> <!-- something to stand on -->

88
src/plugin/convert.lua Normal file
View file

@ -0,0 +1,88 @@
local util = require('util')
app.on('scan_experience', function(exp)
--print_r(exp)
end)
app.on('scan_experience_file', function(exp)
local me = app.plugin.convert
if me.assimp then
local filetypes_in = app.opts.plugin_convert_assimp_filetypes_in:gsub(" "," ")
local filetypes_out = app.opts.plugin_convert_assimp_filetypes_out:gsub(" "," ")
local convert = false
foreach( util.split( filetypes_in , ' '), function(k,ext)
ext = ext:gsub("%W", "")
if exp.path:lower():match( "." .. ext:lower() .. "$") then
convert = true
end
end)
if convert then
foreach( util.split( filetypes_out , ' '), function(k,ext)
ext = ext:gsub("%W", "")
local outfile = exp.path .. '.' .. ext
local fullpath = exp.file.fullpath .. '.' .. ext
local flags = ''
if ext:lower() == 'glb' then flags = flags .. ' --embed-textures' end
if not util.fileExist(outfile) then
print('> assimp export ' .. exp.file.fullpath .. ' ' .. fullpath )
local ok = os.execute('assimp export ' .. exp.file.fullpath .. ' ' .. fullpath )
if ok then
-- notify extra file to plugins
local stat,err = unix.stat(outfile)
if not err then
app.pub("scan_experience_file", nil, util.merge( exp, {
parent = exp.parent,
path = outfile,
url = "/" .. util.stripPath(outfile),
id = outfile,
file = {stat=stat, ino = stat:ino(), attr=stat, fullpath=fullpath}
}))
end
end
end
end)
end
end
end)
app.on('set', function(k,v)
end)
-- this registers the plugin UI
local plugin = {
name = "Conversion",
info = 'Auto-convert (older) 3D fileformats to newer ones, by generating sidecarfiles (foo.x3d.glb e.g.) via <a href="https://assimp.org" target="_blank">assimp</a>',
opts = {
{
key='app.opts.plugin_convert_assimp_filetypes_in',
title='from filetype',
info='Space-separated 3D File extensions for assimp to process, <a href="http://lua-users.org/wiki/PatternsTutorial" target="_blank">lua patterns</a> are allowed',
default = "x3d",
placeholder = "",
values=''
},
{
key='app.opts.plugin_convert_assimp_filetypes_out',
title='to filetype',
info='Space-separated 3D File extensions for assimp to process, <a href="http://lua-users.org/wiki/PatternsTutorial" target="_blank">lua patterns</a> are allowed',
default = 'glb',
values=''
},
},
}
if util.cmdExist('assimp') then
plugin.assimp = true
local stdout, retstr, retcode = util.execute('assimp listext')
plugin.filetypes = stdout:gsub(";%*."," ")
plugin.info = "<br><b class='tag'>assimp detected</b>&nbsp;<br>" .. plugin.info .. '<br><b>Supported filetypes</b>: '
foreach( util.split( plugin.filetypes, ' '), function(k,ext)
plugin.info = plugin.info .. '<b class="tag">' .. ext .. '</b>&nbsp;'
end)
else
plugin.info = plugin.info .. "<br><br><b>NOTE:</b> <a href='https://assimp.org' target='_blank'>assimp</a> is not installed on this system: please install."
end
return plugin

View file

@ -14,6 +14,7 @@ end)
local plugin = { local plugin = {
name = "Global settings", name = "Global settings",
info = '', info = '',
ext = {"mp3","wav","ogg","mp4","vtt","md","html","jml","jpg","png","svg"},
opts = { opts = {
{ {
key='app.opts.plugin_global_title', key='app.opts.plugin_global_title',
@ -66,7 +67,7 @@ local plugin = {
}, },
middleware = { middleware = {
homepage_2D_or_3D = function(req,res,next) homepage_2D_or_3D = function(req,res,next)
if (app.opts.plugin_global_homepage == '3D') or (req.param.view == '3D') then if (app.opts.plugin_global_homepage == '3D') or (req.param.view == '3D') or (req.param.url) then
res.template = 'page/index.html' res.template = 'page/index.html'
else else
res.template = 'page/dir.html' res.template = 'page/dir.html'

View file

@ -8,7 +8,27 @@ app.on('scan_experience', function(exp)
end) end)
app.on('scan_experience_file', function(exp) app.on('scan_experience_file', function(exp)
local me = app.plugin.janusxr
local size = exp.file.attr:size() local size = exp.file.attr:size()
local wrap = false
if app.opts.plugin_janusxr_wrap_3dfile ~= "disabled"
and not (exp.tag.xrfragments and exp.tag.xrfragments.qualify) then -- dont wrap xrfragments experience
foreach( me.ext.object, function(k,ext)
if exp.path:lower():match("."..ext.."$") then
wrap = ext
end
end)
if wrap then
exp.view3D = true
exp.janusxr = {
room = app.opts.plugin_janusxr_wrap_3dfile
}
app.cache.items[ exp.file.ino ] = exp
print("✅ └─ plugin/janusxr: wrapping " .. exp.path)
end
end
if size < 100000 then -- skip large binary files > 100kb if size < 100000 then -- skip large binary files > 100kb
local data = util.readfile( exp.file.fullpath) local data = util.readfile( exp.file.fullpath)
if data then if data then
@ -21,7 +41,7 @@ app.on('scan_experience_file', function(exp)
app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins app.pub("scan_experience_janusxr", nil, {exp=exp, item=item}) -- be nice to other plugins
app.cache.items[ exp.file.ino ] = exp app.cache.items[ exp.file.ino ] = exp
exp.view3D = true exp.view3D = true
exp.janusxr = true exp.janusxr = {}
print("✅ └─ plugin/janusxr: " .. exp.file.name) print("✅ └─ plugin/janusxr: " .. exp.file.name)
end end
else else
@ -34,9 +54,14 @@ app.on('set', function(k,v)
end) end)
-- this registers the plugin UI -- this registers the plugin UI
local rooms = {random="random room",room_plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
local plugin = { local plugin = {
name = "JanusXR", name = "JanusXR",
info = '<a href="https://coderofsalvation.github.io/janus-guide" target="_blank">JanusXR</a> connects XR experiences via XML portals (+AFRAME (geometry)markup also supported).', info = '<a href="https://coderofsalvation.github.io/janus-guide" target="_blank">JanusXR</a> connects XR experiences via XML portals (+AFRAME (geometry)markup also supported).',
ext = {
object = {'glb', 'gltf', 'obj', 'fbx', 'ply', 'stl', 'wrl', 'gltf', 'glb', 'bvh' }
},
opts = { opts = {
{ {
key='app.opts.plugin_janusxr_janusweb_endpoint', key='app.opts.plugin_janusxr_janusweb_endpoint',
@ -52,33 +77,33 @@ local plugin = {
default="https://p.janusxr.org https://cors.xrforge.org", default="https://p.janusxr.org https://cors.xrforge.org",
values='' values=''
}, },
{
key='app.opts.plugin_janusxr_wrap_3dfile',
title='wrap 3D file',
info='render 3D files in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="room_plane",
values=util.merge(rooms,{disabled="-- disabled --"})
},
{ {
key='app.opts.plugin_janusxr_wrap_image', key='app.opts.plugin_janusxr_wrap_image',
title='wrap images', title='wrap images',
info='Generate <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> to embed images into an XR experience', info='render images in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="yes", default="room_plane",
values={yes="yes",no="no"} values=util.merge(rooms,{disabled="-- disabled --"})
}, },
{ {
key='app.opts.plugin_janusxr_wrap_video', key='app.opts.plugin_janusxr_wrap_video',
title='wrap videos', title='wrap videos',
info='Generate <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> to embed videos into an XR experience', info='render videos in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="yes", default="room_plane",
values={yes="yes",no="no"} values=util.merge(rooms,{disabled="-- disabled --"})
}, },
{ {
key='app.opts.plugin_janusxr_wrap_aframe', key='app.opts.plugin_janusxr_wrap_aframe',
title='wrap AFRAME', title='wrap AFRAME',
info='Generate <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> to embed AFRAME markup into an XR experience', info='render AFRAME markup in <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">janusxr.xml</a> room',
default="yes", default="room_plane",
values={yes="yes",no="no"} values=util.merge(rooms,{disabled="-- disabled --"})
},
{
key='app.opts.plugin_janusxr_wrap_template',
title='Media room',
info='What kind of space should images/videos be rendered into?',
default="random",
values={random="random room",plane="wirefloor",room1="room1",room2="room2",room3="room3",room4="room4",room5="room5",room6="room6",room7="room7"}
}, },
{ {
key='app.opts.plugin_janusxr_portalroom', key='app.opts.plugin_janusxr_portalroom',
@ -125,4 +150,10 @@ local plugin = {
end end
} }
plugin.info = plugin.info .. "<br><br>supported 3D files:&nbsp;"
foreach( plugin.ext.object, function(k,ext)
plugin.info = plugin.info .. '<b class="tag">' .. ext .. '</b>&nbsp;'
end)
plugin.info = plugin.info .. '+ <a href="https://coderofsalvation.github.io/janus-guide/#/build/room" target="_blank">JML</a> XML-markup in any text/binary file'
return plugin return plugin

View file

@ -10,7 +10,6 @@ app.on('scan_experience_file', function(exp)
local me = app.plugin.xrfragments local me = app.plugin.xrfragments
foreach( util.split( app.opts.plugin_xrfragments_filetypes, ' '), function(k,ext) foreach( util.split( app.opts.plugin_xrfragments_filetypes, ' '), function(k,ext)
if exp.path:lower():match("."..ext.."$") then if exp.path:lower():match("."..ext.."$") then
exp.view3D = true
local hasHref = false local hasHref = false
if ext == 'glb' then if ext == 'glb' then
-- detect XR Fragment exp https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File -- detect XR Fragment exp https://xrfragment.org/#%F0%9F%93%9C%20level0%3A%20File
@ -37,6 +36,7 @@ app.on('scan_experience_file', function(exp)
end end
exp.tag.xrfragments = { qualify = hasHref or hasPNG or hasWebVTT or hasOGG or hasJSONref, href = hasHref } exp.tag.xrfragments = { qualify = hasHref or hasPNG or hasWebVTT or hasOGG or hasJSONref, href = hasHref }
if exp.tag.xrfragments.qualify then if exp.tag.xrfragments.qualify then
exp.view3D = true
print("✅ └─ plugin/xrfragments: detected xrfragments experience") print("✅ └─ plugin/xrfragments: detected xrfragments experience")
end end
if exp.tag.xrfragments.href then if exp.tag.xrfragments.href then
@ -47,6 +47,7 @@ app.on('scan_experience_file', function(exp)
end) end)
-- finally insert if viewable -- finally insert if viewable
if exp.view3D then if exp.view3D then
print("✅ └─ plugin/xrfragments: marking as 3D viewable")
app.cache.items[ exp.file.ino ] = exp app.cache.items[ exp.file.ino ] = exp
end end
end end