diff --git a/shell.nix b/shell.nix index 55b57ca..5a6aec9 100644 --- a/shell.nix +++ b/shell.nix @@ -10,7 +10,7 @@ foo = pkgs.mkShell { # nativeBuildInputs is usually what you want -- tools you need to run nativeBuildInputs = with pkgs.buildPackages; [ - + assimp ]; shellHooks = '' diff --git a/src/.init.lua b/src/.init.lua index 7616493..f0b687c 100644 --- a/src/.init.lua +++ b/src/.init.lua @@ -57,6 +57,7 @@ app.opts = require('config')(app) -- load system plugins (in specific order for admin panel) app.plugin.global = app.addPlugin('plugin/global') app.plugin.diskscan = app.addPlugin('plugin/diskscan') +app.plugin.convert = app.addPlugin('plugin/convert') app.plugin.moderation = app.addPlugin('plugin/moderation') app.plugin.xrfragments = app.addPlugin('plugin/xrfragments') app.plugin.janusxr = app.addPlugin('plugin/janusxr') diff --git a/src/css/xrforge.css b/src/css/xrforge.css index f30721e..e6e0e93 100644 --- a/src/css/xrforge.css +++ b/src/css/xrforge.css @@ -278,11 +278,10 @@ img.icon{ display:none; } padding:10px 20px; overflow:clip; } -.info .tag{ - margin-bottom:7px; -} b.tag{ + margin-bottom:7px; + line-height:22px; display:inline-block; background: #77f; color: white; diff --git a/src/img/button-88x31.gif b/src/img/button-88x31.gif new file mode 100644 index 0000000..e060d35 Binary files /dev/null and b/src/img/button-88x31.gif differ diff --git a/src/myverse/x3d/test_world.x3d b/src/myverse/x3d/test_world.x3d new file mode 100644 index 0000000..2861716 --- /dev/null +++ b/src/myverse/x3d/test_world.x3d @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/myverse/x3d/test_world.x3d.glb b/src/myverse/x3d/test_world.x3d.glb new file mode 100644 index 0000000..8097701 Binary files /dev/null and b/src/myverse/x3d/test_world.x3d.glb differ diff --git a/src/page/about.html b/src/page/about.html index 2d03b52..01be761 100644 --- a/src/page/about.html +++ b/src/page/about.html @@ -95,41 +95,22 @@ div#xrecosystem{

What is it

An open, seamless XR web built from simple URLs and directories, without gatekeepers.
- 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.
+ +

-
-
-
-    ┌───────────────────────────────────┐   ┌──────────────────────────────────────────┐
-    │           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!            │ │
-    │  └──────────────────────────────┘ │
-    └───────────────────────────────────┘ 
-
-    
-
-

Why people want XRForge

diff --git a/src/page/dir.html b/src/page/dir.html index 612994c..40238a3 100644 --- a/src/page/dir.html +++ b/src/page/dir.html @@ -37,6 +37,29 @@ 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([[ +
+ +
+
+ +
+ ]]) + end + end + end}
${footer} diff --git a/src/page/dir.lua b/src/page/dir.lua index fe7483d..132d2bd 100644 --- a/src/page/dir.lua +++ b/src/page/dir.lua @@ -16,7 +16,8 @@ return { if urlpath == "/" or urlpath:match("^"..rootpath) - and not urlpath:gsub(".*/",""):match("%.") then + and not urlpath:gsub(".*/",""):match("%.") + and not req.param.url then local redirect = false 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) if req.authenticated or app.plugin.moderation.isAllowed(v) then if v.parent then - --trace( "path = " .. util.stripPath(v.path) .. " " .. - -- "parent = " .. util.stripPath(v.parent.path) .. " " .. - -- "matchpath = " .. util.stripPath(matchpath) - --) + trace( "path = " .. util.stripPath(v.path) .. " " .. + "parent = " .. util.stripPath(v.parent.path) .. " " .. + "matchpath = " .. util.stripPath(matchpath) + ) if match(v.path, matchpath) then -- show parent folder if any local parent = app.cache.items[ v.parent.id ] if v.portal then + trace("-- portal redirect") redirect = v.portal.url end if match(parent.path, app.opts.plugin_diskscan_dir) then -- add rootfolder if needed @@ -41,15 +43,20 @@ return { end -- add items from dir - if match(v.parent.path, matchpath) and (v.dir or v.view3D) then - local parent = app.cache.items[ v.parent.id ] - res.items[ v.id ] = v - res.parent = parent + 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 ] + res.items[ v.id ] = v + res.parent = parent + end end end end end) + local data = util.merge({ + url = req.param.url, -- query hint to render janus.url in iframe (index.html) html_items='', items = {}, parent=res.parent @@ -66,7 +73,7 @@ return { local port = ':' .. GetPort() if port == 80 then port = '' end 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? data.janusxr = app.tpl( util.readfile(v.path), data ) end diff --git a/src/page/footer.html b/src/page/footer.html index 6699b77..ad6f19b 100644 --- a/src/page/footer.html +++ b/src/page/footer.html @@ -8,7 +8,6 @@

- diff --git a/src/page/janusxr.html b/src/page/janusxr.html index 99f5dfc..49fb8eb 100644 --- a/src/page/janusxr.html +++ b/src/page/janusxr.html @@ -9,7 +9,9 @@ end} {if items and #items > 0 then for i,v in pairs(items) do - print('\t') + if #v.thumbnail > 0 then + print('\t') + end end end} {if showfediportals then @@ -30,17 +32,20 @@ end} ]]) else 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 - print('') + print(' ') end} {if true then - function addPortal(id,x,v,url,i,z,thumbprefix,rotation,scale) - print('\t\t') - print('\t\t ') - print('\t\t ') - print('\t\t ') - print('\t\t') + function addPortal(id,x,v,url,i,z,thumb,rotation,scale) + print('\t') + print('\t ') + print('\t ') + print('\t ') + print('\t') end end} @@ -52,9 +57,13 @@ end} local width = 3.2 local center = -((#items*width)/1.3) + (i*width) 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.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} {if showfediportals and not item then @@ -74,7 +83,8 @@ end} for i,fediurl in pairs(portals) do local width = 3.2 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} diff --git a/src/plugin/convert.lua b/src/plugin/convert.lua new file mode 100644 index 0000000..32f7f08 --- /dev/null +++ b/src/plugin/convert.lua @@ -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 assimp', + opts = { + { + key='app.opts.plugin_convert_assimp_filetypes_in', + title='from filetype', + info='Space-separated 3D File extensions for assimp to process, lua patterns 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, lua patterns 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 = "
assimp detected 
" .. plugin.info .. '
Supported filetypes: ' + foreach( util.split( plugin.filetypes, ' '), function(k,ext) + plugin.info = plugin.info .. '' .. ext .. ' ' + end) +else + plugin.info = plugin.info .. "

NOTE: assimp is not installed on this system: please install." + +end + + +return plugin diff --git a/src/plugin/global.lua b/src/plugin/global.lua index 6808cbe..0bb6a58 100644 --- a/src/plugin/global.lua +++ b/src/plugin/global.lua @@ -14,6 +14,7 @@ end) local plugin = { name = "Global settings", info = '', + ext = {"mp3","wav","ogg","mp4","vtt","md","html","jml","jpg","png","svg"}, opts = { { key='app.opts.plugin_global_title', @@ -66,7 +67,7 @@ local plugin = { }, middleware = { 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' else res.template = 'page/dir.html' diff --git a/src/plugin/janusxr.lua b/src/plugin/janusxr.lua index c9250c2..48ae3e8 100644 --- a/src/plugin/janusxr.lua +++ b/src/plugin/janusxr.lua @@ -8,7 +8,27 @@ app.on('scan_experience', function(exp) end) app.on('scan_experience_file', function(exp) - local size = exp.file.attr:size() + local me = app.plugin.janusxr + 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 local data = util.readfile( exp.file.fullpath) 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.cache.items[ exp.file.ino ] = exp exp.view3D = true - exp.janusxr = true + exp.janusxr = {} print("✅ └─ plugin/janusxr: " .. exp.file.name) end else @@ -34,9 +54,14 @@ app.on('set', function(k,v) end) -- 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 = { name = "JanusXR", info = 'JanusXR connects XR experiences via XML portals (+AFRAME (geometry)markup also supported).', + ext = { + object = {'glb', 'gltf', 'obj', 'fbx', 'ply', 'stl', 'wrl', 'gltf', 'glb', 'bvh' } + }, opts = { { key='app.opts.plugin_janusxr_janusweb_endpoint', @@ -52,33 +77,33 @@ local plugin = { default="https://p.janusxr.org https://cors.xrforge.org", values='' }, + { + key='app.opts.plugin_janusxr_wrap_3dfile', + title='wrap 3D file', + info='render 3D files in janusxr.xml room', + default="room_plane", + values=util.merge(rooms,{disabled="-- disabled --"}) + }, { key='app.opts.plugin_janusxr_wrap_image', title='wrap images', - info='Generate janusxr.xml to embed images into an XR experience', - default="yes", - values={yes="yes",no="no"} + info='render images in janusxr.xml room', + default="room_plane", + values=util.merge(rooms,{disabled="-- disabled --"}) }, { key='app.opts.plugin_janusxr_wrap_video', title='wrap videos', - info='Generate janusxr.xml to embed videos into an XR experience', - default="yes", - values={yes="yes",no="no"} + info='render videos in janusxr.xml room', + default="room_plane", + values=util.merge(rooms,{disabled="-- disabled --"}) }, { key='app.opts.plugin_janusxr_wrap_aframe', title='wrap AFRAME', - info='Generate janusxr.xml to embed AFRAME markup into an XR experience', - default="yes", - values={yes="yes",no="no"} - }, - { - 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"} + info='render AFRAME markup in janusxr.xml room', + default="room_plane", + values=util.merge(rooms,{disabled="-- disabled --"}) }, { key='app.opts.plugin_janusxr_portalroom', @@ -124,5 +149,11 @@ local plugin = { return room end } + +plugin.info = plugin.info .. "

supported 3D files: " +foreach( plugin.ext.object, function(k,ext) + plugin.info = plugin.info .. '' .. ext .. ' ' +end) +plugin.info = plugin.info .. '+ JML XML-markup in any text/binary file' return plugin diff --git a/src/plugin/xrfragments.lua b/src/plugin/xrfragments.lua index bebc82c..5dcf2a3 100644 --- a/src/plugin/xrfragments.lua +++ b/src/plugin/xrfragments.lua @@ -10,7 +10,6 @@ app.on('scan_experience_file', function(exp) local me = app.plugin.xrfragments foreach( util.split( app.opts.plugin_xrfragments_filetypes, ' '), function(k,ext) if exp.path:lower():match("."..ext.."$") then - exp.view3D = true local hasHref = false if ext == 'glb' then -- 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 exp.tag.xrfragments = { qualify = hasHref or hasPNG or hasWebVTT or hasOGG or hasJSONref, href = hasHref } if exp.tag.xrfragments.qualify then + exp.view3D = true print("✅ └─ plugin/xrfragments: detected xrfragments experience") end if exp.tag.xrfragments.href then @@ -47,6 +47,7 @@ app.on('scan_experience_file', function(exp) end) -- finally insert if viewable if exp.view3D then + print("✅ └─ plugin/xrfragments: marking as 3D viewable") app.cache.items[ exp.file.ino ] = exp end end