diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..50926d4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "json.lua"] + path = json + url = https://github.com/rxi/json.lua diff --git a/conf.lua b/conf.lua index 3add5f2..96e3bcf 100644 --- a/conf.lua +++ b/conf.lua @@ -2,4 +2,5 @@ function lovr.conf(t) t.headset.overlay = 2 t.headset.overlayOrder = 2 t.window = false + t.identity = "lovr-playspace" end diff --git a/config/action_button.txt b/config/action_button.txt deleted file mode 100644 index b28fbc7..0000000 --- a/config/action_button.txt +++ /dev/null @@ -1 +0,0 @@ -trigger \ No newline at end of file diff --git a/config/check_density.txt b/config/check_density.txt deleted file mode 100644 index 30e2fb4..0000000 --- a/config/check_density.txt +++ /dev/null @@ -1 +0,0 @@ -0.05 \ No newline at end of file diff --git a/config/color_close_corners.json b/config/color_close_corners.json deleted file mode 100644 index 3054842..0000000 --- a/config/color_close_corners.json +++ /dev/null @@ -1 +0,0 @@ -[0.45,0.69,0.79,1.0] \ No newline at end of file diff --git a/config/color_close_grid.json b/config/color_close_grid.json deleted file mode 100644 index f95ef1b..0000000 --- a/config/color_close_grid.json +++ /dev/null @@ -1 +0,0 @@ -[0.45,0.69,0.79,0.5] \ No newline at end of file diff --git a/config/color_far_corners.json b/config/color_far_corners.json deleted file mode 100644 index 42e7855..0000000 --- a/config/color_far_corners.json +++ /dev/null @@ -1 +0,0 @@ -[0.45,0.69,0.79,0] \ No newline at end of file diff --git a/config/color_far_grid.json b/config/color_far_grid.json deleted file mode 100644 index 42e7855..0000000 --- a/config/color_far_grid.json +++ /dev/null @@ -1 +0,0 @@ -[0.45,0.69,0.79,0] \ No newline at end of file diff --git a/config/fade_start.txt b/config/fade_start.txt deleted file mode 100644 index ea2303b..0000000 --- a/config/fade_start.txt +++ /dev/null @@ -1 +0,0 @@ -0.5 \ No newline at end of file diff --git a/config/fade_stop.txt b/config/fade_stop.txt deleted file mode 100644 index 415b19f..0000000 --- a/config/fade_stop.txt +++ /dev/null @@ -1 +0,0 @@ -2.0 \ No newline at end of file diff --git a/config/grid_bottom.txt b/config/grid_bottom.txt deleted file mode 100644 index 171538e..0000000 --- a/config/grid_bottom.txt +++ /dev/null @@ -1 +0,0 @@ -0.0 \ No newline at end of file diff --git a/config/grid_density.txt b/config/grid_density.txt deleted file mode 100644 index 9f8e9b6..0000000 --- a/config/grid_density.txt +++ /dev/null @@ -1 +0,0 @@ -1.0 \ No newline at end of file diff --git a/config/grid_top.txt b/config/grid_top.txt deleted file mode 100644 index e440e5c..0000000 --- a/config/grid_top.txt +++ /dev/null @@ -1 +0,0 @@ -3 \ No newline at end of file diff --git a/json b/json new file mode 160000 index 0000000..dbf4b2d --- /dev/null +++ b/json @@ -0,0 +1 @@ +Subproject commit dbf4b2dd2eb7c23be2773c89eb059dadd6436f94 diff --git a/lib/json.lua b/lib/json.lua deleted file mode 100644 index 711ef78..0000000 --- a/lib/json.lua +++ /dev/null @@ -1,388 +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 diff --git a/main.lua b/main.lua index 2c7a695..c1674d6 100644 --- a/main.lua +++ b/main.lua @@ -1,7 +1,5 @@ -- Bootstrap appName = "lovr-playspace" -mainScriptPath = (debug.getinfo(1, "S").source:sub(2):match("(.*[/\\])") or "./"):sub(1,-2) -package.path = mainScriptPath.. "/lib/?.lua;" ..mainScriptPath.. "/lib/?/_main.lua;" ..package.path -- App hands = {"hand/right","hand/left"} @@ -24,48 +22,12 @@ limbs = { } configDirs = {} -json = require("json") - -function platformConfig() - if os.getenv("HOME") ~= nil then - return os.getenv("HOME") .. "/.config" - end - - return os.getenv("APPDATA") -end - -function fileExists(fileName) - local file = io.open(fileName,"rb") - if file == nil then return false end - file:close() - return true -end - -function getConfigFile(fileName) - for _,path in ipairs(configDirs) do - if fileExists(path .. "/" .. fileName) then - return path .. "/" .. fileName - end - end -end +json = require('json/json') function userConfig(fileName) return configDirs[1] .. "/" ..fileName end -function fileWrite(fileName,content) - local file = io.open(fileName,"wb") - file:write(content) - file:close() -end - -function readFile(fileName) - local file = io.open(fileName,"rb") - content = file:read("*all") - file:close() - return content -end - function getDistanceBetweenPoints3D(x1, y1, z1, x2, y2, z2) return math.sqrt((x2 - x1)^2 + (z2 - z1)^2) end @@ -184,54 +146,92 @@ function drawPointGrid(pass,points,cornerColor,miscColor) end function lovr.load() - lovr.graphics.setBackgroundColor(0.0, 0.0, 0.0, 0.0) - --table.insert(configDirs,platformConfig() .. "/" .. appName) - table.insert(configDirs,mainScriptPath .. "/config") - - settings = {} - settings.action_button = readFile(getConfigFile("action_button.txt")) - settings.check_density = tonumber(readFile(getConfigFile("check_density.txt"))) - settings.fade_start = tonumber(readFile(getConfigFile("fade_start.txt"))) - settings.fade_stop = tonumber(readFile(getConfigFile("fade_stop.txt"))) - settings.grid_density = tonumber(readFile(getConfigFile("grid_density.txt"))) - settings.grid_bottom = tonumber(readFile(getConfigFile("grid_bottom.txt"))) - settings.grid_top = tonumber(readFile(getConfigFile("grid_top.txt"))) - settings.color_close_corners = json.decode(readFile(getConfigFile("color_close_corners.json"))) - settings.color_close_grid = json.decode(readFile(getConfigFile("color_close_grid.json"))) - settings.color_far_corners = json.decode(readFile(getConfigFile("color_far_corners.json"))) - settings.color_far_grid = json.decode(readFile(getConfigFile("color_far_grid.json"))) - settings.points = {} - - --[[if not lovr.filesystem.isDirectory(configDirs[1]) then - fileWrite(userConfig("action_button.txt"),readFile(getConfigFile("action_button.txt"))) - fileWrite(userConfig("check_density.txt"),readFile(getConfigFile("check_density.txt"))) - fileWrite(userConfig("fade_start.txt"),readFile(getConfigFile("fade_start.txt"))) - fileWrite(userConfig("fade_stop.txt"),readFile(getConfigFile("fade_stop.txt"))) - fileWrite(userConfig("grid_density.txt"),readFile(getConfigFile("grid_density.txt"))) - fileWrite(userConfig("grid_bottom.txt"),readFile(getConfigFile("grid_bottom.txt"))) - fileWrite(userConfig("grid_top.txt"),readFile(getConfigFile("grid_top.txt"))) - fileWrite(userConfig("color_close_corners.json"),readFile(getConfigFile("color_close_corners.json"))) - fileWrite(userConfig("color_close_grid.json"),readFile(getConfigFile("color_close_grid.json"))) - fileWrite(userConfig("color_far_corners.json"),readFile(getConfigFile("color_far_corners.json"))) - fileWrite(userConfig("color_far_grid.json"),readFile(getConfigFile("color_far_grid.json"))) - initConfigure() - return - end]]-- - - if getConfigFile("points.json") == nil then - initConfigure() - return - end - - for _,hand in ipairs(hands) do - if lovr.headset.isDown(hand,settings.action_button) then - initConfigure() - return + lovr.graphics.setBackgroundColor(0.0, 0.0, 0.0, 0.0) + + -- Default settings + local defaults = { + action_button = "trigger", + check_density = 0.05, + fade_start = 0.5, + fade_stop = 2.0, + grid_density = 1.0, + grid_bottom = 0.0, + grid_top = 3, + color_close_corners = {0.45, 0.69, 0.79, 1.0}, + color_close_grid = {0.45, 0.69, 0.79, 0.5}, + color_far_corners = {0.45, 0.69, 0.79, 0}, + color_far_grid = {0.45, 0.69, 0.79, 0}, + points = {} + } + + -- Helper function to read file with fallback to default + local function loadSetting(filename, default, parser) + print("Checking file:", filename) + + if not lovr.filesystem.isFile(filename) then + print("File doesn't exist, creating:", filename) + local valueToSave + if type(default) == "table" then + valueToSave = json.encode(default) + else + valueToSave = tostring(default) + end + + local success = lovr.filesystem.write(filename, valueToSave) + print("Write success:", success, "for", filename, "with value:", valueToSave) + return default end + + -- File exists, try to read it + local content = lovr.filesystem.read(filename) + if content and parser then + local success, value = pcall(parser, content) + if success then return value end + elseif content then + return content + end + + return default end - - settings.points = json.decode(readFile(getConfigFile("points.json"))) - mode = modeDraw + + -- Initialize settings with fallbacks + settings = { + action_button = loadSetting("action_button.txt", defaults.action_button), + check_density = loadSetting("check_density.txt", defaults.check_density, tonumber), + fade_start = loadSetting("fade_start.txt", defaults.fade_start, tonumber), + fade_stop = loadSetting("fade_stop.txt", defaults.fade_stop, tonumber), + grid_density = loadSetting("grid_density.txt", defaults.grid_density, tonumber), + grid_bottom = loadSetting("grid_bottom.txt", defaults.grid_bottom, tonumber), + grid_top = loadSetting("grid_top.txt", defaults.grid_top, tonumber), + color_close_corners = loadSetting("color_close_corners.json", defaults.color_close_corners, json.decode), + color_close_grid = loadSetting("color_close_grid.json", defaults.color_close_grid, json.decode), + color_far_corners = loadSetting("color_far_corners.json", defaults.color_far_corners, json.decode), + color_far_grid = loadSetting("color_far_grid.json", defaults.color_far_grid, json.decode), + points = {} + } + + -- Handle points.json + local pointsPath = "points.json" + if not lovr.filesystem.isFile(pointsPath) then + initConfigure() + return + end + + -- Check for action button press + for _, hand in ipairs(hands) do + if lovr.headset.isDown(hand, settings.action_button) then + initConfigure() + return + end + end + + -- Load points if we haven't returned already + local pointsContent = lovr.filesystem.read(pointsPath) + if pointsContent then + settings.points = json.decode(pointsContent) + end + + mode = modeDraw end function initConfigure() @@ -290,7 +290,7 @@ function modeConfigure(pass) if inputDev ~= nil then saveProg = saveProg - (deltaTime / 3) if saveProg <= 0 then - fileWrite(userConfig("points.json"),json.encode(settings.points)) + lovr.filesystem.write("config/points.json", json.encode(settings.points)) deinitConfigure() modeDraw(pass) return