Compare commits

..

No commits in common. "dev" and "master" have entirely different histories.
dev ... master

26 changed files with 756 additions and 993 deletions

View file

@ -1,13 +1,16 @@
-- default argument
if arg[1] == nil then
arg[1] = "https://snips.sh/f/_U5-XctEVE?r=1"
end
-- sync lovr/love positional cli arguments
if arg[0] == nil and arg[1] ~= nil then arg[0] = arg[1] end
package.path = package.path .. ';' .. '?.lua;' .. 'lib/?.lua'
package.path = package.path .. ';' ..
'?.lua;' ..
'?/init.lua;' ..
'lib/?.lua;'
local runtime = require("runtime/detect")
package.path = package.path .. ';' ..
arg[0] .. '/' .. runtime.path .. '/?.lua;' ..
arg[0] .. '/' .. runtime.path .. '/?/init.lua;' ..
arg[0] .. '/lib/?.lua'
require( runtime.path .. "/conf")

View file

@ -1,4 +1,4 @@
local ecs = require("lib/tiny-ecs")
local ecs = require("tiny-ecs")
ecs.init = function(api)
print("[i] loading ecs")
@ -30,14 +30,4 @@ ecs.clear = function()
api.world.commit()
end
ecs.extension = {
name = "ecs",
enabled = true,
index = 300,
init = function(api) ecs.init(api) end,
draw = function(pass)
ecs.update( api.world, pass, ecs.filterDraw )
end
}
return ecs

View file

