diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c5b7ab7..4770f745 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,11 @@ if(EMSCRIPTEN) "-s FULL_ES3=1 " "-s FORCE_FILESYSTEM=1 " "-s ALLOW_MEMORY_GROWTH=1 " - "-s \"EXPORTED_FUNCTIONS=['_main','_lovrRun','_lovrQuit','_lovrDestroy','_mat4_rotateQuat','_mat4_set','_mat4_transform','_mat4_transformDirection','_quat_fromMat4','_quat_getAngleAxis']\" " + "-s \"EXPORTED_FUNCTIONS=[ " + "'_main','_lovrRun','_lovrQuit','_lovrDestroy'," + "'_mat4_identity','_mat4_invert','_mat4_multiply','_mat4_rotateQuat','_mat4_transform','_mat4_transformDirection','_mat4_translate'," + "'_quat_fromMat4','_quat_getAngleAxis'" + "]\" " "-s \"EXTRA_EXPORTED_RUNTIME_METHODS=['getValue','setValue']\" " "--js-library \"${CMAKE_CURRENT_SOURCE_DIR}/src/resources/lovr.js\"" ) diff --git a/src/api/headset.c b/src/api/headset.c index f8a39840..16ba9940 100644 --- a/src/api/headset.c +++ b/src/api/headset.c @@ -19,7 +19,9 @@ static HeadsetRenderData headsetRenderData; static void renderHelper(void* userdata) { HeadsetRenderData* renderData = userdata; lua_State* L = renderData->L; +#ifdef EMSCRIPTEN lua_rawgeti(L, LUA_REGISTRYINDEX, renderData->ref); +#endif lua_call(L, 0, 0); } @@ -282,11 +284,13 @@ int l_lovrHeadsetRenderTo(lua_State* L) { lua_settop(L, 1); luaL_checktype(L, 1, LUA_TFUNCTION); +#ifdef EMSCRIPTEN if (headsetRenderData.ref != LUA_NOREF) { luaL_unref(L, LUA_REGISTRYINDEX, headsetRenderData.ref); } headsetRenderData.ref = luaL_ref(L, LUA_REGISTRYINDEX); +#endif headsetRenderData.L = L; lovrHeadsetDriver->renderTo(renderHelper, &headsetRenderData); return 0; diff --git a/src/graphics/canvas.c b/src/graphics/canvas.c index b8826e06..924de018 100644 --- a/src/graphics/canvas.c +++ b/src/graphics/canvas.c @@ -95,11 +95,13 @@ void lovrCanvasDestroy(void* ref) { } void lovrCanvasResolve(Canvas* canvas) { - int width = canvas->texture.width; - int height = canvas->texture.height; - glBindFramebuffer(GL_READ_FRAMEBUFFER, canvas->framebuffer); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, canvas->resolveFramebuffer); - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR); + if (canvas->flags.msaa > 0) { + int width = canvas->texture.width; + int height = canvas->texture.height; + glBindFramebuffer(GL_READ_FRAMEBUFFER, canvas->framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, canvas->resolveFramebuffer); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } if (canvas->flags.mipmaps) { lovrGraphicsBindTexture(&canvas->texture, canvas->texture.type, 0); diff --git a/src/graphics/mesh.c b/src/graphics/mesh.c index f0c24754..3e23debd 100644 --- a/src/graphics/mesh.c +++ b/src/graphics/mesh.c @@ -228,7 +228,7 @@ void lovrMeshUnmapVertices(Mesh* mesh) { size_t stride = mesh->format.stride; size_t start = mesh->mapStart * stride; size_t count = mesh->mapCount * stride; - glBufferSubData(GL_ARRAY_BUFFER, start, count, mesh->data.bytes + start); + glBufferSubData(GL_ARRAY_BUFFER, start, count, mesh->data.bytes); #else glUnmapBuffer(GL_ARRAY_BUFFER); #endif @@ -273,9 +273,8 @@ IndexPointer lovrMeshWriteIndices(Mesh* mesh, uint32_t count, size_t size) { mesh->indexCapacity = nextPo2(size * count); #ifdef EMSCRIPTEN mesh->indices.raw = realloc(mesh->indices.raw, mesh->indexCapacity); -#else - glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indexCapacity, NULL, mesh->usage); #endif + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indexCapacity, NULL, mesh->usage); } #ifdef EMSCRIPTEN diff --git a/src/headset/webvr.c b/src/headset/webvr.c index 1335fb1f..3b58e0c5 100644 --- a/src/headset/webvr.c +++ b/src/headset/webvr.c @@ -5,8 +5,8 @@ #include // Provided by resources/lovr.js -extern bool webvrInit(void); -extern void webvrSetCallbacks(void (*added)(uint32_t id), void (*removed)(uint32_t id), void (*pressed)(uint32_t, ControllerButton button), void (*released)(uint32_t id, ControllerButton button), void (*mount)(bool mounted)); +extern bool webvrInit(float offset, void (*added)(uint32_t id), void (*removed)(uint32_t id), void (*pressed)(uint32_t, ControllerButton button), void (*released)(uint32_t id, ControllerButton button), void (*mount)(bool mounted)); +extern void webvrDestroy(void); extern HeadsetType webvrGetType(void); extern HeadsetOrigin webvrGetOriginType(void); extern bool webvrIsMounted(void); @@ -28,11 +28,11 @@ extern bool webvrControllerIsDown(Controller* controller, ControllerButton butto extern bool webvrControllerIsTouched(Controller* controller, ControllerButton button); extern void webvrControllerVibrate(Controller* controller, float duration, float power); extern ModelData* webvrControllerNewModelData(Controller* controller); -extern void webvrSetRenderCallback(void (*callback)(void*), void* userdata); +extern void webvrSetRenderCallback(void (*callback)(float*, float*, float*, float*, void*), void* userdata); extern void webvrUpdate(float dt); typedef struct { - float offset; + Canvas* canvas; vec_controller_t controllers; void (*renderCallback)(void*); } HeadsetState; @@ -87,16 +87,42 @@ static void onMountChanged(bool mounted) { }); } -static void onFrame(void* userdata) { - // +static void onFrame(float* leftView, float* rightView, float* leftProjection, float* rightProjection, void* userdata) { + if (!state.canvas) { + int32_t width, height; + webvrGetDisplayDimensions(&width, &height); + CanvasFlags flags = { .msaa = 0, .depth = true, .stencil = true, .stereo = true, .mipmaps = false }; + state.canvas = lovrCanvasCreate(width, height, FORMAT_RGB, flags); + } + + Layer layer = { .canvas = state.canvas }; + + memcpy(layer.views, leftView, 16 * sizeof(float)); + memcpy(layer.views + 16, rightView, 16 * sizeof(float)); + memcpy(layer.projections, leftProjection, 16 * sizeof(float)); + memcpy(layer.projections + 16, rightProjection, 16 * sizeof(float)); + + lovrGraphicsPushLayer(layer); + lovrGraphicsClear(true, true, true, lovrGraphicsGetBackgroundColor(), 1., 0); + state.renderCallback(userdata); + lovrGraphicsPopLayer(); + + Color oldColor = lovrGraphicsGetColor(); + lovrGraphicsSetColor((Color) { 1, 1, 1, 1 }); + Shader* lastShader = lovrGraphicsGetShader(); + lovrRetain(lastShader); + lovrGraphicsSetShader(NULL); + lovrGraphicsFill(&state.canvas->texture); + lovrGraphicsSetShader(lastShader); + lovrRelease(lastShader); + lovrGraphicsSetColor(oldColor); } static bool webvrDriverInit(float offset) { - state.offset = offset; vec_init(&state.controllers); - state.renderCallback = NULL; - if (webvrInit()) { - webvrSetCallbacks(onControllerAdded, onControllerRemoved, onControllerPressed, onControllerReleased, onMountChanged); + + if (webvrInit(offset, onControllerAdded, onControllerRemoved, onControllerPressed, onControllerReleased, onMountChanged)) { + state.renderCallback = NULL; return true; } else { return false; @@ -104,6 +130,8 @@ static bool webvrDriverInit(float offset) { } static void webvrDriverDestroy() { + webvrDestroy(); + lovrRelease(state.canvas); vec_deinit(&state.controllers); memset(&state, 0, sizeof(HeadsetState)); } diff --git a/src/lovr.c b/src/lovr.c index e2a111f4..1d34152b 100644 --- a/src/lovr.c +++ b/src/lovr.c @@ -33,6 +33,7 @@ static void emscriptenLoop(void* arg) { bool isRestart = lua_type(L, -1) == LUA_TSTRING && !strcmp(lua_tostring(L, -1), "restart"); lovrDestroy(); + lua_close(L); emscripten_cancel_main_loop(); diff --git a/src/resources/boot.lua b/src/resources/boot.lua index 6423236f..ed323900 100644 --- a/src/resources/boot.lua +++ b/src/resources/boot.lua @@ -126,12 +126,12 @@ function lovr.run() end if lovr.update then lovr.update(dt) end if lovr.graphics then - lovr.graphics.clear() lovr.graphics.origin() if lovr.draw then if lovr.headset then lovr.headset.renderTo(lovr.draw) else + lovr.graphics.clear() lovr.draw() end end diff --git a/src/resources/boot.lua.h b/src/resources/boot.lua.h index 0c6ea02a..cea67cf6 100644 --- a/src/resources/boot.lua.h +++ b/src/resources/boot.lua.h @@ -317,135 +317,136 @@ unsigned char boot_lua[] = { 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, - 0x73, 0x2e, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, - 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, - 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, - 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, - 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x54, 0x6f, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, - 0x77, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, - 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, - 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x65, 0x73, - 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, - 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, - 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, - 0x76, 0x72, 0x2e, 0x65, 0x72, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x28, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x29, 0x0a, 0x20, 0x20, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62, 0x75, - 0x67, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x28, - 0x27, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x5c, 0x6e, 0x27, 0x20, 0x2e, - 0x2e, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x32, - 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x27, 0x5c, 0x6e, 0x5b, 0x5e, - 0x5c, 0x6e, 0x5d, 0x2b, 0x24, 0x27, 0x2c, 0x20, 0x27, 0x27, 0x29, 0x3a, - 0x67, 0x73, 0x75, 0x62, 0x28, 0x27, 0x5c, 0x74, 0x27, 0x2c, 0x20, 0x27, - 0x27, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x27, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, 0x61, 0x63, 0x6b, - 0x27, 0x2c, 0x20, 0x27, 0x5c, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x27, - 0x29, 0x0a, 0x20, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x28, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, - 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, - 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, - 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, - 0x31, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, - 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, - 0x73, 0x2e, 0x72, 0x65, 0x73, 0x65, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, - 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, - 0x73, 0x2e, 0x73, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x2e, 0x31, 0x30, - 0x35, 0x2c, 0x20, 0x2e, 0x30, 0x39, 0x38, 0x2c, 0x20, 0x2e, 0x31, 0x33, - 0x37, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, - 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, - 0x6c, 0x6f, 0x72, 0x28, 0x2e, 0x38, 0x36, 0x33, 0x2c, 0x20, 0x2e, 0x38, - 0x36, 0x33, 0x2c, 0x20, 0x2e, 0x38, 0x36, 0x33, 0x29, 0x0a, 0x20, 0x20, - 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, - 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, - 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x73, 0x65, - 0x74, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x65, 0x64, 0x28, 0x66, 0x61, - 0x6c, 0x73, 0x65, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, - 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, - 0x73, 0x2e, 0x67, 0x65, 0x74, 0x46, 0x6f, 0x6e, 0x74, 0x28, 0x29, 0x0a, - 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, - 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x66, - 0x6f, 0x6e, 0x74, 0x3a, 0x67, 0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, - 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x28, 0x29, 0x0a, 0x20, 0x20, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, - 0x3d, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x3a, 0x67, 0x65, 0x74, 0x57, 0x69, - 0x64, 0x74, 0x68, 0x28, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, - 0x20, 0x2e, 0x35, 0x35, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, - 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x29, 0x0a, 0x20, 0x20, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x28, 0x29, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, - 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x28, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x2d, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x20, 0x2f, 0x20, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x2d, - 0x32, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, - 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x2e, 0x35, 0x35, 0x20, 0x2a, - 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, - 0x79, 0x2c, 0x20, 0x27, 0x6c, 0x65, 0x66, 0x74, 0x27, 0x29, 0x0a, 0x20, - 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, - 0x6e, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, 0x29, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, - 0x69, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x2e, 0x70, 0x6f, 0x6c, 0x6c, 0x28, 0x29, 0x20, 0x64, 0x6f, 0x20, - 0x69, 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x27, - 0x71, 0x75, 0x69, 0x74, 0x27, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, - 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x20, 0x65, 0x6e, 0x64, 0x20, - 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, - 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c, - 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, + 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, + 0x2e, 0x64, 0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, + 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, + 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x65, 0x74, 0x2e, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x6f, 0x28, + 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x29, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, - 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, - 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x72, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x6f, 0x28, 0x72, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, - 0x63, 0x73, 0x2e, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, - 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, - 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, - 0x72, 0x2e, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x28, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x2c, 0x20, 0x65, 0x72, - 0x72, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x27, - 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x5c, 0x6e, 0x5c, 0x6e, 0x27, 0x20, 0x2e, 0x2e, 0x20, 0x65, 0x72, 0x72, - 0x2c, 0x20, 0x30, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x72, 0x65, - 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, - 0x5f, 0x2c, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, - 0x78, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, - 0x62, 0x6f, 0x6f, 0x74, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, - 0x72, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x77, - 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6f, - 0x6b, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, - 0x78, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x74, 0x68, 0x72, 0x65, 0x61, - 0x64, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x72, 0x72, 0x68, - 0x61, 0x6e, 0x64, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, - 0x6e, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, - 0x6f, 0x6b, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x72, 0x65, - 0x61, 0x64, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, - 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x72, 0x6f, - 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x79, 0x69, 0x65, 0x6c, 0x64, 0x28, - 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x0a, 0x65, 0x6e, 0x64, 0x0a + 0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, + 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, + 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, + 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x72, 0x72, 0x68, 0x61, + 0x6e, 0x64, 0x28, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x29, 0x0a, + 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, + 0x64, 0x65, 0x62, 0x75, 0x67, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x62, + 0x61, 0x63, 0x6b, 0x28, 0x27, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x5c, + 0x6e, 0x27, 0x20, 0x2e, 0x2e, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x2c, 0x20, 0x32, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x27, + 0x5c, 0x6e, 0x5b, 0x5e, 0x5c, 0x6e, 0x5d, 0x2b, 0x24, 0x27, 0x2c, 0x20, + 0x27, 0x27, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x27, 0x5c, 0x74, + 0x27, 0x2c, 0x20, 0x27, 0x27, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, + 0x27, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x62, 0x61, 0x63, 0x6b, 0x27, 0x2c, 0x20, 0x27, 0x5c, 0x6e, 0x53, 0x74, + 0x61, 0x63, 0x6b, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x70, 0x72, 0x69, 0x6e, + 0x74, 0x28, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x29, 0x0a, 0x20, + 0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, + 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x31, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x65, 0x6e, + 0x64, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x65, 0x74, 0x28, + 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x42, 0x61, 0x63, + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, + 0x28, 0x2e, 0x31, 0x30, 0x35, 0x2c, 0x20, 0x2e, 0x30, 0x39, 0x38, 0x2c, + 0x20, 0x2e, 0x31, 0x33, 0x37, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, + 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, + 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x2e, 0x38, 0x36, 0x33, + 0x2c, 0x20, 0x2e, 0x38, 0x36, 0x33, 0x2c, 0x20, 0x2e, 0x38, 0x36, 0x33, + 0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, + 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, + 0x74, 0x2e, 0x73, 0x65, 0x74, 0x4d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x65, + 0x64, 0x28, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x20, 0x65, 0x6e, 0x64, + 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, + 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x46, 0x6f, 0x6e, + 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, + 0x70, 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, + 0x20, 0x3d, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x3a, 0x67, 0x65, 0x74, 0x50, + 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x28, + 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x3a, 0x67, + 0x65, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x28, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x2c, 0x20, 0x2e, 0x35, 0x35, 0x20, 0x2a, 0x20, 0x70, + 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x29, + 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, + 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, + 0x6e, 0x74, 0x28, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, + 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x2f, 0x20, 0x32, 0x2c, 0x20, + 0x30, 0x2c, 0x20, 0x2d, 0x32, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x30, + 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x2e, + 0x35, 0x35, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, + 0x6e, 0x73, 0x69, 0x74, 0x79, 0x2c, 0x20, 0x27, 0x6c, 0x65, 0x66, 0x74, + 0x27, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x72, + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, + 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, + 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x6f, 0x6c, 0x6c, 0x28, 0x29, + 0x20, 0x64, 0x6f, 0x20, 0x69, 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x3d, 0x3d, 0x20, 0x27, 0x71, 0x75, 0x69, 0x74, 0x27, 0x20, 0x74, 0x68, + 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x20, + 0x65, 0x6e, 0x64, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, + 0x73, 0x2e, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, + 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, + 0x65, 0x74, 0x2e, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x6f, 0x28, + 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x28, 0x29, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, + 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, + 0x6e, 0x64, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, + 0x2c, 0x20, 0x65, 0x72, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x28, 0x27, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5c, 0x6e, 0x5c, 0x6e, 0x27, 0x20, 0x2e, 0x2e, + 0x20, 0x65, 0x72, 0x72, 0x2c, 0x20, 0x30, 0x29, 0x0a, 0x65, 0x6e, 0x64, + 0x0a, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x20, 0x5f, 0x2c, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, + 0x64, 0x20, 0x3d, 0x20, 0x78, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, + 0x6f, 0x76, 0x72, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x2c, 0x20, 0x6c, 0x6f, + 0x76, 0x72, 0x2e, 0x65, 0x72, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x29, 0x0a, + 0x0a, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x72, 0x75, + 0x65, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x20, 0x6f, 0x6b, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x3d, 0x20, 0x78, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x74, + 0x68, 0x72, 0x65, 0x61, 0x64, 0x2c, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, + 0x65, 0x72, 0x72, 0x68, 0x61, 0x6e, 0x64, 0x29, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x66, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x6f, 0x6b, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x6b, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, + 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x63, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x2e, 0x79, 0x69, + 0x65, 0x6c, 0x64, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x31, 0x0a, + 0x65, 0x6e, 0x64, 0x0a }; -unsigned int boot_lua_len = 5376; +unsigned int boot_lua_len = 5380; diff --git a/src/resources/lovr.js b/src/resources/lovr.js index 824b1082..e87bea5b 100644 --- a/src/resources/lovr.js +++ b/src/resources/lovr.js @@ -1,71 +1,81 @@ var LibraryLOVR = { - $C: { - ORIGIN_HEAD: 0, - ORIGIN_FLOOR: 1, - HEADSET_UNKNOWN: 0, - EYE_LEFT: 0, - EYE_RIGHT: 1, - HAND_UNKNOWN: 0, - HAND_LEFT: 1, - HAND_RIGHT: 2, - CONTROLLER_AXIS_TRIGGER: 0, - CONTROLLER_AXIS_GRIP: 1, - CONTROLLER_AXIS_TOUCHPAD_X: 2, - CONTROLLER_AXIS_TOUCHPAD_Y: 3, - sizeofRef: 8 - }, - $webvr: { - display: null, - gamepads: {}, - lastGamepadState: {}, - initialized: false, - mirrored: true, - oncontrolleradded: 0, - oncontrollerremoved: 0, - oncontrollerpressed: 0, - oncontrollerreleased: 0, - onmount: 0, - renderCallback: 0, - renderUserdata: 0, - frameData: null, - width: 0, - height: 0, - matA: null, - matB: null, - quat: null, buttonMap: { 'OpenVR Gamepad': [null, null, 3, 1, 2, 0], 'Oculus Touch (Right)': [null, null, 3, 1, 2, 0, null, 3, 4], - 'Oculus Touch (Left)': [null, null, 3, 1, 2, 0, null, null, null, 3, 4] + 'Oculus Touch (Left)': [null, null, 3, 1, 2, 0, null, null, null, 3, 4], + 'Spatial Controller': [null, null, 3, 1, 2, 4] }, - init: function() { + init: function(offset, added, removed, pressed, released, mount) { if (webvr.initialized || !Module.lovrDisplay) { return; } - var canvas, display, gamepads = webvr.gamepads; + var a, b, c, d, e, canvas, display; webvr.initialized = true; webvr.display = display = Module.lovrDisplay; webvr.canvas = canvas = Module.canvas; webvr.frameData = new VRFrameData(); - webvr.matA = Module._malloc(64); - webvr.matB = Module._malloc(64); + webvr.gamepads = []; + webvr.lastGamepadState = []; + webvr.mirrored = true; + webvr.offset = offset; + webvr.controlleradded = added; + webvr.controllerremoved = removed; + webvr.controllerpressed = pressed; + webvr.controllerreleased = released; + webvr.mount = mount; + webvr.renderCallback = C.NULL; + webvr.renderUserdata = C.NULL; + webvr.matA = a = Module._malloc(64); + webvr.matB = b = Module._malloc(64); + webvr.matC = c = Module._malloc(64); + webvr.matD = d = Module._malloc(64); + webvr.matE = e = Module._malloc(64); webvr.quat = Module._malloc(16); + webvr.width = display.getEyeParameters('left').renderWidth; + webvr.height = display.getEyeParameters('left').renderHeight; + Browser.setCanvasSize(webvr.width * 2, webvr.height); - var eyeParams = display.getEyeParameters('left'); - webvr.width = eyeParams.renderWidth; - webvr.height = eyeParams.renderHeight; - canvas.width = webvr.width * 2; - canvas.height = webvr.height; + webvr.onentervr = function() { + if (!display.isPresenting) { + display.requestPresent([{ source: canvas }]); + } + }; - display.requestAnimationFrame(function onAnimationFrame() { - display.requestAnimationFrame(onAnimationFrame); + webvr.onexitvr = function() { + if (display.isPresenting) { + display.exitPresent(); + } + }; + + webvr.onvrdisplaypresentchange = function() { + Runtime.dynCall('vi', webvr.mount, [display.isPresenting]); + }; + + webvr.frameId = display.requestAnimationFrame(function onAnimationFrame() { + webvr.frameId = display.requestAnimationFrame(onAnimationFrame); display.getFrameData(webvr.frameData); if (webvr.renderCallback) { - Runtime.dynCall('vi', webvr.renderCallback, [webvr.renderUserdata]); + var sittingToStanding = webvr.display.stageParameters && webvr.display.stageParameters.sittingToStandingTransform; + + if (sittingToStanding) { + HEAPF32.set(sittingToStanding, e >> 2); + Module._mat4_invert(e); + } else { + Module._mat4_identity(e); + HEAPF32[(e + 4 * 13) >> 2] = -webvr.offset; + } + + HEAPF32.set(webvr.frameData.leftViewMatrix, a >> 2); + HEAPF32.set(webvr.frameData.rightViewMatrix, b >> 2); + HEAPF32.set(webvr.frameData.leftProjectionMatrix, c >> 2); + HEAPF32.set(webvr.frameData.rightProjectionMatrix, d >> 2); + Module._mat4_multiply(a, e); + Module._mat4_multiply(b, e); + Runtime.dynCall('viiiii', webvr.renderCallback, [a, b, c, d, webvr.renderUserdata]); } if (display.isPresenting) { @@ -73,62 +83,49 @@ var LibraryLOVR = { } }); - window.addEventListener('gamepadconnected', function(event) { - var gamepad = event.gamepad; - if (gamepad.displayId === display.displayId && gamepad.pose) { - gamepads[gamepad.index] = gamepad; - webvr.lastGamepadState[gamepad.index] = gamepad.buttons.map(function(button) { return button.pressed; }); - webvr.oncontrolleradded && Runtime.dynCall('vi', webvr.oncontrolleradded, [gamepad.index]); - } - }); + window.addEventListener('lovr.entervr', webvr.onentervr); + window.addEventListener('lovr.exitvr', webvr.onexitvr); + window.addEventListener('vrdisplaypresentchange', webvr.onvrdisplaypresentchange); + }, - window.addEventListener('gamepaddisconnected', function(event) { - var gamepad = event.gamepad; - if (gamepads[gamepad.index]) { - webvr.oncontrollerremoved && Runtime.dynCall('vi', webvr.oncontrollerremoved, [gamepad.index]); - delete webvr.lastGamepadState[gamepad.index]; - delete gamepads[gamepad.index]; - } - }); + destroy: function() { + if (!webvr.initialized) { + return; + } - window.addEventListener('lovr.entervr', function() { - if (!display.isPresenting) { - display.requestPresent([{ source: canvas }]); - } - }); + webvr.initialized = false; + Module._free(webvr.matA); + Module._free(webvr.matB); + Module._free(webvr.matC); + Module._free(webvr.matD); + Module._free(webvr.matE); + Module._free(webvr.quat); - window.addEventListener('lovr.exitvr', function() { - if (display.isPresenting) { - display.exitPresent(); - } - }); + window.removeEventListener('lovr.entervr', webvr.onentervr); + window.removeEventListener('lovr.exitvr', webvr.onexitvr); + window.removeEventListener('vrdisplaypresentchange', webvr.onvrdisplaypresentchange); - window.addEventListener('vrdisplaypresentchange', function() { - Runtime.dynCall('vi', webvr.onmount, [display.isPresenting]); - }); + if (webvr.frameId) { + webvr.display.cancelAnimationFrame(webvr.frameId); + } }, controllerToGamepad: function(controller) { - var index = HEAPU32[(controller + C.sizeofRef) >> 2]; - return webvr.gamepads[index]; + return webvr.gamepads[HEAPU32[(controller + C.sizeofRef) >> 2]]; } }, - webvrInit: function() { + webvrInit: function(offset, added, removed, pressed, released, mount) { if (Module.lovrDisplay) { - webvr.init(); + webvr.init(offset, added, removed, pressed, released, mount); return true; } else { return false; } }, - webvrSetCallbacks: function(added, removed, pressed, released, mount) { - webvr.oncontrolleradded = added; - webvr.oncontrollerremoved = removed; - webvr.oncontrollerpressed = pressed; - webvr.oncontrollerreleased = released; - webvr.onmount = mount; + webvrDestroy: function() { + webvr.destroy(); }, webvrGetType: function() { @@ -215,27 +212,34 @@ var LibraryLOVR = { var isLeft = eye === C.EYE_LEFT; var sittingToStanding = webvr.display.stageParameters && webvr.display.stageParameters.sittingToStandingTransform; var eyeParameters = webvr.display.getEyeParameters(isLeft ? 'left' : 'right'); + var pose = webvr.frameData.pose; var matA = webvr.matA; var matB = webvr.matB; var quat = webvr.quat; - if (sittingToStanding) { - HEAPF32.set(sittingToStanding, matA >> 2); - HEAPF32.set(isLeft ? webvr.frameData.leftViewMatrix : webvr.frameData.rightViewMatrix, matB >> 2); - Module._mat4_invert(matB); - Module._mat4_multiply(matA, matB); + if (pose.position && pose.orientation) { + HEAPF32.set(pose.orientation, quat >> 2); + + Module._mat4_identity(matA); + + if (sittingToStanding) { + HEAPF32.set(sittingToStanding, matB >> 2); + Module._mat4_multiply(matA, matB); + } + + Module._mat4_translate(matA, pose.position[0], pose.position[1], pose.position[2]); + Module._mat4_rotateQuat(matA, quat); + Module._mat4_translate(matA, eyeParameters.offset[0], eyeParameters.offset[1], eyeParameters.offset[2]); + + HEAPF32[x >> 2] = HEAPF32[y >> 2] = HEAPF32[z >> 2] = 0; + Module._mat4_transform(matA, x, y, z); + + Module._quat_fromMat4(quat, matA); + Module._quat_getAngleAxis(quat, angle, ax, ay, az); } else { - HEAPF32.set(isLeft ? webvr.frameData.leftViewMatrix : webvr.frameData.rightViewMatrix, matA >> 2); - Module._mat4_invert(matA); + HEAPF32[x >> 2] = HEAPF32[y >> 2] = HEAPF32[z >> 2] = 0; + HEAPF32[angle >> 2] = HEAPF32[ax >> 2] = HEAPF32[ay >> 2] = HEAPF32[az >> 2] = 0; } - - Module._mat4_translate(matA, eyeParameters.offset[0], eyeParameters.offset[1], eyeParameters.offset[2]); - HEAPF32[x >> 2] = matA[12]; - HEAPF32[y >> 2] = matA[13]; - HEAPF32[z >> 2] = matA[14]; - - Module._quat_fromMat4(quat, matA); - Module._quat_getAngleAxis(quat, angle, ax, ay, az); }, webvrGetVelocity: function(x, y, z) { @@ -337,6 +341,13 @@ var LibraryLOVR = { case C.CONTROLLER_AXIS_TOUCHPAD_Y: return gamepad.axes[1]; default: return 0; } + } else if (gamepad.id.startsWith('Spatial Controller')) { + switch (axis) { + case C.CONTROLLER_AXIS_TRIGGER: return gamepad.buttons[1].value; + case C.CONTROLLER_AXIS_TOUCHPAD_X: return gamepad.axes[2]; + case C.CONTROLLER_AXIS_TOUCHPAD_Y: return gamepad.axes[3]; + default: return 0; + } } return 0; @@ -344,21 +355,21 @@ var LibraryLOVR = { webvrControllerIsDown: function(controller, button) { var gamepad = webvr.controllerToGamepad(controller); - var buttonMap = webvr.buttonMap; - return gamepad && buttonMap[gamepad.id] && buttonMap[gamepad.id][button] && gamepad.buttons[buttonMap[gamepad.id][button]].pressed; + var buttonMap = webvr.buttonMap[gamepad.id] || webvr.buttonMap[gamepad.id.substr(0, 18)]; // substr for Spatial Controller prefix + return gamepad && buttonMap && buttonMap[button] && gamepad.buttons[buttonMap[button]].pressed; }, webvrControllerIsTouched: function(controller, button) { var gamepad = webvr.controllerToGamepad(controller); - var buttonMap = webvr.buttonMap; - return gamepad && buttonMap[gamepad.id] && buttonMap[gamepad.id][button] && gamepad.buttons[buttonMap[gamepad.id][button]].touched; + var buttonMap = webvr.buttonMap[gamepad.id] || webvr.buttonMap[gamepad.id.substr(0, 18)]; // substr for Spatial Controller prefix + return gamepad && buttonMap && buttonMap[button] && gamepad.buttons[buttonMap[button]].touched; }, webvrControllerVibrate: function(controller, duration, power) { var gamepad = webvr.controllerToGamepad(controller); if (gamepad && gamepad.hapticActuators && gamepad.hapticActuators[0]) { - gamepad.hapticActuators[0].pulse(power, duration); + gamepad.hapticActuators[0].pulse(power, duration * 1000); } }, @@ -372,24 +383,64 @@ var LibraryLOVR = { }, webvrUpdate: function(dt) { + var gamepads = navigator.getGamepads(); + + // Poll for new gamepads + for (var i = 0; i < gamepads.length; i++) { + var gamepad = gamepads[i]; + if (gamepad && gamepad.pose && !webvr.gamepads[gamepad.index] && gamepad.displayId === webvr.display.displayId && gamepad.pose.position && gamepad.pose.orientation) { + webvr.gamepads[gamepad.index] = gamepad; + webvr.lastGamepadState[gamepad.index] = []; + for (var i = 0; i < gamepad.buttons.length; i++) { + webvr.lastGamepadState[gamepad.index][i] = gamepad.buttons[i].pressed; + } + webvr.controlleradded && Runtime.dynCall('vi', webvr.controlleradded, [gamepad.index]); + } + } + + // Process existing gamepads, checking for disconnection and button state changes for (var index in webvr.gamepads) { var gamepad = webvr.gamepads[index]; - var lastState = webvr.lastGamepadState[index]; - var buttonMap = webvr.buttonMap[gamepad.id]; - for (var button in buttonMap) { - if (buttonMap[button]) { - var pressed = gamepad.buttons[buttonMap[button]].pressed; - if (lastState[buttonIndex] !== pressed) { - lastState[buttonIndex] = pressed; - if (pressed) { - Runtime.dynCall('vii', webvr.oncontrollerpressed, [gamepad.index, button]); - } else { - Runtime.dynCall('vii', webvr.oncontrollerreleased, [gamepad.index, button]); + if (!gamepad.connected || !gamepad.pose.position || !gamepad.pose.orientation) { + webvr.controllerremoved && Runtime.dynCall('vi', webvr.controllerremoved, [gamepad.index]); + delete webvr.lastGamepadState[gamepad.index]; + delete webvr.gamepads[gamepad.index]; + } else { + var lastState = webvr.lastGamepadState[index]; + var buttonMap = webvr.buttonMap[gamepad.id] || webvr.buttonMap[gamepad.id.substr(0, 18)]; // substr for Spatial Controller prefix + for (var button in buttonMap) { + var buttonIndex = buttonMap[button]; + if (buttonIndex !== null) { + var pressed = gamepad.buttons[buttonIndex].pressed; + if (lastState[buttonIndex] !== pressed) { + lastState[buttonIndex] = pressed; + if (pressed) { + Runtime.dynCall('vii', webvr.controllerpressed, [gamepad.index, button]); + } else { + Runtime.dynCall('vii', webvr.controllerreleased, [gamepad.index, button]); + } } } } } } + }, + + $C: { + NULL: 0, + ORIGIN_HEAD: 0, + ORIGIN_FLOOR: 1, + HEADSET_UNKNOWN: 0, + EYE_LEFT: 0, + EYE_RIGHT: 1, + HAND_UNKNOWN: 0, + HAND_LEFT: 1, + HAND_RIGHT: 2, + CONTROLLER_AXIS_TRIGGER: 0, + CONTROLLER_AXIS_GRIP: 1, + CONTROLLER_AXIS_TOUCHPAD_X: 2, + CONTROLLER_AXIS_TOUCHPAD_Y: 3, + sizeofRef: 8 } };