lovr = require 'lovr' local function nogame() function lovr.conf(t) t.headset.supersample = true t.modules.audio = false t.modules.physics = false t.modules.thread = false end local models = {} function lovr.load() if not lovr.graphics then print(string.format('LÖVR %d.%d.%d\nNo game', lovr.getVersion())) lovr.event.quit() return end lovr.graphics.setBackgroundColor(0x20232c) lovr.graphics.setCullingEnabled(true) logo = lovr.graphics.newShader([[ vec4 position(mat4 projection, mat4 transform, vec4 vertex) { return projection * transform * vertex; } ]], [[ vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { float y = (1. - uv.y); uv = uv * 4. - 2.; const float k = sqrt(3.); uv.x = abs(uv.x) - 1.; uv.y = uv.y + 1. / k + .25; if (uv.x + k * uv.y > 0.) { uv = vec2(uv.x - k * uv.y, -k * uv.x - uv.y) / 2.; } uv.x -= clamp(uv.x, -2., 0.); float sdf = -length(uv) * sign(uv.y) - .5; float w = fwidth(sdf) * .5; float alpha = smoothstep(.22 + w, .22 - w, sdf); vec3 color = mix(vec3(.094, .662, .890), vec3(.913, .275, .6), clamp(y * 1.5 - .25, 0., 1.)); color = mix(color, vec3(.2, .2, .24), smoothstep(-.12 + w, -.12 - w, sdf)); return vec4(pow(color, vec3(2.2)), alpha); } ]], { flags = { highp = true } }) text = lovr.graphics.newShader('font', { flags = { highp = true } }) end function lovr.draw() lovr.graphics.setColor(0xffffff) local padding = .1 local font = lovr.graphics.getFont() local fade = .315 + .685 * math.abs(math.sin(lovr.timer.getTime() * 2)) local titlePosition = 1.4 - padding local subtitlePosition = titlePosition - font:getHeight() * .25 - padding lovr.graphics.setShader(logo) lovr.graphics.plane('fill', 0, 1.9, -3, 1, 1, 0, 0, 1) lovr.graphics.setShader(text) lovr.graphics.setColor(0xffffff) lovr.graphics.print('LÖVR', -.012, titlePosition, -3, .25, 0, 0, 1, 0, nil, 'center', 'top') lovr.graphics.setColor(.9, .9, .9, fade) lovr.graphics.print('No game :(', -.005, subtitlePosition, -3, .15, 0, 0, 1, 0, nil, 'center', 'top') lovr.graphics.setColor(0xffffff) lovr.graphics.setShader() if lovr.headset then for i, hand in ipairs(lovr.headset.getHands()) do models[hand] = models[hand] or lovr.headset.newModel(hand, { animated = true }) if models[hand] then lovr.headset.animate(hand, models[hand]) local pose = mat4(lovr.headset.getPose(hand)) if models[hand]:hasJoints() then animated = animated or lovr.graphics.newShader('unlit', { flags = { animated = true } }) lovr.graphics.setShader(animated) lovr.graphics.setColorMask() models[hand]:draw(pose) lovr.graphics.setColorMask(true, true, true, true) lovr.graphics.setColor(0, 0, 0, .5) models[hand]:draw(pose) lovr.graphics.setShader() else models[hand]:draw(pose) end end end end lovr.graphics.setColor(0xffffff) end end -- Note: Cannot be overloaded function lovr.boot() local conf = { version = '0.15.0', identity = 'default', saveprecedence = true, modules = { audio = true, data = true, event = true, graphics = true, headset = true, math = true, physics = true, system = true, thread = true, timer = true }, audio = { start = true, spatializer = nil }, graphics = { debug = false }, headset = { drivers = { 'openxr', 'webxr', 'desktop' }, supersample = false, offset = 1.7, msaa = 4, overlay = false }, math = { globals = true }, window = { width = 1080, height = 600, fullscreen = false, resizable = false, msaa = 0, title = 'LÖVR', icon = nil, vsync = 1 } } lovr.filesystem = require('lovr.filesystem') local hasConf, hasMain = lovr.filesystem.isFile('conf.lua'), lovr.filesystem.isFile('main.lua') if not lovr.filesystem.getSource() or not (hasConf or hasMain) then nogame() end local confOk, confError = true if hasConf then confOk, confError = pcall(require, 'conf') end if confOk and lovr.conf then confOk, confError = pcall(lovr.conf, conf) end lovr._setConf(conf) lovr.filesystem.setIdentity(conf.identity, conf.saveprecedence) for module in pairs(conf.modules) do if conf.modules[module] then local ok, result = pcall(require, 'lovr.' .. module) if not ok then print(string.format('Warning: Could not load module %q: %s', module, result)) else lovr[module] = result end end end if lovr.headset and lovr.graphics and conf.window then lovr.headset.start() end lovr.handlers = setmetatable({}, { __index = lovr }) if not confOk then error(confError) end if hasMain then require 'main' end return lovr.run() end function lovr.run() local dt = 0 if lovr.timer then lovr.timer.step() end if lovr.load then lovr.load(arg) end return function() if lovr.event then lovr.event.pump() for name, a, b, c, d in lovr.event.poll() do if name == 'restart' then local cookie = lovr.restart and lovr.restart() return 'restart', cookie elseif name == 'quit' and (not lovr.quit or not lovr.quit(a)) then return a or 0 end if lovr.handlers[name] then lovr.handlers[name](a, b, c, d) end end end if lovr.timer then dt = lovr.timer.step() end if lovr.headset then lovr.headset.update(dt) end if lovr.update then lovr.update(dt) end if lovr.graphics then lovr.graphics.origin() if lovr.headset then lovr.headset.renderTo(lovr.draw) end if lovr.graphics.hasWindow() then lovr.mirror() end lovr.graphics.present() end if lovr.math then lovr.math.drain() end end end function lovr.mirror() if lovr.headset then -- On some systems, headset module will be disabled local blend, alpha = lovr.graphics.getBlendMode() lovr.graphics.setBlendMode() local texture = lovr.headset.getMirrorTexture() if texture then -- On some drivers, texture is printed directly to the window lovr.graphics.fill(texture) end lovr.graphics.setBlendMode(blend, alpha) else lovr.graphics.clear() if lovr.draw then lovr.draw() end end end local function formatTraceback(s) return s:gsub('\n[^\n]+$', ''):gsub('\t', ''):gsub('stack traceback', '\nStack') end function lovr.errhand(message, traceback) message = tostring(message) message = message .. formatTraceback(traceback or debug.traceback('', 4)) print('Error:\n' .. message) if not lovr.graphics then return function() return 1 end end lovr.graphics.reset() lovr.graphics.setBackgroundColor(.11, .10, .14) lovr.graphics.setColor(.85, .85, .85) local font = lovr.graphics.getFont() font:setPixelDensity() font:setFlipEnabled(false) local wrap = .7 * font:getPixelDensity() local width, lines = font:getWidth(message, wrap) local height = 2.6 + lines local y = math.min(height / 2, 10) local function render() lovr.graphics.print('Error', -width / 2, y, -20, 1.6, 0, 0, 0, 0, nil, 'left', 'top') lovr.graphics.print(message, -width / 2, y - 2.6, -20, 1.0, 0, 0, 0, 0, wrap, 'left', 'top') end return function() lovr.event.pump() for name, a in lovr.event.poll() do if name == 'quit' then return a or 1 elseif name == 'restart' then return 'restart', lovr.restart and lovr.restart() end end lovr.graphics.origin() if lovr.headset then lovr.headset.update(0) lovr.headset.renderTo(render) end if lovr.graphics.hasWindow() then lovr.graphics.setViewPose(1) local width, height = lovr.graphics.getDimensions() local projection = lovr.math.mat4():perspective(.1, 100, math.rad(67), width / height) lovr.graphics.setProjection(1, projection) lovr.graphics.clear() render() end lovr.graphics.present() if lovr.math then lovr.math.drain() end end end function lovr.threaderror(thread, err) error('Thread error\n\n' .. err, 0) end function lovr.log(message, level, tag) message = message:gsub('\n$', '') print(message) end -- This splits up the string returned by luax_getstack so it looks like the error message plus the string from -- debug.traceback(). This includes splitting on the newline before 'stack traceback:' and appending a newline local function splitOnLabelLine(s, t) local at = s:reverse():find(t:reverse()) if at then local slen = #s at = (#s - at - #t + 2) return s:sub(1, at-2), s:sub(at,slen) .. '\n' else return s, '' end end -- lovr will run this function in its own coroutine return function() local errored = false -- lovr.errhand may only be called once local function onerror(e, tb) -- wrapper for errhand to ensure it is only called once local function abortclean() return 1 end if not errored then errored = true return lovr.errhand(e, tb) or abortclean else print('Error occurred while trying to display another error:\n' .. tostring(e) .. formatTraceback(tb or debug.traceback('', 2))) return abortclean end end -- Executes lovr.boot and lovr.run. -- continuation, afterward, will be either lovr.run's per-frame function, or the result of errhand. local _, continuation = xpcall(lovr.boot, onerror) while true do if type(continuation) == 'string' then -- LuaJIT returns a fixed string if an error occurs in an xpcall error handler print('Error occurred while trying to display another error: ' .. continuation) return 1 end local ok, result, extra = xpcall(continuation, onerror) if result and ok then return result, extra -- Result is value returned by function. Return it. elseif not ok then continuation = result end -- Result is value returned by error handler. Make it the new error handler. local externerror = coroutine.yield() -- Return control to C code if externerror then -- A must-report error occurred in the C code local errorpart, tracepart = splitOnLabelLine(externerror, 'stack traceback:') continuation = onerror(errorpart, tracepart) -- Switch continuation to lovr.errhand end end end