@ -14,25 +14,14 @@ return {
obj.root = false
if obj.URI.target == '_top' then
obj.root = true
if obj.URI.protocol ~= 'xrf' then -- xrf:// should not update toplevel URI
api.ext.URI.current = obj.URL -- set current url
end
end
if type(obj.URI.target) == 'table' and obj.URI.target.obj ~= nil then
local parent = obj.URI.target.obj
-- TODO: the runtime/lovr/render/model code should calculate parent offset
obj.x = parent.x
obj.y = parent.y
obj.z = parent.z
obj.scale = parent.scale
else
obj.x = obj.x or 0
obj.y = obj.y or 0
obj.z = obj.z or 0
obj.scale = obj.scale or 1
api.ext.URI.current = obj.URL -- set current url
end
obj.x = obj.x or 0
obj.y = obj.y or 0
obj.z = obj.z or 0
obj.scale = obj.scale or 1
if obj.URL.protocol == 'file' then
obj.model = api.graphics.newModel( obj.URL.file )
obj.model = api.graphics.newModel( obj.URL.string )
else
obj.model = api.graphics.newModel( api.data.newBlob( obj.URLResponse.data) )
end

View file

@ -1,106 +0,0 @@
local api = ...
local ecs = api.ecs
local gctl
gctl = {
name = "gazecontrol",
enabled = (lovr ~= nil),
index = api.ext.max * 0.9, -- important: render last! (renderOrder)
texture = {},
material = nil,
dtmax = 1,
dt = 0,
visor = {
rotate = { x = 0, y = 0, z = 0},
scaleTo = 0.1,
scale = 0.1,
scaleSpeed = 0.005,
walkingSpeed = 2,
texture = false,
textures = {}
},
init = function()
--lovr.mouse.setRelativeMode(true)
gctl.initVisor()
gctl.extendECS()
api.player.matrix = lovr.math.newMat4()
end,
initVisor = function()
local visor = gctl.visor
visor.textures.idle = lovr.graphics.newTexture("ext/gazecontrol/reticle_idle.png")
visor.textures.hover = lovr.graphics.newTexture("ext/gazecontrol/reticle_hover.png")
visor.texture = visor.textures.idle
end,
update = function(dt, input)
local visor = gctl.visor
if input.pointer.hover then
visor.texture = visor.textures.hover
if visor.scale == visor.scaleTo then
visor.scale = 0.05
end
else
visor.texture = visor.textures.idle
visor.scale = visor.scaleTo
end
if input.pointer.wheel then -- move forward/backward via scrollwheel
print( input.pointer.wheel.y)
local direction = vector(lovr.headset.getDirection('head'))
if not api.player.flying then direction = vector(direction.x, 0, direction.z) end
-- move forward/backward via scrollwheel
api.player.matrix:translate(direction * (input.pointer.wheel.y * visor.walkingSpeed) )
end
end,
draw = function(pass,dt)
-- *TODO* render this last (as it needs to be rendered after skybox & floor)
local pos = vector( lovr.headset.getPosition('head') ) + vector( api.player.matrix:getPosition() )
local dirx, diry, dirz = lovr.headset.getDirection('head')
local distance = 1
local x = pos.x + distance * dirx
local y = pos.y + distance * diry
local z = pos.z + distance * dirz
local angle, ax, ay, az = lovr.headset.getOrientation(device)
local visor = gctl.visor
local rot = visor.rotate
local scale = visor.scale
if scale < visor.scaleTo then
scale = scale + visor.scaleSpeed
visor.scale = scale
end
pass:setMaterial(visor.texture)
pass:setColor(1, 1, 1, 1)
pass:setCullMode('back')
--pass:setFaceCull()
--pass:setDepthWrite(true)
pass:setBlendMode("alpha", "alphamultiply")
--pass:setColorWrite(true)
pass:plane(x, y, z, scale, scale, angle, ax, ay, az)
end,
-- ECS API docs: https://bakpakin.github.io/tiny-ecs/doc
extendECS = function()
local visorSystem = ecs.processingSystem({
updatethread = true,
filter = ecs.requireAll('mycomponent'),
onAdd = function(self,obj) print_r(obj) end,
onRemove = function(self,obj) end,
process = function(self,obj) end,
})
ecs.addSystem( api.world, visorSystem )
ecs.add( api.world, { visor = gctl.visor } )
--gctl.visor.commit() -- notify other systems to update entitycache
end
}
return gctl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -7,27 +7,18 @@ return {
enabled = true,
onURI = function(obj)
if obj.URL ~= nil and (obj.URLResponse ~= nil or obj.URL.protocol == 'file') then
if obj.URL.protocol == 'file' and obj.URLResponse.ok == nil then
local data = util.readfile(obj.URL.file)
obj.URLResponse = { data = data, ok = (data ~= nil) }
end
if obj.URLResponse.ok then
local res = obj.URLResponse
-- JML heuristic
if res.data:lower():match("<fireboxroom>") or res.data:lower():match("<room[ >]") then
local JML0
local JML
res.xml = api.parser.xml.newParser()
local xmlstr = res.data
-- cleanup JML0
local JML0 = xmlstr:gsub(".*<[Rr]oom","<room")
JML0 = JML0:gsub("</[Rr]oom>.*","</room>")
api.ext.janusxr.loadXML( JML0, res, obj )
end
else
print("[janusxr] error: could not load " .. obj.URI.url .. ": ")
print_r(obj.URLResponse)
if obj.URL ~= nil and obj.URLResponse ~= nil and obj.URLResponse.ok then
local res = obj.URLResponse
-- JML heuristic
if res.data:lower():match("<fireboxroom>") or res.data:lower():match("<room[ >]") then
local JML0
local JML
res.xml = api.parser.xml.newParser()
local xmlstr = res.data
-- cleanup JML0
local JML0 = xmlstr:gsub(".*<[Rr]oom","<room")
JML0 = JML0:gsub("</[Rr]oom>.*","</room>")
api.ext.janusxr.loadXML( JML0, res, obj )
end
end
end,

View file

@ -9,9 +9,21 @@ return {
--api.ext.skeleton.extendECS()
end,
update = function(dt,input) end,
load = function() end,
draw = function(pass,dt) end,
update = function()
local iui = api.iui
local backend = api.backend
local mainWindow = api.mainWindow
end,
load = function() end,
draw = function(pass)
if api.iui.idiom == "vr" then
else
end
end,
-- ECS API docs: https://bakpakin.github.io/tiny-ecs/doc
extendECS = function()

View file

@ -5,7 +5,6 @@ local shader
return {
name = "startupscene",
enabled = (lovr ~= nil),
index = api.ext.max * 0.1,
init = function() end,
@ -46,30 +45,25 @@ return {
-- skybox
pass:setCullMode('back')
pass:setBlendMode()
pass:setShader()
pass:setColor(1,1,1,1)
pass:skybox(api.world.skybox)
pass:setColor(1, 1, 1)
pass:setMaterial(envTex)
pass:setDepthWrite(false)
pass:draw(envTex, 0, 0, 0, 512, math.pi * 0.5, 1, 0, 0)
-- floor
pass:setMaterial()
pass:setDepthWrite(true)
pass:setShader()
--pass:setCullMode('front')
--pass:setBlendMode('alpha')
--pass:setMaterial()
pass:setCullMode('front')
pass:setBlendMode('alpha')
pass:setMaterial()
pass:setColor(0.8,0.8,0.8,1)
pass:plane(0, 0, 0, 500, 500, math.pi * 0.5, 1, 0, 0, "fill", 5, 5)
pass:setColor( 0, 0, 0, 0.75 )
pass:plane(0, 0.01, 0, 500, 500, math.pi * 0.5, 1, 0, 0, "line", 100, 100)
--pass:setShader( lovr.shader.pbr )
--pass:send('cubemap', api.world.environmentMap)
--pass:send('sphericalHarmonics', api.world.sphericalHarmonics)
pass:setShader( lovr.shader.pbr )
pass:send('cubemap', api.world.environmentMap)
pass:send('sphericalHarmonics', api.world.sphericalHarmonics)
end
end

View file

@ -84,7 +84,7 @@ unixy = {
stderr_file = stderr_file,
callback = callback
})
trace("[unixy] start job with pid " .. pid .. ": " .. cmd)
trace("[unixy] start job with pid " .. pid .. " => 1> " .. stdout_file .. " 2> " .. stderr_file)
end,
check_jobs = function()

View file

@ -0,0 +1,386 @@
--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

View file

@ -107,7 +107,7 @@ xrf.makeClickable = function( physicsWorld, cb)
scale[3] * (d + 0.01) -- planes e.g.
)
print("making node " .. node['name'] .. " clickable: " .. node.extras.href )
collider:setUserData( {name = node['name'], node = node, model = model, mesh = mesh, onclick = cb, obj = obj })
collider:setUserData( {name = node['name'], node = node, model = model, mesh = mesh, onclick = cb })
end
end

View file

@ -11,7 +11,6 @@ xrfimpl = {
init = function() end,
on3DFile = function(obj)
local referer = obj.URI.url
if lovr ~= nil then
xrf.traverseNodesContaining('href', obj,
xrf.makeClickable( api.ecs.worldPhysics,
@ -19,9 +18,10 @@ xrfimpl = {
local href = obj.node.extras.href
trace("\n[xrf] href was clicked: " .. href)
api.ext.exec("onClick", obj, collider)
local ok = xrf.level2.load( href, obj, api.ext.URI.current, xrfimpl.onLoad)
or
xrf.level4.import( href, obj, referer, xrfimpl.onImport)
xrf.level4.import( href, obj, api.ext.URI.current, xrfimpl.onImport)
end
)
@ -51,12 +51,10 @@ xrfimpl = {
local filter = function(o)
return (o.URL ~= nil and o.URL.URN:gsub("#.*","") == absoluteHref:gsub("#.*",""))
end
local newobj = api.world.get(filter)
if newobj then
if newobj.URL.audio ~= nil then
newobj.URL = url.parse(absoluteHref) -- re-parse to detect media fragment change
xrfimpl.onAudioFile(obj)
end
local obj = api.world.get(filter)
if obj then
obj.URL = url.parse(absoluteHref) -- re-parse to detect media fragment change
xrfimpl.onAudioFile(obj)
else
api.ecs.add( api.world, { URI = { url = absoluteHref, method = 'GET', target = obj } })
end

View file

@ -0,0 +1,166 @@
-- 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
-- below is a lua adaptation of Godot's URI.gd
-- https://gist.github.com/coderofsalvation/b2b111a2631fbdc8e76d6cab3bea8f17
local url = {}
-- Helper function to split a string by a delimiter
local function split(str, delimiter)
local result = {}
if delimiter == "" then return {str} end
local pattern = string.format("([^%s]+)", delimiter)
for match in string.gmatch(str, pattern) do
table.insert(result, match)
end
return result
end
-- Helper for guess_type (converts numbers/booleans from strings)
local function guess_type(val)
if tonumber(val) then return tonumber(val) end
if val == "true" then return true end
if val == "false" then return false end
return val
end
-- Helper to parse query parameters and fragments
local function parseArgs(fragment)
local ARG = {}
if not fragment or fragment == "" then return ARG end
local items = split(fragment, "&")
for _, item in ipairs(items) do
local key_value = split(item, "=")
if #key_value > 1 then
local k = key_value[1]
ARG[k] = guess_type(key_value[2]) -- xr fragment operators preserved
ARG[k:gsub("[^%w]","") ] = guess_type(key_value[2]) -- alphanumeric only
elseif #key_value == 1 then
ARG[key_value[1]] = ""
end
end
return ARG
end
-- Main URL Parser function attached to the module object
function url.parse(url_string)
if type(url_string) == 'table' then return url_string end -- already parsed
-- Setup initial URI table structure
local URI = {
domain = "",
fragment = {},
file = "",
extension = "",
string = url_string,
protocol = "",
path = "",
query = {},
hash = "",
URN = "",
isLocal = false
}
local remaining = url_string
local protocol = string.match(remaining, "^(%w+://)") -- protocol
if protocol then
URI.protocol = protocol:gsub("://", "")
remaining = remaining:sub(#protocol + 1)
else
URI.protocol = "file"
end
local hash = string.match(remaining, "(#.*)$") -- fragment
if hash then
URI.hash = hash
remaining = remaining:sub(1, #remaining - #hash)
end
local query = string.match(remaining, "(%?[^#]+)$")
if query then
URI.query = query
remaining = remaining:sub(1, #remaining - #query)
end
URI.path = remaining
-- Process Path, Domain, and File
if URI.path and URI.path ~= "" then
local pathParts = split(URI.path, "/")
if #pathParts > 1 then
local firstPart = pathParts[1]
-- Check if the first part looks like a domain (contains '.' or ':')
if string.find(firstPart, "%.") or string.find(firstPart, ":") then
URI.domain = table.remove(pathParts, 1)
end
end
-- Reconstruct path
URI.path = table.concat(pathParts, "/")
-- Extract file if present
if #pathParts > 0 then
local lastPart = pathParts[#pathParts]
if string.find(lastPart, "%.") then
URI.file = lastPart
URI.extension = URI.file:match("%.([^%.]+)$"):upper()
end
end
end
if URI.hash and URI.hash ~= "" then
URI.fragment = parseArgs(URI.hash:sub(2)) -- sub(2) skips the '#'
else
URI.fragment = {}
end
if URI.query and type(URI.query) == "string" and URI.query ~= "" then
URI.query = parseArgs(URI.query:sub(2)) -- sub(2) skips the '?'
else
URI.query = {}
end
if URI.domain ~= "" then
URI.URN = URI.string:gsub("%?.*", "")
else
URI.URN = ""
end
URI.isLocal = (URI.domain == "")
return URI
end
url.getAbsolute = function(href,referer,keephash)
local URL = url.parse(href)
local URLref = url.parse(referer)
if referer ~= nil and URL.protocol == 'file' and URLref.protocol ~= 'file' then
URL.protocol = URLref.protocol
URL.domain = URLref.domain
URL.string = URLref.protocol .. '://' .. URLref.domain .. '/' .. URL.path .. URL.hash
if keephash then
URL.URN = URL.string
else
URL.URN = URL.string:gsub("#.*", "")
end
end
return URL
end
-- Return the module table so it can be assigned via require()
return url

View file

@ -1,386 +0,0 @@
--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

1
xurfer/lib/json.lua Symbolic link
View file

@ -0,0 +1 @@
../ext/xrfragments/json.lua

View file

@ -695,9 +695,7 @@ function tiny_manageEntities(world)
system.modified = true
local tmpEntity = ses[#ses]
ses[index] = tmpEntity
if tmpEntity ~= nil then
seis[tmpEntity] = index
end
seis[tmpEntity] = index
seis[entity] = nil
ses[#ses] = nil
local onRemove = system.onRemove

View file

@ -1,167 +0,0 @@
-- 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
-- below is a lua adaptation of Godot's URI.gd
-- https://gist.github.com/coderofsalvation/b2b111a2631fbdc8e76d6cab3bea8f17
local url = {}
-- Helper function to split a string by a delimiter
local function split(str, delimiter)
local result = {}
if delimiter == "" then return {str} end
local pattern = string.format("([^%s]+)", delimiter)
for match in string.gmatch(str, pattern) do
table.insert(result, match)
end
return result
end
-- Helper for guess_type (converts numbers/booleans from strings)
local function guess_type(val)
if tonumber(val) then return tonumber(val) end
if val == "true" then return true end
if val == "false" then return false end
return val
end
-- Helper to parse query parameters and fragments
local function parseArgs(fragment)
local ARG = {}
if not fragment or fragment == "" then return ARG end
local items = split(fragment, "&")
for _, item in ipairs(items) do
local key_value = split(item, "=")
if #key_value > 1 then
local k = key_value[1]
ARG[k] = guess_type(key_value[2]) -- xr fragment operators preserved
ARG[k:gsub("[^%w]","") ] = guess_type(key_value[2]) -- alphanumeric only
elseif #key_value == 1 then
ARG[key_value[1]] = ""
end
end
return ARG
end
-- Main URL Parser function attached to the module object
function url.parse(url_string)
if type(url_string) == 'table' then return url_string end -- already parsed
-- Setup initial URI table structure
local URI = {
domain = "",
fragment = {},
file = "",
extension = "",
string = url_string,
protocol = "",
path = "",
query = {},
hash = "",
URN = "",
isLocal = false
}
local remaining = url_string
local protocol = string.match(remaining, "^(%w+://)") -- protocol
if protocol then
URI.protocol = protocol:gsub("://", "")
remaining = remaining:sub(#protocol + 1)
else
URI.protocol = "file"
end
local hash = string.match(remaining, "(#.*)$") -- fragment
if hash then
URI.hash = hash
remaining = remaining:sub(1, #remaining - #hash)
end
local query = string.match(remaining, "(%?[^#]+)$")
if query then
URI.query = query
remaining = remaining:sub(1, #remaining - #query)
end
URI.path = remaining
-- Process Path, Domain, and File
if URI.path and URI.path ~= "" then
local pathParts = split(URI.path, "/")
if #pathParts > 1 then
local firstPart = pathParts[1]
-- Check if the first part looks like a domain (contains '.' or ':')
if string.find(firstPart, "%.") or string.find(firstPart, ":") then
URI.domain = table.remove(pathParts, 1)
end
end
-- Reconstruct path
URI.path = table.concat(pathParts, "/")
-- Extract file if present
if #pathParts > 0 then
local lastPart = pathParts[#pathParts]
if string.find(lastPart, "%.") then
URI.file = lastPart
URI.extension = URI.file:match("%.([^%.]+)$"):upper()
end
end
end
if URI.hash and URI.hash ~= "" then
URI.fragment = parseArgs(URI.hash:sub(2)) -- sub(2) skips the '#'
else
URI.fragment = {}
end
if URI.query and type(URI.query) == "string" and URI.query ~= "" then
URI.query = parseArgs(URI.query:sub(2)) -- sub(2) skips the '?'
else
URI.query = {}
end
if URI.domain ~= "" then
URI.URN = URI.string:gsub("%?.*", "")
else
URI.URN = ""
end
URI.isLocal = (URI.domain == "")
return URI
end
url.getAbsolute = function(href,referer,keephash)
local URL = url.parse(href)
local URLref = url.parse(referer)
if referer ~= nil and URL.protocol == 'file' and URLref.protocol ~= 'file' then
URL.protocol = URLref.protocol or ''
URL.domain = URLref.domain or ''
URL.string = URL.protocol .. '://' .. URL.domain .. '/' .. URL.path .. URL.hash
if keephash then
URL.URN = URL.string
else
URL.URN = URL.string:gsub("#.*", "")
end
end
URL.referer = referer
return URL
end
-- Return the module table so it can be assigned via require()
return url

1
xurfer/lib/url.lua Symbolic link
View file

@ -0,0 +1 @@
../ext/xrfragments/url.lua

View file

@ -167,8 +167,3 @@ function newNode(name)
return node
end
return {
newNode = newNode,
newParser = newParser
}

View file

@ -9,16 +9,16 @@
-- ./lovr xurfer https://janusxr.org/index.html'
-- ./lovr xurfer https://snips.sh/f/rHFLg-cewi?r=1' -- cube
-- ./lovr xurfer https://snips.sh/f/_U5-XctEVE?r=1' -- cube, monkey, scene
--
print("\n X U R F Ξ R")
print(" ⠭ ⠥ ⠗ ⠋ ⠑ ⠗\n")
package.path = package.path .. ';' .. '?.lua'
package.path = package.path .. ';' .. '?.lua;'
require('conf')
local runtime = require("runtime/detect")
local util = require("util")
local ecs = require("ecs")
local ecs = require("ecs")
api = {
runtime = runtime,
@ -31,22 +31,21 @@ api = {
ecs = ecs,
world = ecs.world(),
protocol = {},
player = { x = 0, y = 0, z = 0, ox = 0, oy = 1, oz = 0, oangle = 0, flying = true },
ext = { -- all extensions are loaded here from disk at runtime
ecs = ecs.extension,
max = 10000, -- max plugins (used for sorting)
exec = function(...) util.exec(api.ext,...) end -- util function to call func on each extension
},
i = 1
}
}
api = util.merge( api, runtime.api ) -- overlay api onto lovr/love-compatible runtime api
util.loaddir( "ext", api, api.ext )
require( runtime.path .. "/main")
api.ext.exec('init', api)
util.loaddir( "ext", api, api.ext )
util.loaddir( "media", api, api.media )
ecs.init(api)
api.ext.exec('init')
-- load urls passed on the cli
foreach( arg, function(k,uri)
if uri:find(".") and k ~= 0 and k ~= -1 then

View file

@ -1,31 +1,5 @@
local runtime = require("runtime/detect")
-- sync lovr/love positional cli arguments
if arg[0] == nil and arg[1] ~= nil then arg[0] = arg[1] end
local runtime = require("runtime/detect")
-- setup path
print("!!!!!!!")
package.path = package.path ..
'runtime/lovr/?.lua;' ..
'runtime/lovr/?/init.lua;' ..
'lib/?.lua;' ..
'?.lua'
--arg[0] .. 'runtime/lovr/?.lua;' ..
--arg[0] .. 'runtime/lovr/?/init.lua;' ..
--arg[0] .. 'lib/?.lua;' ..
lovr.filesystem.setRequirePath(
lovr.filesystem.getRequirePath() .. ";" ..
'runtime/lovr/?.lua;' ..
'runtime/lovr/?/init.lua;' ..
'lib/?.lua;' ..
'?.lua'
)
print(lovr.filesystem.getRequirePath())
local util = require("util")
local launch = require "runtime/lovr/launch"
local launch = require "launch"
local function isVR()
return launch.mode == "vr" or util.match(arg,"--desktop") == false

View file

@ -5,8 +5,6 @@ end
local util = require "util"
local launch = require "launch"
require('shaders').create(api)
local iui = require "lib.iui"
local backend = require "lib.lovr-iui"
@ -28,30 +26,9 @@ local ecs = api.ecs
ecs.filterDraw = ecs.requireAll('drawthread')
ecs.filterUpdate = ecs.requireAll('updatethread')
-- sort drawcalls of extensions
api.ext.lovr = {
drawcalls = {},
enabled = true,
name = "lovr",
index = api.ext.max-1,
init = function()
api.ext.lovr.sortDrawCalls()
end,
sortDrawCalls = function()
local drawcalls = api.ext.lovr.drawcalls
foreach( api.ext, function(k,v)
if type(v) == 'table' and v.draw ~= nil then
table.insert(drawcalls, v)
end
end)
table.sort( drawcalls, function(a,b) return a.index < b.index end)
end
}
-- ecs systems
local interactions
local interactionsUpdater
--- @type Texture
local envTex
@ -61,7 +38,8 @@ local mainWindow
function lovr.load()
if launch.mode == "desktop" then
--backend.mouse = require "lib.lovr-mouse"
backend.mouse = require "lib.lovr-mouse"
lovr.system.setKeyRepeat(true)
end
@ -70,10 +48,7 @@ function lovr.load()
api.ext.exec("load")
if iui.idiom == "vr" then
local modes = lovr.headset.getPassthroughModes()
local mode = "opaque"
if modes.blend then mode = "blend" end
lovr.headset.setPassthrough( mode )
lovr.headset.setPassthrough("opaque")
mainWindow = backend.worldWindow.new()
api.mainWindow = mainWindow
@ -89,7 +64,7 @@ function lovr.update(dt)
-- In desktop mode, we use IUI's standard window API to fill the screen.
iui.beginWindow(lovr.system.getWindowDimensions())
api.ext.exec("update", dt, interactions.event )
api.ext.exec("update", dt)
iui.endWindow()
@ -99,7 +74,7 @@ function lovr.update(dt)
if mainWindow:beginFrame() then
iui.beginWindow(mainWindow.w, mainWindow.h)
api.ext.exec("update", dt, interactions.event )
api.ext.exec("update", dt)
iui.endWindow()
@ -111,152 +86,120 @@ function lovr.update(dt)
end
function lovr.draw(pass)
--api.player.matrix:setPosition( api.player.x, api.player.y, api.player.z )
--api.player.matrix.setOrientation( api.player.x, api.player.y, api.player.z )
pass:transform(mat4(api.player.matrix):invert())
if iui.idiom == "desktop" then
backend.graphics.pass = pass
iui.draw()
end
if api.iui.idiom == "vr" then
pass:setClear(0.2, 0.2, 0.2)
pass:setClear(0.5, 0.5, 0.5)
end
-- call draw() function of extensions (sorted by .index)
foreach( api.ext.lovr.drawcalls, function(k,ext) ext.draw(pass) end)
api.ext.exec("draw",pass)
ecs.update( api.world, pass, ecs.filterDraw )
-- Dot
if interactionsUpdater.selectedBox then
pass:setColor(0, 0, 1)
pass:sphere( interactionsUpdater.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, interactionsUpdater.selectedBox and interactionsUpdater.hitpoint or (hand + direction * 50))
-- -- Dot
--if interactions.selectedBox then
-- pass:setColor(0, 0, 1)
-- pass:sphere( interactions.hitpoint, .01)
--end
--
---- Laser pointers
--local hands = {"left","right"}
--foreach( hands, function(k,side)
-- local hand = vec3(lovr.headset.getPosition('hand/' .. side .. '/point'))
-- local direction = quat(lovr.headset.getOrientation('hand/' .. side .. '/point')):direction()
-- pass:setColor(1, 1, 1)
-- pass:line(hand, interactions.selectedBox and interactions.hitpoint or (hand + direction * 50))
--end)
--if api.mainWindow then
-- api.mainWindow:draw(pass)
--end
if api.mainWindow then
api.mainWindow:draw(pass)
end
return false
end
function lovr.recenter()
if iui.idiom == "vr" and lovr.headset.isActive() then
if iui.idiom == "vr" then
mainWindow:recenter()
end
end
function lovr.mousemoved(x, y, dx, dy)
backend.mousemoved(x, y, dx, dy)
interactions.pointer.moved = {x=x, y=y}
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}
end
function lovr.mousepressed(x, y, button)
backend.mousepressed(x, y, button)
interactions.pointer.pressed = {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)
interactions.pointer.released = {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("mouserelease")
end
function lovr.wheelmoved(x, y)
backend.wheelmoved(x, y)
interactions.pointer.wheel = {x=x, y=y}
print_r(interactions.pointer.wheel)
end
function lovr.wheelmoved(x, y)
backend.wheelmoved(x, y)
interactionsUpdater.mouse.wheel = {x=x, y=y}
end
function lovr.keypressed(key, scancode, isRepeat)
backend.keypressed(key, scancode, isRepeat)
interactions.key.pressed = {key=key,scancode=scancode,isRepeat=isRepeat}
end
function lovr.keypressed(key, scancode, isRepeat)
backend.keypressed(key, scancode, isRepeat)
interactionsUpdater.key.pressed = {key=key,scancode=scancode,isRepeat=isRepeat}
end
function lovr.keyreleased(key, scancode)
backend.keyreleased(key, scancode)
interactions.key.released = {key=key,scancode=scancode}
end
function lovr.keyreleased(key, scancode)
backend.keyreleased(key, scancode)
interactionsUpdater.key.released = {key=key,scancode=scancode}
end
function lovr.textinput(text)
backend.textinput(text)
interactions.input = text
function lovr.textinput(text)
backend.textinput(text)
interactionsUpdater.input = text
end
end
function initInteractions(ecs)
ecs.worldPhysics = lovr.physics.newWorld(0, 0, 0)
interactions = ecs.processingSystem()
interactions.updatethread = true
interactions.filter = function(obj) return true end -- always run
interactionsUpdater = ecs.processingSystem()
interactionsUpdater.updatethread = true
interactionsUpdater.filter = function(obj) return true end -- always run
-- collider vars
interactions.pointer = { released = false, pressed = false, moved = false, hover = false, wheel = false}
interactions.key = { released = false, pressed = false }
interactions.input = ""
interactions.hitpoint = vector()
interactions.selectedBox = nil
interactions.clearTable = function(o) foreach( o, function(k,v) o[k] = false end ) end
interactions.clear = function()
interactions.clearTable( interactions.pointer )
interactions.clearTable( interactions.key )
interactions.input = false
end
interactionsUpdater.mouse = { released = false, pressed = false, moved = false, wheel = false}
interactionsUpdater.key = { released = false, pressed = false }
interactionsUpdater.input = ""
interactionsUpdater.hitpoint = lovr.math.newVec3()
interactionsUpdater.selectedBox = nil
interactionsUpdater.clear = function(o) foreach( o, function(k,v) o[k] = false end ) end
function interactions:process(obj, dt)
function interactionsUpdater:process(obj, dt)
ecs.worldPhysics:update(dt)
local castsrc = 'hand/left/point'
local castsrc = 'head'
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)
local ox, oy, oz = lovr.headset.getPosition(castsrc)
local direction = quat(lovr.headset.getOrientation(castsrc)):direction()
if direction ~= nil then
local dx, dy, dz = vector( direction * 50 ):unpack()
local collider, shape, x, y, z = ecs.worldPhysics:raycast(ox, oy, oz, ox + dx, oy + dy, oz + dz)
if collider then
interactions.pointer.hover = true
local node = collider:getUserData()
interactions.selectedBox = collider
interactions.hitpoint.x = x
interactions.hitpoint.y = y
interactions.hitpoint.z = z
for i, hand in ipairs(lovr.headset.getHands()) do
if lovr.headset.isDown(hand, 'trigger') then
interactions.pointer.pressed = true
end
if lovr.headset.wasReleased(hand, 'trigger') then
interactions.pointer.released = true
end
if collider then
for i, hand in ipairs(lovr.headset.getHands()) do
if lovr.headset.isDown(hand, 'trigger') then
print("buttondown")
end
if interactions.pointer.released and node.onclick ~= nil then
node.onclick(node,collider)
if lovr.headset.wasReleased(hand, 'trigger') or interactionsUpdater['mouse']['released'] then
local node = collider:getUserData()
interactionsUpdater.selectedBox = collider
interactionsUpdater.hitpoint:set(x, y, z)
if node.onclick ~= nil then node.onclick(node,collider) end
end
end
end
-- backup & reset stuff
interactions.event = {
input = util.clone(interactions.input),
pointer = util.clone(interactions.pointer),
key = util.clone(interactions.key),
hitpoint = { x = interactions.hitpoint.x, y = interactions.hitpoint.y, z = interactions.z },
--head = { x = ox, y = oy, z = oz, direction = direction},
collider = collider
}
-- clear interactions
interactions.clear()
-- reset stuff
interactionsUpdater.clear( interactionsUpdater.mouse )
interactionsUpdater.clear( interactionsUpdater.key )
interactionsUpdater.input = false
end
ecs.addSystem( api.world, interactions )
ecs.addSystem( api.world, interactionsUpdater )
end
function initCleanups(ecs)

View file

@ -7,6 +7,8 @@ modelRenderer = {
init = function(api)
local ecs = api.ecs
api.world.shader = { pbr = false }
lovr.shader = lovr.shader or {}
lovr.shader['pbr'] = modelRenderer.initShaderPBR()
renderer = ecs.processingSystem()
renderer.filter = ecs.requireAll('model')
@ -34,6 +36,38 @@ modelRenderer = {
end
end
ecs.addSystem( api.world, renderer )
end,
initShaderPBR = function()
return lovr.graphics.newShader([[
vec4 lovrmain() {
return DefaultPosition;
}
]], [[
uniform textureCube cubemap;
uniform sphericalHarmonics { vec3 sh[9]; };
vec4 lovrmain() {
Surface surface;
initSurface(surface);
vec3 color = vec3(0);
vec3 lightDirection = vec3(-1, -1, -1);
vec4 lightColorAndBrightness = vec4(1, 1, 1, 3);
float visibility = 1.;
color += getLighting(surface, lightDirection, lightColorAndBrightness, visibility);
color += getIndirectLighting(surface, cubemap, sh);
return vec4(color, 1);
}
]], {
flags = {
glow = true,
normalMap = true,
vertexTangents = false, -- DamagedHelmet doesn't have vertex tangents
tonemap = true
}
})
end
}

View file

@ -1,34 +0,0 @@
return {
create = function(api)
lovr.shader = {}
lovr.shader.pbr = lovr.graphics.newShader([[
vec4 lovrmain() {
return DefaultPosition;
}
]], [[
uniform textureCube cubemap;
uniform sphericalHarmonics { vec3 sh[9]; };
vec4 lovrmain() {
Surface surface;
initSurface(surface);
vec3 color = vec3(0);
vec3 lightDirection = vec3(-1, -1, -1);
vec4 lightColorAndBrightness = vec4(1, 1, 1, 3);
float visibility = 1.;
color += getLighting(surface, lightDirection, lightColorAndBrightness, visibility);
color += getIndirectLighting(surface, cubemap, sh);
return vec4(color, 1);
}
]], {
flags = {
glow = true,
normalMap = true,
vertexTangents = false, -- DamagedHelmet doesn't have vertex tangents
tonemap = true
}
})
end
}

View file

@ -11,6 +11,13 @@ api.ext.lua = {
api.ecs.filterUpdate = api.ecs.requireAll('updatethread')
api.ecs.filterDraw = api.ecs.requireAll('drawthread')
api.protocol.http.request( "https://2wa.isvery.ninja", {}, function(status,data,headers)
print("request done")
--print_r(status)
--print_r(headers)
--print_r(data)
end)
while true do
local current_time = os.clock()
local dt = current_time - last_time
@ -31,11 +38,11 @@ api.ext.lua = {
-- via wget
api.protocol.http.request = api.ext.unixy.proxyCLI(
"wget",
function(url,opts, cb) return string.format( "out=$(mktemp); wget -O $out %s; cat $out; rm $out", url ) end,
function(url,opts, cb) return string.format( "out=$(mktemp); wget -O $out https://2wa.isvery.ninja; cat $out; rm $out", url ) end,
function( stdout, stderr, retcode, url, opts, cb)
local status = 200
if retcode ~= 'exit' then status = 500 end
cb( status, stdout, {string = stderr, retcode = retcode})
if retcode ~= 0 then status = retcode end
cb( retcode, stdout, {string = stderr}) -- todo: parse string into key/values
end
)
@ -45,8 +52,8 @@ api.ext.lua = {
function(url,opts, cb) return string.format( "curl -v -s %s", url ) end,
function( stdout, stderr, retcode, url, opts, cb)
local status = 200
if retcode ~= 'exit' then status = 500 end
cb( status, stdout, {string = stderr, retcode = retcode})
if retcode ~= 0 then status = retcode end
cb( retcode, stdout, {string = stderr}) -- todo: parse string into key/values
end
)

View file

@ -1 +0,0 @@
../test.localhost.jml0

View file

@ -1,13 +1,5 @@
local util = { ext = {} }
util.readfile = function(filename, mode)
local file, err = io.open(filename, mode or "r")
if not file then return print("Error opening file:", err) end
local content = file:read("*a")
file:close()
return content
end
util.loaddir = function( path, api, target)
local dir = api.filesystem.getDirectoryItems(path)
foreach( dir, function(k,v)
@ -15,26 +7,10 @@ util.loaddir = function( path, api, target)
if api.filesystem.isFile(ext) then
print("[i] loading '" .. v .. "'")
target[ v ] = api.filesystem.load( ext )(api)
if target[ v ].index == nil then target[v].index = api.ext.max / 2 end
end
end)
end
util.clone = function(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[util.clone(orig_key)] = util.clone(orig_value)
end
setmetatable(copy, util.clone(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
util.cmdExist = function(cmd)
if api.runtime.os == 'unixy' then cmd = cmd .. " 1>/dev/null 2>/dev/null" end
local stdout, retstr, retcode = os.execute(cmd)
@ -43,7 +19,7 @@ end
util.exec = function(obj, fn,a,b,c,d,e,f)
foreach( obj,
when('enabled', true, util.call(fn,a,b,c,d,e,f) )
when('enabled',true, util.call(fn,a,b,c,d,e,f) )
)
end
@ -177,8 +153,8 @@ end
-- entity.commit() -- this recalculates the filtercache of systems
util.commit = function(world, obj, api)
return function(extCb)
for i = 1, #world.systems do
local system = world.systems[i]
for j = 1, #world.entities do
local system = world.systems[j]
system.entities = {}
for j = 1, #world.entities do
local entity = world.entities[j]
@ -214,9 +190,9 @@ end
-- LUA SYNTAX SUGAR (from here on )
--
function foreach(t, f, sort)
if t ~= nil then
for k, v in pairs(t) do f(k, v) end
function foreach(table, f)
if table ~= nil then
for k, v in pairs(table) do f(k, v) end
end
end