Merge branch 'master' into fake-headset

This commit is contained in:
Ben Campbell 2017-10-23 09:32:32 +13:00
commit a22a9ed1f4
51 changed files with 2001 additions and 1526 deletions

View File

@ -27,8 +27,9 @@ set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "")
set(ASSIMP_NO_EXPORT ON OFF CACHE BOOL "") set(ASSIMP_NO_EXPORT ON OFF CACHE BOOL "")
set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "") set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "")
set(ASSIMP_BUILD_COLLADA_IMPORTER ON CACHE BOOL "") set(ASSIMP_BUILD_COLLADA_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_FBX_IMPORTER ON CACHE BOOL "") set(ASSIMP_BUILD_FBX_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "")
set(ASSIMP_BUILD_OBJ_IMPORTER ON CACHE BOOL "")
if(EMSCRIPTEN) if(EMSCRIPTEN)
set(ZLIB_FOUND 1) set(ZLIB_FOUND 1)
set(ZLIB_LIBRARIES "-s USE_ZLIB=1") set(ZLIB_LIBRARIES "-s USE_ZLIB=1")
@ -197,11 +198,11 @@ set(PHYSFS_BUILD_WX_TEST FALSE CACHE BOOL "")
if(EMSCRIPTEN) if(EMSCRIPTEN)
option(PHYSFS_ARCHIVE_ZIP OFF) option(PHYSFS_ARCHIVE_ZIP OFF)
add_subdirectory(deps/physfs physfs) add_subdirectory(deps/physfs physfs)
include_directories(deps/physfs) include_directories(deps/physfs/src)
set(LOVR_PHYSFS physfs) set(LOVR_PHYSFS physfs)
elseif(WIN32) elseif(WIN32)
add_subdirectory(deps/physfs physfs) add_subdirectory(deps/physfs physfs)
include_directories(deps/physfs) include_directories(deps/physfs/src)
set(LOVR_PHYSFS physfs) set(LOVR_PHYSFS physfs)
else() else()
find_package(PhysFS REQUIRED) find_package(PhysFS REQUIRED)
@ -224,12 +225,12 @@ set(LOVR_SRC
src/api/types/controller.c src/api/types/controller.c
src/api/types/font.c src/api/types/font.c
src/api/types/joints.c src/api/types/joints.c
src/api/types/material.c
src/api/types/mesh.c src/api/types/mesh.c
src/api/types/model.c src/api/types/model.c
src/api/types/randomGenerator.c src/api/types/randomGenerator.c
src/api/types/shader.c src/api/types/shader.c
src/api/types/shapes.c src/api/types/shapes.c
src/api/types/skybox.c
src/api/types/source.c src/api/types/source.c
src/api/types/texture.c src/api/types/texture.c
src/api/types/transform.c src/api/types/transform.c
@ -238,13 +239,15 @@ set(LOVR_SRC
src/audio/source.c src/audio/source.c
src/event/event.c src/event/event.c
src/filesystem/blob.c src/filesystem/blob.c
src/filesystem/file.c
src/filesystem/filesystem.c src/filesystem/filesystem.c
src/graphics/font.c src/graphics/font.c
src/graphics/graphics.c src/graphics/graphics.c
src/graphics/material.c
src/graphics/mesh.c src/graphics/mesh.c
src/graphics/model.c src/graphics/model.c
src/graphics/shader.c src/graphics/shader.c
src/graphics/skybox.c src/graphics/shaders.c
src/graphics/texture.c src/graphics/texture.c
src/headset/fake.c src/headset/fake.c
src/headset/headset.c src/headset/headset.c
@ -259,6 +262,7 @@ set(LOVR_SRC
src/lib/stb/stb_vorbis.c src/lib/stb/stb_vorbis.c
src/lib/vec/vec.c src/lib/vec/vec.c
src/loaders/font.c src/loaders/font.c
src/loaders/material.c
src/loaders/model.c src/loaders/model.c
src/loaders/source.c src/loaders/source.c
src/loaders/texture.c src/loaders/texture.c
@ -306,6 +310,10 @@ target_link_libraries(lovr
) )
if(WIN32) if(WIN32)
set_target_properties(lovr PROPERTIES COMPILE_FLAGS "/wd4244")
set_target_properties(lovr PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(lovr PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
function(move_dll ARG_TARGET) function(move_dll ARG_TARGET)
add_custom_command(TARGET lovr POST_BUILD add_custom_command(TARGET lovr POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy COMMAND ${CMAKE_COMMAND} -E copy

View File

@ -75,12 +75,20 @@ lovr /path/to/myGame
Linux Linux
--- ---
On Arch Linux, first install necessary dependencies: First, install the dependencies using your package manager of choice.
#### Arch Linux
```sh ```sh
pacman -S assimp glfw-x11 luajit physfs freetype2 openal ode pacman -S assimp glfw-x11 luajit physfs freetype2 openal ode
``` ```
#### Debian/Ubuntu
```sh
sudo apt-get install build-essential cmake libassimp-dev libglfw3-dev libluajit-5.1-dev libphysfs-dev libfreetype6-dev libopenal-dev libode-dev
```
Then, build with CMake: Then, build with CMake:
```sh ```sh

2
deps/physfs vendored

@ -1 +1 @@
Subproject commit abb9a4c8b6d16389ca5a519c18b25f34249c3426 Subproject commit 68beaa110991cb5002c8dc3a3d61718845046e79

View File

@ -23,11 +23,12 @@ Blob* luax_readblob(lua_State* L, int index, const char* debug) {
} }
} }
static void pushDirectoryItem(void* userdata, const char* path, const char* filename) { static int pushDirectoryItem(void* userdata, const char* path, const char* filename) {
lua_State* L = userdata; lua_State* L = userdata;
int n = lua_objlen(L, -1); int n = lua_objlen(L, -1);
lua_pushstring(L, filename); lua_pushstring(L, filename);
lua_rawseti(L, -2, n + 1); lua_rawseti(L, -2, n + 1);
return 1;
} }
// Loader to help Lua's require understand PhysFS. // Loader to help Lua's require understand PhysFS.
@ -122,12 +123,6 @@ int l_lovrFilesystemCreateDirectory(lua_State* L) {
return 1; return 1;
} }
int l_lovrFilesystemExists(lua_State* L) {
const char* path = luaL_checkstring(L, 1);
lua_pushboolean(L, lovrFilesystemExists(path));
return 1;
}
int l_lovrFilesystemGetAppdataDirectory(lua_State* L) { int l_lovrFilesystemGetAppdataDirectory(lua_State* L) {
char buffer[1024]; char buffer[1024];
@ -195,7 +190,11 @@ int l_lovrFilesystemGetSaveDirectory(lua_State* L) {
int l_lovrFilesystemGetSize(lua_State* L) { int l_lovrFilesystemGetSize(lua_State* L) {
const char* path = luaL_checkstring(L, 1); const char* path = luaL_checkstring(L, 1);
lua_pushinteger(L, lovrFilesystemGetSize(path)); size_t size = lovrFilesystemGetSize(path);
if ((int) size == -1) {
return luaL_error(L, "File does not exist");
}
lua_pushinteger(L, size);
return 1; return 1;
} }
@ -322,7 +321,6 @@ int l_lovrFilesystemWrite(lua_State* L) {
const luaL_Reg lovrFilesystem[] = { const luaL_Reg lovrFilesystem[] = {
{ "append", l_lovrFilesystemAppend }, { "append", l_lovrFilesystemAppend },
{ "createDirectory", l_lovrFilesystemCreateDirectory }, { "createDirectory", l_lovrFilesystemCreateDirectory },
{ "exists", l_lovrFilesystemExists },
{ "getAppdataDirectory", l_lovrFilesystemGetAppdataDirectory }, { "getAppdataDirectory", l_lovrFilesystemGetAppdataDirectory },
{ "getDirectoryItems", l_lovrFilesystemGetDirectoryItems }, { "getDirectoryItems", l_lovrFilesystemGetDirectoryItems },
{ "getExecutablePath", l_lovrFilesystemGetExecutablePath }, { "getExecutablePath", l_lovrFilesystemGetExecutablePath },

View File

@ -1,8 +1,10 @@
#include "api/lovr.h" #include "api/lovr.h"
#include "graphics/graphics.h" #include "graphics/graphics.h"
#include "graphics/material.h"
#include "graphics/mesh.h" #include "graphics/mesh.h"
#include "graphics/model.h" #include "graphics/model.h"
#include "loaders/font.h" #include "loaders/font.h"
#include "loaders/material.h"
#include "loaders/model.h" #include "loaders/model.h"
#include "loaders/texture.h" #include "loaders/texture.h"
#include "filesystem/filesystem.h" #include "filesystem/filesystem.h"
@ -14,6 +16,8 @@ map_int_t CompareModes;
map_int_t DrawModes; map_int_t DrawModes;
map_int_t FilterModes; map_int_t FilterModes;
map_int_t HorizontalAligns; map_int_t HorizontalAligns;
map_int_t MaterialColors;
map_int_t MaterialTextures;
map_int_t MatrixTypes; map_int_t MatrixTypes;
map_int_t MeshAttributeTypes; map_int_t MeshAttributeTypes;
map_int_t MeshDrawModes; map_int_t MeshDrawModes;
@ -63,24 +67,16 @@ static void luax_readvertices(lua_State* L, int index, vec_float_t* points) {
} }
} }
static Texture* luax_readtexture(lua_State* L, int index) {
Blob* blob = luax_readblob(L, index, "Texture");
TextureData* textureData = lovrTextureDataFromBlob(blob);
Texture* texture = lovrTextureCreate(textureData);
lovrRelease(&blob->ref);
return texture;
}
// Base // Base
int l_lovrGraphicsInit(lua_State* L) { int l_lovrGraphicsInit(lua_State* L) {
lua_newtable(L); lua_newtable(L);
luaL_register(L, NULL, lovrGraphics); luaL_register(L, NULL, lovrGraphics);
luax_registertype(L, "Font", lovrFont); luax_registertype(L, "Font", lovrFont);
luax_registertype(L, "Material", lovrMaterial);
luax_registertype(L, "Mesh", lovrMesh); luax_registertype(L, "Mesh", lovrMesh);
luax_registertype(L, "Model", lovrModel); luax_registertype(L, "Model", lovrModel);
luax_registertype(L, "Shader", lovrShader); luax_registertype(L, "Shader", lovrShader);
luax_registertype(L, "Skybox", lovrSkybox);
luax_registertype(L, "Texture", lovrTexture); luax_registertype(L, "Texture", lovrTexture);
map_init(&BlendAlphaModes); map_init(&BlendAlphaModes);
@ -120,6 +116,13 @@ int l_lovrGraphicsInit(lua_State* L) {
map_set(&HorizontalAligns, "right", ALIGN_RIGHT); map_set(&HorizontalAligns, "right", ALIGN_RIGHT);
map_set(&HorizontalAligns, "center", ALIGN_CENTER); map_set(&HorizontalAligns, "center", ALIGN_CENTER);
map_init(&MaterialColors);
map_set(&MaterialColors, "diffuse", COLOR_DIFFUSE);
map_init(&MaterialTextures);
map_set(&MaterialTextures, "diffuse", TEXTURE_DIFFUSE);
map_set(&MaterialTextures, "environment", TEXTURE_ENVIRONMENT_MAP);
map_init(&MatrixTypes); map_init(&MatrixTypes);
map_set(&MatrixTypes, "model", MATRIX_MODEL); map_set(&MatrixTypes, "model", MATRIX_MODEL);
map_set(&MatrixTypes, "view", MATRIX_VIEW); map_set(&MatrixTypes, "view", MATRIX_VIEW);
@ -253,26 +256,7 @@ int l_lovrGraphicsGetColor(lua_State* L) {
} }
int l_lovrGraphicsSetColor(lua_State* L) { int l_lovrGraphicsSetColor(lua_State* L) {
Color color = { 0xff, 0xff, 0xff, 0xff }; Color color = luax_checkcolor(L, 1);
if (lua_istable(L, 1)) {
for (int i = 1; i <= 4; i++) {
lua_rawgeti(L, 1, i);
}
color.r = luaL_checknumber(L, -4);
color.g = luaL_checknumber(L, -3);
color.b = luaL_checknumber(L, -2);
color.a = luaL_optnumber(L, -1, 255);
lua_pop(L, 4);
} else if (lua_gettop(L) >= 3) {
color.r = lua_tointeger(L, 1);
color.g = lua_tointeger(L, 2);
color.b = lua_tointeger(L, 3);
color.a = lua_isnoneornil(L, 4) ? 255 : lua_tointeger(L, 4);
} else {
return luaL_error(L, "Invalid color, expected 3 numbers, 4 numbers, or a table");
}
lovrGraphicsSetColor(color); lovrGraphicsSetColor(color);
return 0; return 0;
} }
@ -357,6 +341,22 @@ int l_lovrGraphicsSetLineWidth(lua_State* L) {
return 0; return 0;
} }
int l_lovrGraphicsGetMaterial(lua_State* L) {
Material* material = lovrGraphicsGetMaterial();
if (material && !material->isDefault) {
luax_pushtype(L, Material, material);
} else {
lua_pushnil(L);
}
return 1;
}
int l_lovrGraphicsSetMaterial(lua_State* L) {
Material* material = lua_isnoneornil(L, 1) ? NULL : luax_checktype(L, 1, Material);
lovrGraphicsSetMaterial(material);
return 0;
}
int l_lovrGraphicsGetPointSize(lua_State* L) { int l_lovrGraphicsGetPointSize(lua_State* L) {
lua_pushnumber(L, lovrGraphicsGetPointSize()); lua_pushnumber(L, lovrGraphicsGetPointSize());
return 1; return 1;
@ -493,36 +493,24 @@ int l_lovrGraphicsTriangle(lua_State* L) {
} }
int l_lovrGraphicsPlane(lua_State* L) { int l_lovrGraphicsPlane(lua_State* L) {
Texture* texture = NULL; if (lua_isuserdata(L, 1)) {
DrawMode drawMode; Texture* texture = luax_checktype(L, 1, Texture);
if (lua_isstring(L, 1)) { lovrGraphicsPlaneFullscreen(texture);
drawMode = *(DrawMode*) luax_checkenum(L, 1, &DrawModes, "draw mode"); return 0;
} else {
drawMode = DRAW_MODE_FILL;
texture = luax_checktype(L, 1, Texture);
if (lua_gettop(L) == 1) {
lovrGraphicsPlaneFullscreen(texture);
return 0;
}
} }
DrawMode drawMode = *(DrawMode*) luax_checkenum(L, 1, &DrawModes, "draw mode");
float transform[16]; float transform[16];
luax_readtransform(L, 2, transform, 1); luax_readtransform(L, 2, transform, 1);
lovrGraphicsPlane(drawMode, texture, transform); lovrGraphicsPlane(drawMode, transform);
return 0; return 0;
} }
static int luax_rectangularprism(lua_State* L, int uniformScale) { static int luax_rectangularprism(lua_State* L, int uniformScale) {
Texture* texture = NULL; DrawMode drawMode = *(DrawMode*) luax_checkenum(L, 1, &DrawModes, "draw mode");
DrawMode drawMode;
if (lua_isstring(L, 1)) {
drawMode = *(DrawMode*) luax_checkenum(L, 1, &DrawModes, "draw mode");
} else {
drawMode = DRAW_MODE_FILL;
texture = luax_checktype(L, 1, Texture);
}
float transform[16]; float transform[16];
luax_readtransform(L, 2, transform, uniformScale); luax_readtransform(L, 2, transform, uniformScale);
lovrGraphicsBox(drawMode, texture, transform); lovrGraphicsBox(drawMode, transform);
return 0; return 0;
} }
@ -550,15 +538,21 @@ int l_lovrGraphicsCylinder(lua_State* L) {
} }
int l_lovrGraphicsSphere(lua_State* L) { int l_lovrGraphicsSphere(lua_State* L) {
Texture* texture = NULL;
float transform[16]; float transform[16];
int index = 1; int index = 1;
if (lua_isuserdata(L, 1) && (lua_isuserdata(L, 2) || lua_isnumber(L, 2))) {
texture = luax_checktype(L, index++, Texture);
}
index = luax_readtransform(L, index, transform, 1); index = luax_readtransform(L, index, transform, 1);
int segments = luaL_optnumber(L, index, 30); int segments = luaL_optnumber(L, index, 30);
lovrGraphicsSphere(texture, transform, segments, NULL); lovrGraphicsSphere(transform, segments);
return 0;
}
int l_lovrGraphicsSkybox(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture);
float angle = luaL_optnumber(L, 2, 0);
float ax = luaL_optnumber(L, 3, 0);
float ay = luaL_optnumber(L, 4, 1);
float az = luaL_optnumber(L, 5, 0);
lovrGraphicsSkybox(texture, angle, ax, ay, az);
return 0; return 0;
} }
@ -598,6 +592,13 @@ int l_lovrGraphicsNewFont(lua_State* L) {
return 1; return 1;
} }
int l_lovrGraphicsNewMaterial(lua_State* L) {
MaterialData* materialData = lovrMaterialDataCreateEmpty();
Material* material = lovrMaterialCreate(materialData, 0);
luax_pushtype(L, Material, material);
return 1;
}
int l_lovrGraphicsNewMesh(lua_State* L) { int l_lovrGraphicsNewMesh(lua_State* L) {
int size; int size;
int dataIndex = 0; int dataIndex = 0;
@ -671,13 +672,6 @@ int l_lovrGraphicsNewModel(lua_State* L) {
Blob* blob = luax_readblob(L, 1, "Model"); Blob* blob = luax_readblob(L, 1, "Model");
ModelData* modelData = lovrModelDataCreate(blob); ModelData* modelData = lovrModelDataCreate(blob);
Model* model = lovrModelCreate(modelData); Model* model = lovrModelCreate(modelData);
if (lua_gettop(L) >= 2) {
Texture* texture = luax_readtexture(L, 2);
lovrModelSetTexture(model, texture);
lovrRelease(&texture->ref);
}
luax_pushtype(L, Model, model); luax_pushtype(L, Model, model);
lovrRelease(&model->ref); lovrRelease(&model->ref);
lovrRelease(&blob->ref); lovrRelease(&blob->ref);
@ -707,51 +701,6 @@ int l_lovrGraphicsNewShader(lua_State* L) {
return 1; return 1;
} }
int l_lovrGraphicsNewSkybox(lua_State* L) {
Blob* blobs[6] = { NULL };
SkyboxType type;
if (lua_gettop(L) == 1 && lua_type(L, 1) == LUA_TSTRING) {
type = SKYBOX_PANORAMA;
blobs[0] = luax_readblob(L, 1, "Skybox");
} else if (lua_istable(L, 1)) {
if (lua_objlen(L, 1) != 6) {
return luaL_argerror(L, 1, "Expected 6 strings or a table containing 6 strings");
}
for (int i = 0; i < 6; i++) {
lua_rawgeti(L, 1, i + 1);
if (!lua_isstring(L, -1)) {
return luaL_argerror(L, 1, "Expected 6 strings or a table containing 6 strings");
}
blobs[i] = luax_readblob(L, -1, "Skybox");
lua_pop(L, 1);
}
type = SKYBOX_CUBE;
} else {
for (int i = 0; i < 6; i++) {
blobs[i] = luax_readblob(L, i + 1, "Skybox");
}
type = SKYBOX_CUBE;
}
Skybox* skybox = lovrSkyboxCreate(blobs, type);
luax_pushtype(L, Skybox, skybox);
lovrRelease(&skybox->ref);
for (int i = 0; i < 6; i++) {
if (blobs[i]) {
lovrRelease(&blobs[i]->ref);
}
}
return 1;
}
int l_lovrGraphicsNewTexture(lua_State* L) { int l_lovrGraphicsNewTexture(lua_State* L) {
Texture* texture; Texture* texture;
@ -763,7 +712,34 @@ int l_lovrGraphicsNewTexture(lua_State* L) {
TextureData* textureData = lovrTextureDataGetEmpty(width, height, FORMAT_RGBA); TextureData* textureData = lovrTextureDataGetEmpty(width, height, FORMAT_RGBA);
texture = lovrTextureCreateWithFramebuffer(textureData, *projection, msaa); texture = lovrTextureCreateWithFramebuffer(textureData, *projection, msaa);
} else { } else {
texture = luax_readtexture(L, 1); Blob* blobs[6];
int isTable = lua_istable(L, 1);
int count = isTable ? lua_objlen(L, 1) : lua_gettop(L);
if (count != 1 && count != 6) {
return luaL_error(L, "Expected 1 image for a 2D texture or 6 images for a cube texture, got %d", count);
}
if (isTable) {
for (int i = 0; i < count; i++) {
lua_rawgeti(L, -1, i + 1);
blobs[i] = luax_readblob(L, -1, "Texture");
lua_pop(L, 1);
}
} else {
for (int i = 0; i < count; i++) {
blobs[i] = luax_readblob(L, i + 1, "Texture");
}
}
TextureData* slices[6];
for (int i = 0; i < count; i++) {
slices[i] = lovrTextureDataFromBlob(blobs[i]);
lovrRelease(&blobs[i]->ref);
}
TextureType type = (count == 1) ? TEXTURE_2D : TEXTURE_CUBE;
texture = lovrTextureCreate(type, slices, count);
} }
luax_pushtype(L, Texture, texture); luax_pushtype(L, Texture, texture);
@ -796,6 +772,8 @@ const luaL_Reg lovrGraphics[] = {
{ "getSystemLimits", l_lovrGraphicsGetSystemLimits }, { "getSystemLimits", l_lovrGraphicsGetSystemLimits },
{ "getLineWidth", l_lovrGraphicsGetLineWidth }, { "getLineWidth", l_lovrGraphicsGetLineWidth },
{ "setLineWidth", l_lovrGraphicsSetLineWidth }, { "setLineWidth", l_lovrGraphicsSetLineWidth },
{ "getMaterial", l_lovrGraphicsGetMaterial },
{ "setMaterial", l_lovrGraphicsSetMaterial },
{ "getPointSize", l_lovrGraphicsGetPointSize }, { "getPointSize", l_lovrGraphicsGetPointSize },
{ "setPointSize", l_lovrGraphicsSetPointSize }, { "setPointSize", l_lovrGraphicsSetPointSize },
{ "getShader", l_lovrGraphicsGetShader }, { "getShader", l_lovrGraphicsGetShader },
@ -819,12 +797,13 @@ const luaL_Reg lovrGraphics[] = {
{ "box", l_lovrGraphicsBox }, { "box", l_lovrGraphicsBox },
{ "cylinder", l_lovrGraphicsCylinder }, { "cylinder", l_lovrGraphicsCylinder },
{ "sphere", l_lovrGraphicsSphere }, { "sphere", l_lovrGraphicsSphere },
{ "skybox", l_lovrGraphicsSkybox },
{ "print", l_lovrGraphicsPrint }, { "print", l_lovrGraphicsPrint },
{ "newFont", l_lovrGraphicsNewFont }, { "newFont", l_lovrGraphicsNewFont },
{ "newMaterial", l_lovrGraphicsNewMaterial },
{ "newMesh", l_lovrGraphicsNewMesh }, { "newMesh", l_lovrGraphicsNewMesh },
{ "newModel", l_lovrGraphicsNewModel }, { "newModel", l_lovrGraphicsNewModel },
{ "newShader", l_lovrGraphicsNewShader }, { "newShader", l_lovrGraphicsNewShader },
{ "newSkybox", l_lovrGraphicsNewSkybox },
{ "newTexture", l_lovrGraphicsNewTexture }, { "newTexture", l_lovrGraphicsNewTexture },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -31,6 +31,7 @@ extern const luaL_Reg lovrGraphics[];
extern const luaL_Reg lovrHeadset[]; extern const luaL_Reg lovrHeadset[];
extern const luaL_Reg lovrHingeJoint[]; extern const luaL_Reg lovrHingeJoint[];
extern const luaL_Reg lovrJoint[]; extern const luaL_Reg lovrJoint[];
extern const luaL_Reg lovrMaterial[];
extern const luaL_Reg lovrMath[]; extern const luaL_Reg lovrMath[];
extern const luaL_Reg lovrMesh[]; extern const luaL_Reg lovrMesh[];
extern const luaL_Reg lovrModel[]; extern const luaL_Reg lovrModel[];
@ -38,7 +39,6 @@ extern const luaL_Reg lovrPhysics[];
extern const luaL_Reg lovrRandomGenerator[]; extern const luaL_Reg lovrRandomGenerator[];
extern const luaL_Reg lovrShader[]; extern const luaL_Reg lovrShader[];
extern const luaL_Reg lovrShape[]; extern const luaL_Reg lovrShape[];
extern const luaL_Reg lovrSkybox[];
extern const luaL_Reg lovrSliderJoint[]; extern const luaL_Reg lovrSliderJoint[];
extern const luaL_Reg lovrSource[]; extern const luaL_Reg lovrSource[];
extern const luaL_Reg lovrSphereShape[]; extern const luaL_Reg lovrSphereShape[];
@ -61,6 +61,8 @@ extern map_int_t HeadsetOrigins;
extern map_int_t HeadsetTypes; extern map_int_t HeadsetTypes;
extern map_int_t HorizontalAligns; extern map_int_t HorizontalAligns;
extern map_int_t JointTypes; extern map_int_t JointTypes;
extern map_int_t MaterialColors;
extern map_int_t MaterialTextures;
extern map_int_t MatrixTypes; extern map_int_t MatrixTypes;
extern map_int_t MeshAttributeTypes; extern map_int_t MeshAttributeTypes;
extern map_int_t MeshDrawModes; extern map_int_t MeshDrawModes;

View File

@ -70,14 +70,8 @@ int l_lovrControllerVibrate(lua_State* L) {
int l_lovrControllerNewModel(lua_State* L) { int l_lovrControllerNewModel(lua_State* L) {
Controller* controller = luax_checktype(L, 1, Controller); Controller* controller = luax_checktype(L, 1, Controller);
ModelData* modelData = lovrHeadsetControllerNewModelData(controller); ModelData* modelData = lovrHeadsetControllerNewModelData(controller);
TextureData* textureData = lovrHeadsetControllerNewTextureData(controller);
if (modelData) { if (modelData) {
Model* model = lovrModelCreate(modelData); Model* model = lovrModelCreate(modelData);
if (textureData) {
Texture* texture = lovrTextureCreate(textureData);
lovrModelSetTexture(model, texture);
lovrRelease(&texture->ref);
}
luax_pushtype(L, Model, model); luax_pushtype(L, Model, model);
lovrRelease(&model->ref); lovrRelease(&model->ref);
} else { } else {

55
src/api/types/material.c Normal file
View File

@ -0,0 +1,55 @@
#include "api/lovr.h"
#include "graphics/material.h"
int l_lovrMaterialGetColor(lua_State* L) {
Material* material = luax_checktype(L, 1, Material);
MaterialColor colorType = *(MaterialColor*) luax_optenum(L, 2, "diffuse", &MaterialColors, "color");
Color color = lovrMaterialGetColor(material, colorType);
lua_pushinteger(L, color.r);
lua_pushinteger(L, color.g);
lua_pushinteger(L, color.b);
lua_pushinteger(L, color.a);
return 4;
}
int l_lovrMaterialSetColor(lua_State* L) {
Material* material = luax_checktype(L, 1, Material);
MaterialColor colorType = COLOR_DIFFUSE;
int index = 2;
if (lua_type(L, index) == LUA_TSTRING) {
colorType = *(MaterialColor*) luax_checkenum(L, index, &MaterialColors, "color");
index++;
}
Color color = luax_checkcolor(L, index);
lovrMaterialSetColor(material, colorType, color);
return 0;
}
int l_lovrMaterialGetTexture(lua_State* L) {
Material* material = luax_checktype(L, 1, Material);
MaterialTexture textureType = *(MaterialTexture*) luax_optenum(L, 2, "diffuse", &MaterialTextures, "texture");
Texture* texture = lovrMaterialGetTexture(material, textureType);
luax_pushtype(L, Texture, texture);
return 1;
}
int l_lovrMaterialSetTexture(lua_State* L) {
Material* material = luax_checktype(L, 1, Material);
MaterialTexture textureType = TEXTURE_DIFFUSE;
int index = 2;
if (lua_type(L, index) == LUA_TSTRING) {
textureType = *(MaterialTexture*) luax_checkenum(L, index, &MaterialTextures, "texture");
index++;
}
Texture* texture = lua_isnoneornil(L, index) ? NULL : luax_checktype(L, index, Texture);
lovrMaterialSetTexture(material, textureType, texture);
return 0;
}
const luaL_Reg lovrMaterial[] = {
{ "getColor", l_lovrMaterialGetColor },
{ "setColor", l_lovrMaterialSetColor },
{ "getTexture", l_lovrMaterialGetTexture },
{ "setTexture", l_lovrMaterialSetTexture },
{ NULL, NULL }
};

View File

@ -273,27 +273,30 @@ int l_lovrMeshSetVertexMap(lua_State* L) {
luaL_checktype(L, 2, LUA_TTABLE); luaL_checktype(L, 2, LUA_TTABLE);
int count = lua_objlen(L, 2); int count = lua_objlen(L, 2);
unsigned int* indices = malloc(count * sizeof(unsigned int)); int indexSize = mesh->indexSize;
void* indices = realloc(lovrMeshGetVertexMap(mesh, NULL), indexSize * count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
lua_rawgeti(L, 2, i + 1); lua_rawgeti(L, 2, i + 1);
if (!lua_isnumber(L, -1)) { if (!lua_isnumber(L, -1)) {
free(indices);
return luaL_error(L, "Mesh vertex map index #%d must be numeric", i); return luaL_error(L, "Mesh vertex map index #%d must be numeric", i);
} }
int index = lua_tointeger(L, -1); int index = lua_tointeger(L, -1);
if (index > lovrMeshGetVertexCount(mesh) || index < 1) { if (index > lovrMeshGetVertexCount(mesh) || index < 1) {
free(indices);
return luaL_error(L, "Invalid vertex map value: %d", index); return luaL_error(L, "Invalid vertex map value: %d", index);
} }
indices[i] = index - 1; if (indexSize == sizeof(uint16_t)) {
*(((uint16_t*) indices) + i) = index - 1;
} else if (indexSize == sizeof(uint32_t)) {
*(((uint32_t*) indices) + i) = index - 1;
}
lua_pop(L, 1); lua_pop(L, 1);
} }
lovrMeshSetVertexMap(mesh, indices, count); lovrMeshSetVertexMap(mesh, indices, count);
free(indices);
return 0; return 0;
} }
@ -336,30 +339,7 @@ int l_lovrMeshSetDrawRange(lua_State* L) {
lovrMeshSetRangeEnabled(mesh, 1); lovrMeshSetRangeEnabled(mesh, 1);
int rangeStart = luaL_checkinteger(L, 2) - 1; int rangeStart = luaL_checkinteger(L, 2) - 1;
int rangeCount = luaL_checkinteger(L, 3); int rangeCount = luaL_checkinteger(L, 3);
if (lovrMeshSetDrawRange(mesh, rangeStart, rangeCount)) { lovrMeshSetDrawRange(mesh, rangeStart, rangeCount);
return luaL_error(L, "Invalid mesh draw range (%d, %d)", rangeStart + 1, rangeCount);
}
return 0;
}
int l_lovrMeshGetTexture(lua_State* L) {
Mesh* mesh = luax_checktype(L, 1, Mesh);
Texture* texture = lovrMeshGetTexture(mesh);
if (texture) {
luax_pushtype(L, Texture, texture);
} else {
lua_pushnil(L);
}
return 1;
}
int l_lovrMeshSetTexture(lua_State* L) {
Mesh* mesh = luax_checktype(L, 1, Mesh);
Texture* texture = lua_isnoneornil(L, 2) ? NULL : luax_checktype(L, 2, Texture);
lovrMeshSetTexture(mesh, texture);
return 0; return 0;
} }
@ -380,7 +360,5 @@ const luaL_Reg lovrMesh[] = {
{ "setDrawMode", l_lovrMeshSetDrawMode }, { "setDrawMode", l_lovrMeshSetDrawMode },
{ "getDrawRange", l_lovrMeshGetDrawRange }, { "getDrawRange", l_lovrMeshGetDrawRange },
{ "setDrawRange", l_lovrMeshSetDrawRange }, { "setDrawRange", l_lovrMeshSetDrawRange },
{ "getTexture", l_lovrMeshGetTexture },
{ "setTexture", l_lovrMeshSetTexture },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -9,33 +9,15 @@ int l_lovrModelDraw(lua_State* L) {
return 0; return 0;
} }
int l_lovrModelGetTexture(lua_State* L) { int l_lovrModelGetMesh(lua_State* L) {
Model* model = luax_checktype(L, 1, Model); Model* model = luax_checktype(L, 1, Model);
Texture* texture = lovrModelGetTexture(model); Mesh* mesh = lovrModelGetMesh(model);
luax_pushtype(L, Texture, texture); luax_pushtype(L, Mesh, mesh);
return 1; return 1;
} }
int l_lovrModelSetTexture(lua_State* L) {
Model* model = luax_checktype(L, 1, Model);
Texture* texture = luax_checktype(L, 2, Texture);
lovrModelSetTexture(model, texture);
return 0;
}
int l_lovrModelGetAABB(lua_State* L) {
Model* model = luax_checktype(L, 1, Model);
float* aabb = lovrModelGetAABB(model);
for (int i = 0; i < 6; i++) {
lua_pushnumber(L, aabb[i]);
}
return 6;
}
const luaL_Reg lovrModel[] = { const luaL_Reg lovrModel[] = {
{ "draw", l_lovrModelDraw }, { "draw", l_lovrModelDraw },
{ "getTexture", l_lovrModelGetTexture }, { "getMesh", l_lovrModelGetMesh },
{ "setTexture", l_lovrModelSetTexture },
{ "getAABB", l_lovrModelGetAABB },
{ NULL, NULL } { NULL, NULL }
}; };

View File

@ -3,163 +3,134 @@
#include "graphics/shader.h" #include "graphics/shader.h"
#include "math/transform.h" #include "math/transform.h"
struct TempData {
void* data;
size_t size;
};
static struct TempData tempData;
int l_lovrShaderSend(lua_State* L) { int l_lovrShaderSend(lua_State* L) {
Shader* shader = luax_checktype(L, 1, Shader); Shader* shader = luax_checktype(L, 1, Shader);
const char* name = luaL_checkstring(L, 2); const char* name = luaL_checkstring(L, 2);
lua_settop(L, 3); lua_settop(L, 3);
int id = lovrShaderGetUniformId(shader, name); Uniform* uniform = lovrShaderGetUniform(shader, name);
if (id == -1) {
if (!uniform) {
return luaL_error(L, "Unknown shader variable '%s'", name); return luaL_error(L, "Unknown shader variable '%s'", name);
} }
GLenum type; if (!tempData.data) {
int size; tempData.data = malloc(uniform->size);
lovrShaderGetUniformType(shader, name, &type, &size); } else if (tempData.size < uniform->size) {
lovrGraphicsBindProgram(shader->id); tempData.size = uniform->size;
float data[16]; tempData.data = realloc(tempData.data, tempData.size);
int n;
vec_float_t values;
vec_init(&values);
switch (type) {
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
case GL_INT:
lovrShaderSendInt(shader, id, luaL_checkinteger(L, 3));
break;
case GL_FLOAT:
lovrShaderSendFloat(shader, id, luaL_checknumber(L, 3));
break;
case GL_FLOAT_VEC2:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 2; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec2(shader, id, size, values.data);
break;
case GL_FLOAT_VEC3:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 3; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec3(shader, id, size, values.data);
break;
case GL_FLOAT_VEC4:
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n < size) {
return luaL_error(L, "Expected %d vec3s, got %d", size, n);
}
for (int i = 0; i < size; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < 4; j++) {
lua_rawgeti(L, -1, j + 1);
vec_push(&values, lua_tonumber(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lovrShaderSendFloatVec4(shader, id, size, values.data);
break;
case GL_FLOAT_MAT2:
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 4; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
lovrShaderSendFloatMat2(shader, id, data);
break;
case GL_FLOAT_MAT3:
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 9; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
lovrShaderSendFloatMat3(shader, id, data);
break;
case GL_FLOAT_MAT4:
if (lua_isuserdata(L, 3)) {
Transform* transform = luax_checktype(L, 3, Transform);
memcpy(data, transform->matrix, 16 * sizeof(float));
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < 16; i++) {
lua_rawgeti(L, 3, i + 1);
data[i] = lua_tonumber(L, -1);
lua_pop(L, 1);
}
}
lovrShaderSendFloatMat4(shader, id, data);
break;
default:
return luaL_error(L, "Unknown uniform type %d", type);
} }
vec_deinit(&values); int* ints = (int*) tempData.data;
float* floats = (float*) tempData.data;
Texture** textures = (Texture**) tempData.data;
int n = 1;
int components = uniform->components;
if (components > 1) {
luaL_checktype(L, 3, LUA_TTABLE);
lua_rawgeti(L, 3, 1);
if (!lua_istable(L, -1)) {
lua_newtable(L);
lua_pushvalue(L, 3);
lua_rawseti(L, -2, 1);
} else {
lua_pop(L, 1);
}
n = lua_objlen(L, -1);
if (n != uniform->count) {
const char* elements = uniform->count == 1 ? "element" : "elements";
return luaL_error(L, "Expected %d %s for array '%s', got %d", uniform->count, elements, uniform->name, n);
}
}
switch (uniform->type) {
case UNIFORM_FLOAT:
if (components == 1) {
floats[0] = luaL_checkinteger(L, 3);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
if (lua_type(L, -1) != LUA_TTABLE || (int) lua_objlen(L, -1) != components) {
return luaL_error(L, "Expected %d components for uniform '%s' #%d, got %d", components, uniform->name, lua_objlen(L, -1));
}
for (int j = 0; j < components; j++) {
lua_rawgeti(L, -1, j + 1);
floats[i * components + j] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetFloat(shader, name, floats, n);
break;
case UNIFORM_INT:
if (components == 1) {
ints[0] = luaL_checkinteger(L, 3);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
if (lua_type(L, -1) != LUA_TTABLE || (int) lua_objlen(L, -1) != components) {
return luaL_error(L, "Expected %d components for uniform '%s' #%d, got %d", components, uniform->name, lua_objlen(L, -1));
}
for (int j = 0; j < components; j++) {
lua_rawgeti(L, -1, j + 1);
ints[i * components + j] = luaL_checkinteger(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetInt(shader, name, ints, n);
break;
case UNIFORM_MATRIX:
if (components == 4 && lua_isuserdata(L, 3)) {
Transform* transform = luax_checktype(L, 3, Transform);
memcpy(floats, transform->matrix, 16 * sizeof(float));
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
for (int j = 0; j < components * components; j++) {
lua_rawgeti(L, -1, j + 1);
floats[i * components * components + j] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lovrShaderSetMatrix(shader, name, floats, n);
break;
case UNIFORM_SAMPLER:
if (components == 1) {
textures[0] = luax_checktype(L, 3, Texture);
} else {
luaL_checktype(L, 3, LUA_TTABLE);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
textures[i] = luax_checktype(L, -1, Texture);
lua_pop(L, 1);
}
}
lovrShaderSetTexture(shader, name, textures, n);
break;
default: break;
}
return 0; return 0;
} }

View File

@ -1,17 +0,0 @@
#include "api/lovr.h"
#include "graphics/graphics.h"
int l_lovrSkyboxDraw(lua_State* L) {
Skybox* skybox = luax_checktype(L, 1, Skybox);
float angle = luaL_optnumber(L, 2, 0.f);
float ax = luaL_optnumber(L, 3, 0.f);
float ay = luaL_optnumber(L, 4, 0.f);
float az = luaL_optnumber(L, 5, 0.f);
lovrGraphicsSkybox(skybox, angle, ax, ay, az);
return 0;
}
const luaL_Reg lovrSkybox[] = {
{ "draw", l_lovrSkyboxDraw },
{ NULL, NULL }
};

View File

@ -3,8 +3,8 @@
int l_lovrTextureGetDimensions(lua_State* L) { int l_lovrTextureGetDimensions(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
lua_pushnumber(L, lovrTextureGetWidth(texture)); lua_pushnumber(L, texture->width);
lua_pushnumber(L, lovrTextureGetHeight(texture)); lua_pushnumber(L, texture->height);
return 2; return 2;
} }
@ -21,33 +21,36 @@ int l_lovrTextureGetFilter(lua_State* L) {
int l_lovrTextureGetHeight(lua_State* L) { int l_lovrTextureGetHeight(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
lua_pushnumber(L, lovrTextureGetHeight(texture)); lua_pushnumber(L, texture->height);
return 1; return 1;
} }
int l_lovrTextureGetWidth(lua_State* L) { int l_lovrTextureGetWidth(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
lua_pushnumber(L, lovrTextureGetWidth(texture)); lua_pushnumber(L, texture->width);
return 1; return 1;
} }
int l_lovrTextureGetWrap(lua_State* L) { int l_lovrTextureGetWrap(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
WrapMode horizontal, vertical; TextureWrap wrap = lovrTextureGetWrap(texture);
lovrTextureGetWrap(texture, &horizontal, &vertical); luax_pushenum(L, &WrapModes, wrap.s);
luax_pushenum(L, &WrapModes, horizontal); luax_pushenum(L, &WrapModes, wrap.t);
luax_pushenum(L, &WrapModes, vertical); if (texture->type == TEXTURE_CUBE) {
luax_pushenum(L, &WrapModes, wrap.r);
return 3;
}
return 2; return 2;
} }
int l_lovrTextureRenderTo(lua_State* L) { int l_lovrTextureRenderTo(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
lovrGraphicsPushCanvas(); lovrGraphicsPushView();
lovrTextureBindFramebuffer(texture); lovrTextureBindFramebuffer(texture);
lua_settop(L, 2); lua_settop(L, 2);
lua_call(L, 0, 0); lua_call(L, 0, 0);
lovrTextureResolveMSAA(texture); lovrTextureResolveMSAA(texture);
lovrGraphicsPopCanvas(); lovrGraphicsPopView();
return 0; return 0;
} }
@ -62,9 +65,11 @@ int l_lovrTextureSetFilter(lua_State* L) {
int l_lovrTextureSetWrap(lua_State* L) { int l_lovrTextureSetWrap(lua_State* L) {
Texture* texture = luax_checktype(L, 1, Texture); Texture* texture = luax_checktype(L, 1, Texture);
WrapMode* horizontal = (WrapMode*) luax_checkenum(L, 2, &WrapModes, "wrap mode"); TextureWrap wrap;
WrapMode* vertical = (WrapMode*) luax_optenum(L, 3, luaL_checkstring(L, 2), &WrapModes, "wrap mode"); wrap.s = *(WrapMode*) luax_checkenum(L, 2, &WrapModes, "wrap mode");
lovrTextureSetWrap(texture, *horizontal, *vertical); wrap.t = *(WrapMode*) luax_optenum(L, 3, luaL_checkstring(L, 2), &WrapModes, "wrap mode");
wrap.r = *(WrapMode*) luax_optenum(L, 4, luaL_checkstring(L, 2), &WrapModes, "wrap mode");
lovrTextureSetWrap(texture, wrap);
return 0; return 0;
} }

View File

@ -78,7 +78,8 @@ if not lovr.filesystem.getSource() or not runnable then
local logo, controllers local logo, controllers
function lovr.load() function lovr.load()
logo = lovr.graphics.newTexture(lovr.filesystem.newBlob(lovr._logo, 'logo.png')) logo = lovr.graphics.newMaterial()
logo:setTexture(lovr.graphics.newTexture(lovr.filesystem.newBlob(lovr._logo, 'logo.png')))
lovr.graphics.setBackgroundColor(245, 252, 255) lovr.graphics.setBackgroundColor(245, 252, 255)
refreshControllers() refreshControllers()
end end
@ -97,7 +98,9 @@ if not lovr.filesystem.getSource() or not runnable then
local titlePosition = 1.3 - padding local titlePosition = 1.3 - padding
local subtitlePosition = titlePosition - font:getHeight() * .25 - padding local subtitlePosition = titlePosition - font:getHeight() * .25 - padding
lovr.graphics.plane(logo, 0, 1.8, -3, 1, 0, 0, 1) lovr.graphics.setMaterial(logo)
lovr.graphics.plane('fill', 0, 1.8, -3, 1, 0, 0, 1)
lovr.graphics.setMaterial()
lovr.graphics.setColor(15, 15, 15) lovr.graphics.setColor(15, 15, 15)
lovr.graphics.print('LÖVR', -.01, titlePosition, -3, .25, 0, 0, 1, 0, nil, 'center', 'top') lovr.graphics.print('LÖVR', -.01, titlePosition, -3, .25, 0, 0, 1, 0, nil, 'center', 'top')
lovr.graphics.setColor(15, 15, 15, fade) lovr.graphics.setColor(15, 15, 15, fade)

View File

@ -174,353 +174,363 @@ unsigned char boot_lua[] = {
0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x6f, 0x67, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x6c, 0x6f, 0x67, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77,
0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x28, 0x29, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6c, 0x6f, 0x67, 0x6f, 0x3a, 0x73, 0x65, 0x74, 0x54,
0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77,
0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e,
0x6e, 0x65, 0x77, 0x42, 0x6c, 0x6f, 0x62, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x6e, 0x65, 0x77, 0x42, 0x6c, 0x6f, 0x62, 0x28, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x2c, 0x20, 0x27, 0x6c, 0x6f, 0x67, 0x2e, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x2c, 0x20, 0x27, 0x6c, 0x6f, 0x67,
0x6f, 0x2e, 0x70, 0x6e, 0x67, 0x27, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x2e, 0x70, 0x6e, 0x67, 0x27, 0x29, 0x29, 0x29, 0x0a, 0x20, 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, 0x32, 0x34,
0x35, 0x2c, 0x20, 0x32, 0x35, 0x32, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x28,
0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x20, 0x20, 0x66,
0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x20, 0x20, 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, 0x32,
0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35,
0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x6d,
0x6f, 0x64, 0x65, 0x6c, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x61, 0x69, 0x72,
0x73, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20,
0x7a, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x3a, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x6f, 0x64, 0x65, 0x6c, 0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x78, 0x2c,
0x20, 0x79, 0x2c, 0x20, 0x7a, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x3a, 0x67, 0x65, 0x74,
0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x61,
0x64, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x2e, 0x31, 0x0a, 0x20,
0x20, 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, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61,
0x6c, 0x20, 0x66, 0x61, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x38, 0x30, 0x20,
0x2b, 0x20, 0x31, 0x35, 0x30, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74, 0x68,
0x2e, 0x61, 0x62, 0x73, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x73, 0x69,
0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72,
0x2e, 0x67, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x28, 0x29, 0x20, 0x2a,
0x20, 0x32, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x33, 0x20, 0x2d,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x62, 0x74, 0x69,
0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x3d, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x3a, 0x67,
0x65, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x20, 0x2a,
0x20, 0x2e, 0x32, 0x35, 0x20, 0x2d, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69,
0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x6c,
0x61, 0x6e, 0x65, 0x28, 0x6c, 0x6f, 0x67, 0x6f, 0x2c, 0x20, 0x30, 0x2c,
0x20, 0x31, 0x2e, 0x38, 0x2c, 0x20, 0x2d, 0x33, 0x2c, 0x20, 0x31, 0x2c,
0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68,
0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67,
0x28, 0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x29, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x32,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x34, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x32, 0x2c, 0x20, 0x32, 0x35, 0x35,
0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73,
0x28, 0x27, 0x4c, 0xc3, 0x96, 0x56, 0x52, 0x27, 0x2c, 0x20, 0x2d, 0x2e, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73,
0x30, 0x31, 0x2c, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x20, 0x20,
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x2d, 0x33, 0x2c, 0x20, 0x2e, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76,
0x32, 0x35, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x30, 0x2c, 0x20, 0x6e, 0x69, 0x6c, 0x2c, 0x20, 0x27, 0x63, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x20, 0x27, 0x74, 0x6f, 0x70, 0x27, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28,
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35,
0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x35, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20,
0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x2c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2c, 0x20,
0x20, 0x31, 0x35, 0x2c, 0x20, 0x66, 0x61, 0x64, 0x65, 0x29, 0x0a, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x61, 0x69,
0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x72, 0x73, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x28, 0x27, 0x72, 0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x4e, 0x6f, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3a, 0x28, 0x27, 0x2c, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x78, 0x2c, 0x20, 0x79, 0x2c,
0x20, 0x2d, 0x2e, 0x30, 0x31, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x74, 0x69, 0x20, 0x7a, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x6c, 0x65, 0x72, 0x3a, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74,
0x20, 0x2d, 0x33, 0x2c, 0x20, 0x2e, 0x31, 0x35, 0x2c, 0x20, 0x30, 0x2c, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x6e, 0x69, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x78,
0x6c, 0x2c, 0x20, 0x27, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x7a, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x63,
0x20, 0x27, 0x74, 0x6f, 0x70, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x3a, 0x67, 0x65,
0x64, 0x0a, 0x0a, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x28, 0x29, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x6e, 0x64,
0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x5f, 0x2c,
0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20,
0x69, 0x6e, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73,
0x28, 0x29, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73,
0x5b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5d,
0x20, 0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x3a, 0x6e, 0x65, 0x77, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65,
0x6e, 0x64, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64,
0x65, 0x64, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x0a,
0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64,
0x20, 0x3d, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x0a, 0x65, 0x6e,
0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63,
0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20,
0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72,
0x65, 0x2c, 0x20, 0x27, 0x63, 0x6f, 0x6e, 0x66, 0x27, 0x29, 0x0a, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72,
0x0a, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e,
0x66, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x73, 0x75, 0x63,
0x63, 0x65, 0x73, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72,
0x72, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x2c, 0x20, 0x63, 0x6f, 0x6e,
0x66, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e,
0x73, 0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x28,
0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
0x79, 0x29, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x20, 0x27, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x27, 0x2c, 0x20, 0x27, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x27, 0x2c, 0x20, 0x27, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63,
0x73, 0x27, 0x2c, 0x20, 0x27, 0x6d, 0x61, 0x74, 0x68, 0x27, 0x2c, 0x20,
0x27, 0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x73, 0x27, 0x2c, 0x20, 0x27,
0x74, 0x69, 0x6d, 0x65, 0x72, 0x27, 0x20, 0x7d, 0x0a, 0x66, 0x6f, 0x72,
0x20, 0x5f, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x69,
0x6e, 0x20, 0x69, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, 0x6d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x69,
0x66, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
0x65, 0x73, 0x5b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5d, 0x20, 0x74,
0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x5b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5d, 0x20, 0x3d, 0x20, 0x72,
0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x27, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x27, 0x20, 0x2e, 0x2e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a,
0x0a, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61,
0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x6f,
0x6e, 0x66, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x74, 0x68,
0x65, 0x6e, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77,
0x20, 0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x77, 0x69, 0x6e, 0x64,
0x6f, 0x77, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72,
0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x28, 0x77, 0x2e, 0x77, 0x69,
0x64, 0x74, 0x68, 0x2c, 0x20, 0x77, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x2c, 0x20, 0x77, 0x2e, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72,
0x65, 0x65, 0x6e, 0x2c, 0x20, 0x77, 0x2e, 0x6d, 0x73, 0x61, 0x61, 0x2c,
0x20, 0x77, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x2c, 0x20, 0x77, 0x2e,
0x69, 0x63, 0x6f, 0x6e, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x2d,
0x2d, 0x20, 0x63, 0x68, 0x65, 0x65, 0x73, 0x79, 0x2c, 0x20, 0x74, 0x65,
0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x20, 0x68, 0x61, 0x63, 0x6b,
0x20, 0x2d, 0x20, 0x66, 0x61, 0x6b, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64,
0x73, 0x65, 0x74, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73,
0x20, 0x61, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x77, 0x69, 0x6e,
0x64, 0x6f, 0x77, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x66, 0x69,
0x78, 0x21, 0x0a, 0x69, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6d,
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x5b, 0x27, 0x68, 0x65, 0x61, 0x64,
0x73, 0x65, 0x74, 0x27, 0x5d, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x5b, 0x27, 0x68, 0x65, 0x61, 0x64, 0x73,
0x65, 0x74, 0x27, 0x5d, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69,
0x72, 0x65, 0x28, 0x27, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x65, 0x74, 0x27, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a,
0x2d, 0x2d, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x20, 0x61, 0x66, 0x74,
0x65, 0x72, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x69, 0x73,
0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x0a, 0x69, 0x66, 0x20,
0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72, 0x20, 0x74, 0x68, 0x65, 0x6e,
0x0a, 0x20, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x63, 0x6f, 0x6e,
0x66, 0x65, 0x72, 0x72, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 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, 0x63, 0x6f, 0x6e,
0x66, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e,
0x64, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x65, 0x74, 0x2e, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x20, 0x65,
0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x61, 0x6e,
0x64, 0x6c, 0x65, 0x72, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x74, 0x6d,
0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x28, 0x7b, 0x0a, 0x20,
0x20, 0x71, 0x75, 0x69, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a,
0x20, 0x20, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x75,
0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x66, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x66, 0x6f,
0x63, 0x75, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x28, 0x66, 0x29, 0x20, 0x65,
0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x20, 0x20,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64,
0x64, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x63, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68,
0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64, 0x65, 0x64, 0x28,
0x63, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64,
0x2c, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x3d, 0x20,
0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x28, 0x63, 0x29,
0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a,
0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75,
0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x2c, 0x20, 0x62, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x70,
0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x63,
0x2c, 0x20, 0x62, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65,
0x6e, 0x64, 0x2c, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64,
0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x63, 0x2c, 0x20, 0x62, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64,
0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c,
0x65, 0x61, 0x73, 0x65, 0x64, 0x28, 0x63, 0x2c, 0x20, 0x62, 0x29, 0x20,
0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x7d, 0x2c,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x73, 0x65, 0x6c, 0x66, 0x2c, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x27,
0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x3a, 0x20, 0x27, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29, 0x29,
0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x7d, 0x29, 0x0a, 0x0a, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x52, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28,
0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f,
0x72, 0x69, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x28, 0x29, 0x20,
0x3d, 0x3d, 0x20, 0x27, 0x68, 0x65, 0x61, 0x64, 0x27, 0x20, 0x74, 0x68,
0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79,
0x48, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65,
0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
0x65, 0x6e, 0x64, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x66,
0x6f, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x61, 0x2c, 0x20,
0x62, 0x2c, 0x20, 0x63, 0x2c, 0x20, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x6f,
0x6c, 0x6c, 0x28, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x27,
0x71, 0x75, 0x69, 0x74, 0x27, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x6e,
0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x71, 0x75, 0x69, 0x74,
0x20, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x71, 0x75, 0x69, 0x74, 0x28, 0x29, 0x29, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75,
0x72, 0x6e, 0x20, 0x61, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x61,
0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x5b, 0x6e, 0x61, 0x6d, 0x65, 0x5d,
0x28, 0x61, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x63, 0x2c, 0x20, 0x64, 0x29,
0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63,
0x61, 0x6c, 0x20, 0x64, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28,
0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e,
0x69, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x28, 0x64, 0x74, 0x29, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x75, 0x70,
0x64, 0x61, 0x74, 0x65, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69,
0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x69, 0x73, 0x50, 0x72,
0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x4f, 0x72, 0x69,
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x74, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x28, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x28, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a,
0x76, 0x72, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70,
0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x2e, 0x31, 0x0a,
0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f,
0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x29, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x46, 0x6f,
0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x56, 0x65, 0x6c, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63,
0x6f, 0x63, 0x69, 0x74, 0x79, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x61, 0x6c, 0x20, 0x66, 0x61, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x38, 0x30,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x56, 0x65, 0x20, 0x2b, 0x20, 0x31, 0x35, 0x30, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74,
0x6c, 0x6f, 0x63, 0x69, 0x74, 0x79, 0x28, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x68, 0x2e, 0x61, 0x62, 0x73, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x73,
0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x69, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x74, 0x69, 0x6d, 0x65,
0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x75, 0x70, 0x72, 0x2e, 0x67, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x28, 0x29, 0x20,
0x64, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x2a, 0x20, 0x32, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x28, 0x64, 0x74, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73,
0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x33, 0x20,
0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2d, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20,
0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x62, 0x74,
0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x3a,
0x73, 0x2e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x67, 0x65, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x20,
0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x64, 0x2a, 0x20, 0x2e, 0x32, 0x35, 0x20, 0x2d, 0x20, 0x70, 0x61, 0x64, 0x64,
0x72, 0x61, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76,
0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x65, 0x74, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x28, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x6f, 0x67, 0x6f, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76,
0x69, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70,
0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x61, 0x6e, 0x65, 0x28, 0x27, 0x66, 0x69, 0x6c, 0x6c, 0x27, 0x2c,
0x20, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x38, 0x2c, 0x20, 0x2d, 0x33, 0x2c,
0x20, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72,
0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4d, 0x61,
0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x28, 0x29, 0x0a, 0x20, 0x20, 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,
0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 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,
0x27, 0x4c, 0xc3, 0x96, 0x56, 0x52, 0x27, 0x2c, 0x20, 0x2d, 0x2e, 0x30,
0x31, 0x2c, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x2d, 0x33, 0x2c, 0x20, 0x2e, 0x32,
0x35, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20,
0x30, 0x2c, 0x20, 0x6e, 0x69, 0x6c, 0x2c, 0x20, 0x27, 0x63, 0x65, 0x6e,
0x74, 0x65, 0x72, 0x27, 0x2c, 0x20, 0x27, 0x74, 0x6f, 0x70, 0x27, 0x29,
0x0a, 0x20, 0x20, 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, 0x31, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x2c, 0x20,
0x31, 0x35, 0x2c, 0x20, 0x66, 0x61, 0x64, 0x65, 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, 0x27, 0x4e,
0x6f, 0x20, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3a, 0x28, 0x27, 0x2c, 0x20,
0x2d, 0x2e, 0x30, 0x31, 0x2c, 0x20, 0x73, 0x75, 0x62, 0x74, 0x69, 0x74,
0x6c, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20,
0x2d, 0x33, 0x2c, 0x20, 0x2e, 0x31, 0x35, 0x2c, 0x20, 0x30, 0x2c, 0x20,
0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x6e, 0x69, 0x6c,
0x2c, 0x20, 0x27, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x20,
0x27, 0x74, 0x6f, 0x70, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64,
0x0a, 0x0a, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x28, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e,
0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x6e, 0x64, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x5f, 0x2c, 0x20,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x69,
0x6e, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x28,
0x29, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x5b,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5d, 0x20,
0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x3a, 0x6e, 0x65, 0x77, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x28, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e,
0x64, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64, 0x65,
0x64, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x0a, 0x20,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20,
0x3d, 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x73, 0x0a, 0x65, 0x6e, 0x64,
0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x70,
0x63, 0x61, 0x6c, 0x6c, 0x28, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65,
0x2c, 0x20, 0x27, 0x63, 0x6f, 0x6e, 0x66, 0x27, 0x29, 0x0a, 0x6c, 0x6f,
0x63, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72, 0x0a,
0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66,
0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72,
0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x66,
0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73,
0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x28, 0x63,
0x6f, 0x6e, 0x66, 0x2e, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
0x29, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x6f, 0x64,
0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x20, 0x27, 0x61, 0x75,
0x64, 0x69, 0x6f, 0x27, 0x2c, 0x20, 0x27, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x27, 0x2c, 0x20, 0x27, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
0x27, 0x2c, 0x20, 0x27, 0x6d, 0x61, 0x74, 0x68, 0x27, 0x2c, 0x20, 0x27,
0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x73, 0x27, 0x2c, 0x20, 0x27, 0x74,
0x69, 0x6d, 0x65, 0x72, 0x27, 0x20, 0x7d, 0x0a, 0x66, 0x6f, 0x72, 0x20,
0x5f, 0x2c, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x69, 0x6e,
0x20, 0x69, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, 0x6d, 0x6f, 0x64, 0x75,
0x6c, 0x65, 0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x69, 0x66,
0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x73, 0x5b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5d, 0x20, 0x74, 0x68,
0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x5b,
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5d, 0x20, 0x3d, 0x20, 0x72, 0x65,
0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x27, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x27, 0x20, 0x2e, 0x2e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x29,
0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a,
0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70,
0x68, 0x69, 0x63, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x6f, 0x6e,
0x66, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x20,
0x3d, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f,
0x77, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x67, 0x72, 0x61,
0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x28, 0x77, 0x2e, 0x77, 0x69, 0x64,
0x74, 0x68, 0x2c, 0x20, 0x77, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x2c, 0x20, 0x77, 0x2e, 0x66, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65,
0x65, 0x6e, 0x2c, 0x20, 0x77, 0x2e, 0x6d, 0x73, 0x61, 0x61, 0x2c, 0x20,
0x77, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x2c, 0x20, 0x77, 0x2e, 0x69,
0x63, 0x6f, 0x6e, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x2d, 0x2d,
0x20, 0x63, 0x68, 0x65, 0x65, 0x73, 0x79, 0x2c, 0x20, 0x74, 0x65, 0x6d,
0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x20, 0x68, 0x61, 0x63, 0x6b, 0x20,
0x2d, 0x20, 0x66, 0x61, 0x6b, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, 0x73,
0x65, 0x74, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x20,
0x61, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x77, 0x69, 0x6e, 0x64,
0x6f, 0x77, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x66, 0x69, 0x78,
0x21, 0x0a, 0x69, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x73, 0x5b, 0x27, 0x68, 0x65, 0x61, 0x64, 0x73,
0x65, 0x74, 0x27, 0x5d, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x5b, 0x27, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65,
0x74, 0x27, 0x5d, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72,
0x65, 0x28, 0x27, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x65, 0x74, 0x27, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x2d,
0x2d, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x20, 0x61, 0x66, 0x74, 0x65,
0x72, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x0a, 0x69, 0x66, 0x20, 0x63,
0x6f, 0x6e, 0x66, 0x65, 0x72, 0x72, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x20, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x63, 0x6f, 0x6e, 0x66,
0x65, 0x72, 0x72, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x69, 0x66,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65,
0x74, 0x2e, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x54, 0x6f, 0x28, 0x68, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x73, 0x65, 0x74, 0x4d,
0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x29, 0x0a, 0x20, 0x20, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x65, 0x64, 0x28, 0x63, 0x6f, 0x6e, 0x66,
0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64,
0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65,
0x61, 0x64, 0x73, 0x65, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x74, 0x2e, 0x6d, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x20, 0x65, 0x6e,
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x61, 0x6e, 0x64,
0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x6c, 0x65, 0x72, 0x73, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x74, 0x6d, 0x65,
0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x28, 0x7b, 0x0a, 0x20, 0x20,
0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x71, 0x75, 0x69, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74,
0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x20,
0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x20, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e,
0x64, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x74, 0x69, 0x6d, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x66, 0x29, 0x0a, 0x20, 0x20, 0x20,
0x65, 0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x28, 0x2e, 0x30, 0x30, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x66, 0x6f, 0x63,
0x31, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x75, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x72, 0x75, 0x2e, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x28, 0x66, 0x29, 0x20, 0x65, 0x6e,
0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x20, 0x20, 0x63,
0x72, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x20, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x28, 0x63, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
0x74, 0x72, 0x75, 0x65, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x78, 0x69, 0x74, 0x20, 0x3d, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28, 0x29, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x78, 0x69, 0x74, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x61, 0x64, 0x64, 0x65, 0x64, 0x28, 0x63,
0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x2c,
0x20, 0x65, 0x78, 0x69, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x69, 0x66, 0x20, 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x29, 0x0a, 0x20,
0x74, 0x65, 0x6d, 0x2e, 0x69, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x28, 0x27, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63,
0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x6c, 0x75, 0x61, 0x27, 0x29, 0x20, 0x74, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6d,
0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f,
0x65, 0x28, 0x27, 0x6d, 0x61, 0x69, 0x6e, 0x27, 0x29, 0x0a, 0x65, 0x6e, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x64, 0x0a 0x72, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x28, 0x63, 0x29, 0x20,
0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x20,
0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x70,
0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x2c, 0x20, 0x62, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x70, 0x72,
0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x63, 0x2c,
0x20, 0x62, 0x29, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e,
0x64, 0x2c, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x20,
0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63,
0x2c, 0x20, 0x62, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x72, 0x65, 0x6c, 0x65,
0x61, 0x73, 0x65, 0x64, 0x28, 0x63, 0x2c, 0x20, 0x62, 0x29, 0x20, 0x65,
0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x7d, 0x2c, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20,
0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x73,
0x65, 0x6c, 0x66, 0x2c, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x27, 0x55,
0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x3a, 0x20, 0x27, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29, 0x29, 0x0a,
0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x7d, 0x29, 0x0a, 0x0a, 0x6c, 0x6f,
0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x52, 0x65, 0x6e, 0x64,
0x65, 0x72, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x72,
0x69, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x28, 0x29, 0x20, 0x3d,
0x3d, 0x20, 0x27, 0x68, 0x65, 0x61, 0x64, 0x27, 0x20, 0x74, 0x68, 0x65,
0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x48,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74,
0x28, 0x29, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a, 0x65,
0x6e, 0x64, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28, 0x29, 0x0a,
0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x2e, 0x70, 0x75, 0x6d, 0x70, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x66, 0x6f,
0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x62,
0x2c, 0x20, 0x63, 0x2c, 0x20, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x6f, 0x6c,
0x6c, 0x28, 0x29, 0x20, 0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69,
0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x71,
0x75, 0x69, 0x74, 0x27, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x6e, 0x6f,
0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x71, 0x75, 0x69, 0x74, 0x20,
0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x71, 0x75, 0x69, 0x74, 0x28, 0x29, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
0x6e, 0x20, 0x61, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x61, 0x6e,
0x64, 0x6c, 0x65, 0x72, 0x73, 0x5b, 0x6e, 0x61, 0x6d, 0x65, 0x5d, 0x28,
0x61, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x63, 0x2c, 0x20, 0x64, 0x29, 0x0a,
0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61,
0x6c, 0x20, 0x64, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e,
0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x69,
0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20, 0x74,
0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x75,
0x70, 0x64, 0x61, 0x74, 0x65, 0x28, 0x64, 0x74, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65,
0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x69, 0x73, 0x50, 0x72, 0x65,
0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x4f, 0x72, 0x69, 0x65,
0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x50,
0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x61,
0x75, 0x64, 0x69, 0x6f, 0x2e, 0x73, 0x65, 0x74, 0x56, 0x65, 0x6c, 0x6f,
0x63, 0x69, 0x74, 0x79, 0x28, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x56, 0x65, 0x6c,
0x6f, 0x63, 0x69, 0x74, 0x79, 0x28, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20,
0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c, 0x6f, 0x76,
0x72, 0x2e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x28, 0x64, 0x74, 0x29,
0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20,
0x74, 0x68, 0x65, 0x6e, 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, 0x64, 0x72,
0x61, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f,
0x76, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x2e, 0x69,
0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x28, 0x29, 0x20, 0x74,
0x68, 0x65, 0x6e, 0x0a, 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, 0x68, 0x65,
0x61, 0x64, 0x73, 0x65, 0x74, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43,
0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x29, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x65, 0x61,
0x64, 0x73, 0x65, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x28, 0x29,
0x0a, 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, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65,
0x6e, 0x64, 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, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x74, 0x69, 0x6d, 0x65,
0x72, 0x2e, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x28, 0x2e, 0x30, 0x30, 0x31,
0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x72, 0x75, 0x6e,
0x28, 0x29, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x72,
0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x20, 0x65,
0x6e, 0x64, 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, 0x65, 0x78, 0x69, 0x74, 0x20, 0x3d, 0x20,
0x6c, 0x6f, 0x76, 0x72, 0x2e, 0x73, 0x74, 0x65, 0x70, 0x28, 0x29, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x78, 0x69, 0x74, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
0x65, 0x78, 0x69, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65,
0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x69, 0x66, 0x20, 0x6c,
0x6f, 0x76, 0x72, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74,
0x65, 0x6d, 0x2e, 0x69, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x28, 0x27, 0x6d,
0x61, 0x69, 0x6e, 0x2e, 0x6c, 0x75, 0x61, 0x27, 0x29, 0x20, 0x74, 0x68,
0x65, 0x6e, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65,
0x28, 0x27, 0x6d, 0x61, 0x69, 0x6e, 0x27, 0x29, 0x0a, 0x65, 0x6e, 0x64,
0x0a
}; };
unsigned int boot_lua_len = 6266; unsigned int boot_lua_len = 6385;

View File

@ -7,6 +7,7 @@ Blob* lovrBlobCreate(void* data, size_t size, const char* name) {
blob->data = data; blob->data = data;
blob->size = size; blob->size = size;
blob->name = name; blob->name = name;
blob->seek = 0;
return blob; return blob;
} }

View File

@ -7,6 +7,7 @@ typedef struct {
void* data; void* data;
size_t size; size_t size;
const char* name; const char* name;
size_t seek;
} Blob; } Blob;
Blob* lovrBlobCreate(void* data, size_t size, const char* name); Blob* lovrBlobCreate(void* data, size_t size, const char* name);

64
src/filesystem/file.c Normal file
View File

@ -0,0 +1,64 @@
#include "filesystem/file.h"
#include <physfs.h>
File* lovrFileCreate(const char* path) {
File* file = lovrAlloc(sizeof(File), lovrFileDestroy);
if (!file) return NULL;
file->path = path;
file->handle = NULL;
return file;
}
void lovrFileDestroy(const Ref* ref) {
File* file = containerof(ref, File);
if (file->handle) {
PHYSFS_close(file->handle);
}
free(file);
}
int lovrFileOpen(File* file, FileMode mode) {
lovrAssert(!file->handle, "File is already open");
file->mode = mode;
switch (mode) {
case OPEN_READ: file->handle = PHYSFS_openRead(file->path); break;
case OPEN_WRITE: file->handle = PHYSFS_openWrite(file->path); break;
case OPEN_APPEND: file->handle = PHYSFS_openAppend(file->path); break;
}
return file->handle == NULL;
}
void lovrFileClose(File* file) {
lovrAssert(file->handle, "File must be open to close it");
PHYSFS_close(file->handle);
file->handle = NULL;
}
size_t lovrFileRead(File* file, void* data, size_t size, size_t count) {
lovrAssert(file->handle && file->mode == OPEN_READ, "File must be open for reading");
return PHYSFS_read(file->handle, data, size, count);
}
size_t lovrFileWrite(File* file, void* data, size_t size, size_t count) {
lovrAssert(file->handle && (file->mode == OPEN_READ || file->mode == OPEN_WRITE), "File must be open for writing");
return PHYSFS_write(file->handle, data, size, count);
}
size_t lovrFileGetSize(File* file) {
lovrAssert(file->handle, "File must be open to get its size");
return PHYSFS_fileLength(file->handle);
}
int lovrFileSeek(File* file, size_t position) {
lovrAssert(file->handle, "File must be open to seek");
return !PHYSFS_seek(file->handle, position);
}
size_t lovrFileTell(File* file) {
lovrAssert(file->handle, "File must be open to tell");
return PHYSFS_tell(file->handle);
}

26
src/filesystem/file.h Normal file
View File

@ -0,0 +1,26 @@
#include "util.h"
#pragma once
typedef enum {
OPEN_READ,
OPEN_WRITE,
OPEN_APPEND
} FileMode;
typedef struct {
Ref ref;
const char* path;
void* handle;
FileMode mode;
} File;
File* lovrFileCreate(const char* filename);
void lovrFileDestroy(const Ref* ref);
int lovrFileOpen(File* file, FileMode mode);
void lovrFileClose(File* file);
size_t lovrFileRead(File* file, void* data, size_t size, size_t count);
size_t lovrFileWrite(File* file, void* data, size_t size, size_t count);
size_t lovrFileGetSize(File* file);
int lovrFileSeek(File* file, size_t position);
size_t lovrFileTell(File* file);

View File

@ -1,9 +1,9 @@
#include "filesystem/filesystem.h" #include "filesystem/filesystem.h"
#include "filesystem/file.h"
#include "util.h" #include "util.h"
#include <physfs.h> #include <physfs.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#ifdef __APPLE__ #ifdef __APPLE__
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#endif #endif
@ -22,7 +22,7 @@ static FilesystemState state;
void lovrFilesystemInit(const char* arg0, const char* arg1) { void lovrFilesystemInit(const char* arg0, const char* arg1) {
if (!PHYSFS_init(arg0)) { if (!PHYSFS_init(arg0)) {
lovrThrow("Could not initialize filesystem: %s", PHYSFS_getLastError()); lovrThrow("Could not initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
} }
state.source = malloc(LOVR_PATH_MAX * sizeof(char)); state.source = malloc(LOVR_PATH_MAX * sizeof(char));
@ -60,10 +60,6 @@ int lovrFilesystemCreateDirectory(const char* path) {
return !PHYSFS_mkdir(path); return !PHYSFS_mkdir(path);
} }
int lovrFilesystemExists(const char* path) {
return PHYSFS_exists(path);
}
int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size) { int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size) {
#ifdef __APPLE__ #ifdef __APPLE__
const char* home; const char* home;
@ -97,7 +93,7 @@ int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size) {
} }
void lovrFilesystemGetDirectoryItems(const char* path, getDirectoryItemsCallback callback, void* userdata) { void lovrFilesystemGetDirectoryItems(const char* path, getDirectoryItemsCallback callback, void* userdata) {
PHYSFS_enumerateFilesCallback(path, callback, userdata); PHYSFS_enumerate(path, callback, userdata);
} }
int lovrFilesystemGetExecutablePath(char* dest, unsigned int size) { int lovrFilesystemGetExecutablePath(char* dest, unsigned int size) {
@ -126,7 +122,8 @@ const char* lovrFilesystemGetIdentity() {
} }
long lovrFilesystemGetLastModified(const char* path) { long lovrFilesystemGetLastModified(const char* path) {
return PHYSFS_getLastModTime(path); PHYSFS_Stat stat;
return PHYSFS_stat(path, &stat) ? stat.modtime : -1;
} }
const char* lovrFilesystemGetRealDirectory(const char* path) { const char* lovrFilesystemGetRealDirectory(const char* path) {
@ -137,15 +134,9 @@ const char* lovrFilesystemGetSaveDirectory() {
return state.savePathFull; return state.savePathFull;
} }
int lovrFilesystemGetSize(const char* path) { size_t lovrFilesystemGetSize(const char* path) {
PHYSFS_file* handle = PHYSFS_openRead(path); PHYSFS_Stat stat;
if (!handle) { return PHYSFS_stat(path, &stat) ? stat.filesize : -1;
return -1;
}
int length = PHYSFS_fileLength(handle);
PHYSFS_close(handle);
return length;
} }
const char* lovrFilesystemGetSource() { const char* lovrFilesystemGetSource() {
@ -153,15 +144,29 @@ const char* lovrFilesystemGetSource() {
} }
const char* lovrFilesystemGetUserDirectory() { const char* lovrFilesystemGetUserDirectory() {
return PHYSFS_getUserDir(); #if defined(__APPLE__) || defined(__linux__)
const char* home;
if ((home = getenv("HOME")) == NULL) {
home = getpwuid(getuid())->pw_dir;
}
return home;
#elif _WIN32
return getenv("USERPROFILE");
#elif EMSCRIPTEN
return "/home/web_user";
#else
#error "This platform is missing an implementation for lovrFilesystemGetUserDirectory"
#endif
} }
int lovrFilesystemIsDirectory(const char* path) { int lovrFilesystemIsDirectory(const char* path) {
return PHYSFS_isDirectory(path); PHYSFS_Stat stat;
return PHYSFS_stat(path, &stat) ? stat.filetype == PHYSFS_FILETYPE_DIRECTORY : 0;
} }
int lovrFilesystemIsFile(const char* path) { int lovrFilesystemIsFile(const char* path) {
return lovrFilesystemExists(path) && !lovrFilesystemIsDirectory(path); PHYSFS_Stat stat;
return PHYSFS_stat(path, &stat) ? stat.filetype == PHYSFS_FILETYPE_REGULAR : 0;
} }
int lovrFilesystemIsFused() { int lovrFilesystemIsFused() {
@ -174,15 +179,20 @@ int lovrFilesystemMount(const char* path, const char* mountpoint, int append) {
void* lovrFilesystemRead(const char* path, size_t* bytesRead) { void* lovrFilesystemRead(const char* path, size_t* bytesRead) {
// Open file // Create file
PHYSFS_file* handle = PHYSFS_openRead(path); File* file = lovrFileCreate(path);
if (!handle) { if (!file) {
return NULL;
}
// Open it
if (lovrFileOpen(file, OPEN_READ)) {
return NULL; return NULL;
} }
// Get file size // Get file size
int size = PHYSFS_fileLength(handle); size_t size = lovrFileGetSize(file);
if (size < 0) { if (size == (unsigned int) -1) {
return NULL; return NULL;
} }
@ -193,8 +203,8 @@ void* lovrFilesystemRead(const char* path, size_t* bytesRead) {
} }
// Perform read // Perform read
*bytesRead = PHYSFS_read(handle, data, 1, size); *bytesRead = lovrFileRead(file, data, 1, size);
PHYSFS_close(handle); lovrFileClose(file);
// Make sure we got everything // Make sure we got everything
if (*bytesRead != (size_t) size) { if (*bytesRead != (size_t) size) {
@ -214,7 +224,7 @@ int lovrFilesystemSetIdentity(const char* identity) {
// Unmount old write directory // Unmount old write directory
if (state.savePathFull && state.savePathRelative) { if (state.savePathFull && state.savePathRelative) {
PHYSFS_removeFromSearchPath(state.savePathRelative); PHYSFS_unmount(state.savePathRelative);
} else { } else {
state.savePathRelative = malloc(LOVR_PATH_MAX); state.savePathRelative = malloc(LOVR_PATH_MAX);
state.savePathFull = malloc(LOVR_PATH_MAX); state.savePathFull = malloc(LOVR_PATH_MAX);
@ -231,7 +241,8 @@ int lovrFilesystemSetIdentity(const char* identity) {
strncpy(state.savePathFull, fullPathBuffer, LOVR_PATH_MAX); strncpy(state.savePathFull, fullPathBuffer, LOVR_PATH_MAX);
PHYSFS_mkdir(state.savePathRelative); PHYSFS_mkdir(state.savePathRelative);
if (!PHYSFS_setWriteDir(state.savePathFull)) { if (!PHYSFS_setWriteDir(state.savePathFull)) {
lovrThrow("Could not set write directory: %s (%s)", PHYSFS_getLastError(), state.savePathRelative); const char* error = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
lovrThrow("Could not set write directory: %s (%s)", error, state.savePathRelative);
} }
PHYSFS_mount(state.savePathFull, NULL, 0); PHYSFS_mount(state.savePathFull, NULL, 0);
@ -240,16 +251,18 @@ int lovrFilesystemSetIdentity(const char* identity) {
} }
int lovrFilesystemUnmount(const char* path) { int lovrFilesystemUnmount(const char* path) {
return !PHYSFS_removeFromSearchPath(path); return !PHYSFS_unmount(path);
} }
int lovrFilesystemWrite(const char* path, const char* content, size_t size, int append) { size_t lovrFilesystemWrite(const char* path, const char* content, size_t size, int append) {
PHYSFS_file* handle = append ? PHYSFS_openAppend(path) : PHYSFS_openWrite(path); File* file = lovrFileCreate(path);
if (!handle) { if (!file) {
return 0; return 0;
} }
int bytesWritten = PHYSFS_write(handle, content, 1, size); lovrFileOpen(file, append ? OPEN_APPEND : OPEN_WRITE);
PHYSFS_close(handle); size_t bytesWritten = lovrFileWrite(file, (void*) content, 1, size);
lovrFileClose(file);
lovrRelease(&file->ref);
return bytesWritten; return bytesWritten;
} }

View File

@ -4,7 +4,7 @@
#define LOVR_PATH_MAX 1024 #define LOVR_PATH_MAX 1024
typedef void getDirectoryItemsCallback(void* userdata, const char* dir, const char* file); typedef int getDirectoryItemsCallback(void* userdata, const char* dir, const char* file);
typedef struct { typedef struct {
char* source; char* source;
@ -17,7 +17,6 @@ typedef struct {
void lovrFilesystemInit(const char* arg0, const char* arg1); void lovrFilesystemInit(const char* arg0, const char* arg1);
void lovrFilesystemDestroy(); void lovrFilesystemDestroy();
int lovrFilesystemCreateDirectory(const char* path); int lovrFilesystemCreateDirectory(const char* path);
int lovrFilesystemExists(const char* path);
int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size); int lovrFilesystemGetAppdataDirectory(char* dest, unsigned int size);
void lovrFilesystemGetDirectoryItems(const char* path, getDirectoryItemsCallback callback, void* userdata); void lovrFilesystemGetDirectoryItems(const char* path, getDirectoryItemsCallback callback, void* userdata);
int lovrFilesystemGetExecutablePath(char* dest, unsigned int size); int lovrFilesystemGetExecutablePath(char* dest, unsigned int size);
@ -25,7 +24,7 @@ const char* lovrFilesystemGetIdentity();
long lovrFilesystemGetLastModified(const char* path); long lovrFilesystemGetLastModified(const char* path);
const char* lovrFilesystemGetRealDirectory(const char* path); const char* lovrFilesystemGetRealDirectory(const char* path);
const char* lovrFilesystemGetSaveDirectory(); const char* lovrFilesystemGetSaveDirectory();
int lovrFilesystemGetSize(const char* path); size_t lovrFilesystemGetSize(const char* path);
const char* lovrFilesystemGetSource(); const char* lovrFilesystemGetSource();
const char* lovrFilesystemGetUserDirectory(); const char* lovrFilesystemGetUserDirectory();
int lovrFilesystemIsDirectory(const char* path); int lovrFilesystemIsDirectory(const char* path);
@ -37,4 +36,4 @@ int lovrFilesystemRemove(const char* path);
int lovrFilesystemSetIdentity(const char* identity); int lovrFilesystemSetIdentity(const char* identity);
int lovrFilesystemSetSource(const char* source); int lovrFilesystemSetSource(const char* source);
int lovrFilesystemUnmount(const char* path); int lovrFilesystemUnmount(const char* path);
int lovrFilesystemWrite(const char* path, const char* content, size_t size, int append); size_t lovrFilesystemWrite(const char* path, const char* content, size_t size, int append);

View File

@ -49,9 +49,9 @@ Font* lovrFontCreate(FontData* fontData) {
// Texture // Texture
TextureData* textureData = lovrTextureDataGetBlank(font->atlas.width, font->atlas.height, 0x0, FORMAT_RGB); TextureData* textureData = lovrTextureDataGetBlank(font->atlas.width, font->atlas.height, 0x0, FORMAT_RGB);
TextureFilter filter = { .mode = FILTER_BILINEAR }; TextureFilter filter = { .mode = FILTER_BILINEAR };
font->texture = lovrTextureCreate(textureData); font->texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1);
lovrTextureSetFilter(font->texture, filter); lovrTextureSetFilter(font->texture, filter);
lovrTextureSetWrap(font->texture, WRAP_CLAMP, WRAP_CLAMP); lovrTextureSetWrap(font->texture, (TextureWrap) { .s = WRAP_CLAMP, .t = WRAP_CLAMP });
return font; return font;
} }
@ -277,7 +277,7 @@ void lovrFontAddGlyph(Font* font, Glyph* glyph) {
glyph->y = atlas->y; glyph->y = atlas->y;
// Paste glyph into texture // Paste glyph into texture
lovrGraphicsBindTexture(font->texture); lovrGraphicsBindTexture(font->texture, TEXTURE_2D, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, atlas->x, atlas->y, glyph->tw, glyph->th, FORMAT_RGB.glFormat, GL_UNSIGNED_BYTE, glyph->data); glTexSubImage2D(GL_TEXTURE_2D, 0, atlas->x, atlas->y, glyph->tw, glyph->th, FORMAT_RGB.glFormat, GL_UNSIGNED_BYTE, glyph->data);
// Advance atlas cursor // Advance atlas cursor
@ -299,7 +299,7 @@ void lovrFontExpandTexture(Font* font) {
} }
// Resize the texture storage // Resize the texture storage
lovrTextureDataResize(font->texture->textureData, atlas->width, atlas->height, 0x0); lovrTextureDataResize(font->texture->slices[0], atlas->width, atlas->height, 0x0);
lovrTextureRefresh(font->texture); lovrTextureRefresh(font->texture);
// Reset the cursor // Reset the cursor

View File

@ -1,4 +1,5 @@
#include "graphics/graphics.h" #include "graphics/graphics.h"
#include "graphics/shaders.h"
#include "loaders/texture.h" #include "loaders/texture.h"
#include "loaders/font.h" #include "loaders/font.h"
#include "event/event.h" #include "event/event.h"
@ -35,6 +36,7 @@ void lovrGraphicsDestroy() {
for (int i = 0; i < DEFAULT_SHADER_COUNT; i++) { for (int i = 0; i < DEFAULT_SHADER_COUNT; i++) {
if (state.defaultShaders[i]) lovrRelease(&state.defaultShaders[i]->ref); if (state.defaultShaders[i]) lovrRelease(&state.defaultShaders[i]->ref);
} }
if (state.defaultMaterial) lovrRelease(&state.defaultMaterial->ref);
if (state.defaultFont) lovrRelease(&state.defaultFont->ref); if (state.defaultFont) lovrRelease(&state.defaultFont->ref);
if (state.defaultTexture) lovrRelease(&state.defaultTexture->ref); if (state.defaultTexture) lovrRelease(&state.defaultTexture->ref);
glDeleteVertexArrays(1, &state.streamVAO); glDeleteVertexArrays(1, &state.streamVAO);
@ -49,7 +51,7 @@ void lovrGraphicsReset() {
int h = lovrGraphicsGetHeight(); int h = lovrGraphicsGetHeight();
float projection[16]; float projection[16];
state.transform = 0; state.transform = 0;
state.canvas = 0; state.view = 0;
state.defaultShader = -1; state.defaultShader = -1;
lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 255 }); lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 255 });
lovrGraphicsSetBlendMode(BLEND_ALPHA, BLEND_ALPHA_MULTIPLY); lovrGraphicsSetBlendMode(BLEND_ALPHA, BLEND_ALPHA_MULTIPLY);
@ -58,6 +60,7 @@ void lovrGraphicsReset() {
lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = FILTER_TRILINEAR }); lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = FILTER_TRILINEAR });
lovrGraphicsSetDepthTest(COMPARE_LEQUAL); lovrGraphicsSetDepthTest(COMPARE_LEQUAL);
lovrGraphicsSetFont(NULL); lovrGraphicsSetFont(NULL);
lovrGraphicsSetMaterial(NULL);
lovrGraphicsSetLineWidth(1); lovrGraphicsSetLineWidth(1);
lovrGraphicsSetPointSize(1); lovrGraphicsSetPointSize(1);
lovrGraphicsSetShader(NULL); lovrGraphicsSetShader(NULL);
@ -86,9 +89,51 @@ void lovrGraphicsPrepare() {
mat4 model = state.transforms[state.transform][MATRIX_MODEL]; mat4 model = state.transforms[state.transform][MATRIX_MODEL];
mat4 view = state.transforms[state.transform][MATRIX_VIEW]; mat4 view = state.transforms[state.transform][MATRIX_VIEW];
mat4 projection = state.canvases[state.canvas].projection; mat4 projection = state.views[state.view].projection;
lovrShaderSetMatrix(shader, "lovrModel", model, 16);
lovrShaderSetMatrix(shader, "lovrView", view, 16);
lovrShaderSetMatrix(shader, "lovrProjection", projection, 16);
float transform[16];
mat4_multiply(mat4_set(transform, view), model);
lovrShaderSetMatrix(shader, "lovrTransform", transform, 16);
if (lovrShaderGetUniform(shader, "lovrNormalMatrix")) {
if (mat4_invert(transform)) {
mat4_transpose(transform);
} else {
mat4_identity(transform);
}
float normalMatrix[9] = {
transform[0], transform[1], transform[2],
transform[4], transform[5], transform[6],
transform[8], transform[9], transform[10]
};
lovrShaderSetMatrix(shader, "lovrNormalMatrix", normalMatrix, 9);
}
// Color
float color[4] = { state.color.r / 255., state.color.g / 255., state.color.b / 255., state.color.a / 255. };
lovrShaderSetFloat(shader, "lovrColor", color, 4);
// Material
Material* material = lovrGraphicsGetMaterial();
for (int i = 0; i < MAX_MATERIAL_COLORS; i++) {
Color color = lovrMaterialGetColor(material, i);
float data[4] = { color.r / 255., color.g / 255., color.b / 255., color.a / 255. };
lovrShaderSetFloat(shader, lovrShaderColorUniforms[i], data, 4);
}
for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) {
Texture* texture = lovrMaterialGetTexture(material, i);
lovrShaderSetTexture(shader, lovrShaderTextureUniforms[i], &texture, 1);
}
lovrGraphicsBindProgram(shader->id); lovrGraphicsBindProgram(shader->id);
lovrShaderBind(shader, model, view, projection, state.color, 0); lovrShaderBind(shader);
} }
void lovrGraphicsCreateWindow(int w, int h, int fullscreen, int msaa, const char* title, const char* icon) { void lovrGraphicsCreateWindow(int w, int h, int fullscreen, int msaa, const char* title, const char* icon) {
@ -327,6 +372,31 @@ void lovrGraphicsSetLineWidth(float width) {
glLineWidth(width); glLineWidth(width);
} }
Material* lovrGraphicsGetMaterial() {
if (!state.material) {
if (!state.defaultMaterial) {
MaterialData* materialData = lovrMaterialDataCreateEmpty();
state.defaultMaterial = lovrMaterialCreate(materialData, 1);
}
lovrGraphicsSetMaterial(state.defaultMaterial);
}
return state.material;
}
void lovrGraphicsSetMaterial(Material* material) {
if (state.material) {
lovrRelease(&state.material->ref);
}
state.material = material;
if (material) {
lovrRetain(&material->ref);
}
}
float lovrGraphicsGetPointSize() { float lovrGraphicsGetPointSize() {
return state.pointSize; return state.pointSize;
} }
@ -417,7 +487,7 @@ void lovrGraphicsMatrixTransform(MatrixType type, mat4 transform) {
// Primitives // Primitives
static void lovrGraphicsSetShapeData(float* data, int length) { static void lovrGraphicsSetStreamData(float* data, int length) {
vec_clear(&state.streamData); vec_clear(&state.streamData);
vec_pusharr(&state.streamData, data, length); vec_pusharr(&state.streamData, data, length);
} }
@ -465,25 +535,22 @@ static void lovrGraphicsDrawPrimitive(GLenum mode, int hasNormals, int hasTexCoo
} }
void lovrGraphicsPoints(float* points, int count) { void lovrGraphicsPoints(float* points, int count) {
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(points, count); lovrGraphicsSetStreamData(points, count);
lovrGraphicsDrawPrimitive(GL_POINTS, 0, 0, 0); lovrGraphicsDrawPrimitive(GL_POINTS, 0, 0, 0);
} }
void lovrGraphicsLine(float* points, int count) { void lovrGraphicsLine(float* points, int count) {
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(points, count); lovrGraphicsSetStreamData(points, count);
lovrGraphicsDrawPrimitive(GL_LINE_STRIP, 0, 0, 0); lovrGraphicsDrawPrimitive(GL_LINE_STRIP, 0, 0, 0);
} }
void lovrGraphicsTriangle(DrawMode mode, float* points) { void lovrGraphicsTriangle(DrawMode mode, float* points) {
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
if (mode == DRAW_MODE_LINE) { if (mode == DRAW_MODE_LINE) {
lovrGraphicsSetShapeData(points, 9); lovrGraphicsSetStreamData(points, 9);
lovrGraphicsDrawPrimitive(GL_LINE_LOOP, 0, 0, 0); lovrGraphicsDrawPrimitive(GL_LINE_LOOP, 0, 0, 0);
} else { } else {
float normal[3]; float normal[3];
@ -495,12 +562,12 @@ void lovrGraphicsTriangle(DrawMode mode, float* points) {
points[6], points[7], points[8], normal[0], normal[1], normal[2] points[6], points[7], points[8], normal[0], normal[1], normal[2]
}; };
lovrGraphicsSetShapeData(data, 18); lovrGraphicsSetStreamData(data, 18);
lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 0, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 0, 0);
} }
} }
void lovrGraphicsPlane(DrawMode mode, Texture* texture, mat4 transform) { void lovrGraphicsPlane(DrawMode mode, mat4 transform) {
lovrGraphicsPush(); lovrGraphicsPush();
lovrGraphicsMatrixTransform(MATRIX_MODEL, transform); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
@ -512,9 +579,8 @@ void lovrGraphicsPlane(DrawMode mode, Texture* texture, mat4 transform) {
-.5, -.5, 0 -.5, -.5, 0
}; };
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(points, 12); lovrGraphicsSetStreamData(points, 12);
lovrGraphicsDrawPrimitive(GL_LINE_LOOP, 0, 0, 0); lovrGraphicsDrawPrimitive(GL_LINE_LOOP, 0, 0, 0);
} else if (mode == DRAW_MODE_FILL) { } else if (mode == DRAW_MODE_FILL) {
float data[] = { float data[] = {
@ -524,9 +590,8 @@ void lovrGraphicsPlane(DrawMode mode, Texture* texture, mat4 transform) {
.5, -.5, 0, 0, 0, -1, 1, 1 .5, -.5, 0, 0, 0, -1, 1, 1
}; };
lovrGraphicsBindTexture(texture);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(data, 32); lovrGraphicsSetStreamData(data, 32);
lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 1, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 1, 0);
} }
@ -541,13 +606,16 @@ void lovrGraphicsPlaneFullscreen(Texture* texture) {
1, -1, 0, 1, 0 1, -1, 0, 1, 0
}; };
lovrGraphicsBindTexture(texture);
lovrGraphicsSetDefaultShader(SHADER_FULLSCREEN); lovrGraphicsSetDefaultShader(SHADER_FULLSCREEN);
lovrGraphicsSetShapeData(data, 20); Material* material = lovrGraphicsGetMaterial();
Texture* lastTexture = lovrMaterialGetTexture(material, TEXTURE_DIFFUSE);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
lovrGraphicsSetStreamData(data, 20);
lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 0, 1, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 0, 1, 0);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, lastTexture);
} }
void lovrGraphicsBox(DrawMode mode, Texture* texture, mat4 transform) { void lovrGraphicsBox(DrawMode mode, mat4 transform) {
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsPush(); lovrGraphicsPush();
lovrGraphicsMatrixTransform(MATRIX_MODEL, transform); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
@ -573,9 +641,8 @@ void lovrGraphicsBox(DrawMode mode, Texture* texture, mat4 transform) {
0, 4, 1, 5, 2, 6, 3, 7 // Connections 0, 4, 1, 5, 2, 6, 3, 7 // Connections
}; };
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(points, 24); lovrGraphicsSetStreamData(points, 24);
lovrGraphicsSetIndexData(indices, 24); lovrGraphicsSetIndexData(indices, 24);
lovrGraphicsDrawPrimitive(GL_LINES, 0, 0, 1); lovrGraphicsDrawPrimitive(GL_LINES, 0, 0, 1);
} else { } else {
@ -621,9 +688,8 @@ void lovrGraphicsBox(DrawMode mode, Texture* texture, mat4 transform) {
.5, .5, .5, 0, 1, 0, 1, 0 .5, .5, .5, 0, 1, 0, 1, 0
}; };
lovrGraphicsBindTexture(texture);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsSetShapeData(data, 208); lovrGraphicsSetStreamData(data, 208);
lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 1, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 1, 1, 0);
} }
@ -729,14 +795,13 @@ void lovrGraphicsCylinder(float x1, float y1, float z1, float x2, float y2, floa
} }
} }
lovrGraphicsBindTexture(NULL);
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsDrawPrimitive(GL_TRIANGLES, 1, 0, 1); lovrGraphicsDrawPrimitive(GL_TRIANGLES, 1, 0, 1);
#undef PUSH_CYLINDER_VERTEX #undef PUSH_CYLINDER_VERTEX
#undef PUSH_CYLINDER_TRIANGLE #undef PUSH_CYLINDER_TRIANGLE
} }
void lovrGraphicsSphere(Texture* texture, mat4 transform, int segments, Skybox* skybox) { void lovrGraphicsSphere(mat4 transform, int segments) {
vec_clear(&state.streamData); vec_clear(&state.streamData);
vec_clear(&state.streamIndices); vec_clear(&state.streamIndices);
@ -753,11 +818,9 @@ void lovrGraphicsSphere(Texture* texture, mat4 transform, int segments, Skybox*
vec_push(&state.streamData, y); vec_push(&state.streamData, y);
vec_push(&state.streamData, z); vec_push(&state.streamData, z);
if (!skybox) { vec_push(&state.streamData, x);
vec_push(&state.streamData, x); vec_push(&state.streamData, y);
vec_push(&state.streamData, y); vec_push(&state.streamData, z);
vec_push(&state.streamData, z);
}
vec_push(&state.streamData, u); vec_push(&state.streamData, u);
vec_push(&state.streamData, v); vec_push(&state.streamData, v);
@ -779,23 +842,20 @@ void lovrGraphicsSphere(Texture* texture, mat4 transform, int segments, Skybox*
} }
} }
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); if (transform) {
if (skybox) {
Texture* oldTexture = lovrGraphicsGetTexture();
glBindTexture(GL_TEXTURE_2D, skybox->texture);
lovrGraphicsDrawPrimitive(GL_TRIANGLES, 0, 1, 1);
glBindTexture(GL_TEXTURE_2D, oldTexture->id);
} else {
lovrGraphicsBindTexture(texture);
lovrGraphicsPush(); lovrGraphicsPush();
lovrGraphicsMatrixTransform(MATRIX_MODEL, transform); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
lovrGraphicsDrawPrimitive(GL_TRIANGLES, 1, 1, 1); }
lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsDrawPrimitive(GL_TRIANGLES, 1, 1, 1);
if (transform) {
lovrGraphicsPop(); lovrGraphicsPop();
} }
} }
void lovrGraphicsSkybox(Skybox* skybox, float angle, float ax, float ay, float az) { void lovrGraphicsSkybox(Texture* texture, float angle, float ax, float ay, float az) {
lovrGraphicsPush(); lovrGraphicsPush();
lovrGraphicsOrigin(); lovrGraphicsOrigin();
lovrGraphicsRotate(MATRIX_MODEL, angle, ax, ay, az); lovrGraphicsRotate(MATRIX_MODEL, angle, ax, ay, az);
@ -803,7 +863,7 @@ void lovrGraphicsSkybox(Skybox* skybox, float angle, float ax, float ay, float a
int wasCulling = lovrGraphicsIsCullingEnabled(); int wasCulling = lovrGraphicsIsCullingEnabled();
lovrGraphicsSetCullingEnabled(0); lovrGraphicsSetCullingEnabled(0);
if (skybox->type == SKYBOX_CUBE) { if (texture->type == TEXTURE_CUBE) {
float cube[] = { float cube[] = {
// Front // Front
1.f, -1.f, -1.f, 1.f, -1.f, -1.f,
@ -846,15 +906,19 @@ void lovrGraphicsSkybox(Skybox* skybox, float angle, float ax, float ay, float a
1.f, 1.f, 1.f 1.f, 1.f, 1.f
}; };
lovrGraphicsSetShapeData(cube, 78); lovrGraphicsSetStreamData(cube, 78);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox->texture);
lovrGraphicsSetDefaultShader(SHADER_SKYBOX); lovrGraphicsSetDefaultShader(SHADER_SKYBOX);
Material* material = lovrGraphicsGetMaterial();
Texture* lastTexture = lovrMaterialGetTexture(material, TEXTURE_ENVIRONMENT_MAP);
lovrMaterialSetTexture(material, TEXTURE_ENVIRONMENT_MAP, texture);
lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 0, 0, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLE_STRIP, 0, 0, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); lovrMaterialSetTexture(material, TEXTURE_ENVIRONMENT_MAP, lastTexture);
glActiveTexture(GL_TEXTURE0); } else if (texture->type == TEXTURE_2D) {
} else if (skybox->type == SKYBOX_PANORAMA) { Material* material = lovrGraphicsGetMaterial();
lovrGraphicsSphere(NULL, NULL, 30, skybox); Texture* lastTexture = lovrMaterialGetTexture(material, TEXTURE_DIFFUSE);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, texture);
lovrGraphicsSphere(NULL, 30);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, lastTexture);
} }
lovrGraphicsSetCullingEnabled(wasCulling); lovrGraphicsSetCullingEnabled(wasCulling);
@ -872,68 +936,80 @@ void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAl
lovrGraphicsMatrixTransform(MATRIX_MODEL, transform); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
lovrGraphicsScale(MATRIX_MODEL, scale, scale, scale); lovrGraphicsScale(MATRIX_MODEL, scale, scale, scale);
lovrGraphicsTranslate(MATRIX_MODEL, 0, offsety, 0); lovrGraphicsTranslate(MATRIX_MODEL, 0, offsety, 0);
lovrGraphicsBindTexture(font->texture);
lovrGraphicsSetDefaultShader(SHADER_FONT); lovrGraphicsSetDefaultShader(SHADER_FONT);
Material* material = lovrGraphicsGetMaterial();
Texture* lastTexture = lovrMaterialGetTexture(material, TEXTURE_DIFFUSE);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, font->texture);
glDepthMask(GL_FALSE);
lovrGraphicsDrawPrimitive(GL_TRIANGLES, 0, 1, 0); lovrGraphicsDrawPrimitive(GL_TRIANGLES, 0, 1, 0);
glDepthMask(GL_TRUE);
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, lastTexture);
lovrGraphicsPop(); lovrGraphicsPop();
} }
// Internal State // Internal State
void lovrGraphicsPushCanvas() { void lovrGraphicsPushView() {
if (++state.canvas >= MAX_CANVASES) { if (++state.view >= MAX_VIEWS) {
lovrThrow("Canvas overflow"); lovrThrow("View overflow");
} }
memcpy(&state.canvases[state.canvas], &state.canvases[state.canvas - 1], sizeof(CanvasState)); memcpy(&state.views[state.view], &state.views[state.view - 1], sizeof(View));
} }
void lovrGraphicsPopCanvas() { void lovrGraphicsPopView() {
if (--state.canvas < 0) { if (--state.view < 0) {
lovrThrow("Canvas underflow"); lovrThrow("View underflow");
} }
int* viewport = state.canvases[state.canvas].viewport; int* viewport = state.views[state.view].viewport;
lovrGraphicsSetViewport(viewport[0], viewport[1], viewport[2], viewport[3]); lovrGraphicsSetViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
lovrGraphicsBindFramebuffer(state.canvases[state.canvas].framebuffer); lovrGraphicsBindFramebuffer(state.views[state.view].framebuffer);
} }
mat4 lovrGraphicsGetProjection() { mat4 lovrGraphicsGetProjection() {
return state.canvases[state.canvas].projection; return state.views[state.view].projection;
} }
void lovrGraphicsSetProjection(mat4 projection) { void lovrGraphicsSetProjection(mat4 projection) {
memcpy(state.canvases[state.canvas].projection, projection, 16 * sizeof(float)); memcpy(state.views[state.view].projection, projection, 16 * sizeof(float));
} }
void lovrGraphicsSetViewport(int x, int y, int w, int h) { void lovrGraphicsSetViewport(int x, int y, int w, int h) {
state.canvases[state.canvas].viewport[0] = x; state.views[state.view].viewport[0] = x;
state.canvases[state.canvas].viewport[1] = y; state.views[state.view].viewport[1] = y;
state.canvases[state.canvas].viewport[2] = w; state.views[state.view].viewport[2] = w;
state.canvases[state.canvas].viewport[3] = h; state.views[state.view].viewport[3] = h;
glViewport(x, y, w, h); glViewport(x, y, w, h);
} }
void lovrGraphicsBindFramebuffer(int framebuffer) { void lovrGraphicsBindFramebuffer(int framebuffer) {
state.canvases[state.canvas].framebuffer = framebuffer; state.views[state.view].framebuffer = framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
} }
Texture* lovrGraphicsGetTexture() { Texture* lovrGraphicsGetTexture(int slot) {
return state.texture; return state.textures[slot];
} }
void lovrGraphicsBindTexture(Texture* texture) { void lovrGraphicsBindTexture(Texture* texture, TextureType type, int slot) {
if (!texture) { if (!texture) {
if (!state.defaultTexture) { if (!state.defaultTexture) {
state.defaultTexture = lovrTextureCreate(lovrTextureDataGetBlank(1, 1, 0xff, FORMAT_RGBA)); TextureData* textureData = lovrTextureDataGetBlank(1, 1, 0xff, FORMAT_RGBA);
state.defaultTexture = lovrTextureCreate(TEXTURE_2D, &textureData, 1);
} }
texture = state.defaultTexture; texture = state.defaultTexture;
} }
if (texture != state.texture) { if (texture != state.textures[slot]) {
state.texture = texture; if (state.textures[slot]) {
glBindTexture(GL_TEXTURE_2D, texture->id); lovrRelease(&state.textures[slot]->ref);
}
state.textures[slot] = texture;
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(type, texture->id);
lovrRetain(&texture->ref);
} }
} }

View File

@ -1,16 +1,17 @@
#include "graphics/font.h" #include "graphics/font.h"
#include "graphics/material.h"
#include "graphics/shader.h" #include "graphics/shader.h"
#include "graphics/skybox.h"
#include "graphics/texture.h" #include "graphics/texture.h"
#include "math/math.h" #include "math/math.h"
#include "lib/glfw.h" #include "lib/glfw.h"
#pragma once #pragma once
#define MAX_CANVASES 4 #define MAX_VIEWS 4
#define MAX_TRANSFORMS 60 #define MAX_TRANSFORMS 60
#define INTERNAL_TRANSFORMS 4 #define INTERNAL_TRANSFORMS 4
#define DEFAULT_SHADER_COUNT 4 #define DEFAULT_SHADER_COUNT 4
#define MAX_TEXTURES 16
typedef enum { typedef enum {
BLEND_ALPHA, BLEND_ALPHA,
@ -48,6 +49,11 @@ typedef enum {
COMPARE_GREATER = GL_GREATER COMPARE_GREATER = GL_GREATER
} CompareMode; } CompareMode;
typedef enum {
MATRIX_MODEL,
MATRIX_VIEW
} MatrixType;
typedef struct { typedef struct {
int initialized; int initialized;
float pointSizes[2]; float pointSizes[2];
@ -60,17 +66,13 @@ typedef struct {
int framebuffer; int framebuffer;
float projection[16]; float projection[16];
int viewport[4]; int viewport[4];
} CanvasState; } View;
typedef enum {
MATRIX_MODEL,
MATRIX_VIEW
} MatrixType;
typedef struct { typedef struct {
GLFWwindow* window; GLFWwindow* window;
Shader* defaultShaders[DEFAULT_SHADER_COUNT]; Shader* defaultShaders[DEFAULT_SHADER_COUNT];
DefaultShader defaultShader; DefaultShader defaultShader;
Material* defaultMaterial;
Font* defaultFont; Font* defaultFont;
Texture* defaultTexture; Texture* defaultTexture;
float transforms[MAX_TRANSFORMS + INTERNAL_TRANSFORMS][2][16]; float transforms[MAX_TRANSFORMS + INTERNAL_TRANSFORMS][2][16];
@ -85,6 +87,7 @@ typedef struct {
Font* font; Font* font;
GraphicsLimits limits; GraphicsLimits limits;
float lineWidth; float lineWidth;
Material* material;
float pointSize; float pointSize;
Shader* shader; Shader* shader;
Winding winding; Winding winding;
@ -94,9 +97,9 @@ typedef struct {
uint32_t streamIBO; uint32_t streamIBO;
vec_float_t streamData; vec_float_t streamData;
vec_uint_t streamIndices; vec_uint_t streamIndices;
CanvasState canvases[MAX_CANVASES]; View views[MAX_VIEWS];
int canvas; int view;
Texture* texture; Texture* textures[MAX_TEXTURES];
uint32_t program; uint32_t program;
uint32_t vertexArray; uint32_t vertexArray;
uint32_t vertexBuffer; uint32_t vertexBuffer;
@ -132,6 +135,8 @@ void lovrGraphicsSetFont(Font* font);
GraphicsLimits lovrGraphicsGetLimits(); GraphicsLimits lovrGraphicsGetLimits();
float lovrGraphicsGetLineWidth(); float lovrGraphicsGetLineWidth();
void lovrGraphicsSetLineWidth(float width); void lovrGraphicsSetLineWidth(float width);
Material* lovrGraphicsGetMaterial();
void lovrGraphicsSetMaterial(Material* material);
float lovrGraphicsGetPointSize(); float lovrGraphicsGetPointSize();
void lovrGraphicsSetPointSize(float size); void lovrGraphicsSetPointSize(float size);
Shader* lovrGraphicsGetShader(); Shader* lovrGraphicsGetShader();
@ -154,23 +159,23 @@ void lovrGraphicsMatrixTransform(MatrixType type, mat4 transform);
void lovrGraphicsPoints(float* points, int count); void lovrGraphicsPoints(float* points, int count);
void lovrGraphicsLine(float* points, int count); void lovrGraphicsLine(float* points, int count);
void lovrGraphicsTriangle(DrawMode mode, float* points); void lovrGraphicsTriangle(DrawMode mode, float* points);
void lovrGraphicsPlane(DrawMode mode, Texture* texture, mat4 transform); void lovrGraphicsPlane(DrawMode mode, mat4 transform);
void lovrGraphicsPlaneFullscreen(Texture* texture); void lovrGraphicsPlaneFullscreen(Texture* texture);
void lovrGraphicsBox(DrawMode mode, Texture* texture, mat4 transform); void lovrGraphicsBox(DrawMode mode, mat4 transform);
void lovrGraphicsCylinder(float x1, float y1, float z1, float x2, float y2, float z2, float r1, float r2, int capped, int segments); void lovrGraphicsCylinder(float x1, float y1, float z1, float x2, float y2, float z2, float r1, float r2, int capped, int segments);
void lovrGraphicsSphere(Texture* texture, mat4 transform, int segments, Skybox* skybox); void lovrGraphicsSphere(mat4 transform, int segments);
void lovrGraphicsSkybox(Skybox* skybox, float angle, float ax, float ay, float az); void lovrGraphicsSkybox(Texture* texture, float angle, float ax, float ay, float az);
void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAlign halign, VerticalAlign valign); void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAlign halign, VerticalAlign valign);
// Internal State // Internal State
void lovrGraphicsPushCanvas(); void lovrGraphicsPushView();
void lovrGraphicsPopCanvas(); void lovrGraphicsPopView();
mat4 lovrGraphicsGetProjection(); mat4 lovrGraphicsGetProjection();
void lovrGraphicsSetProjection(mat4 projection); void lovrGraphicsSetProjection(mat4 projection);
void lovrGraphicsSetViewport(int x, int y, int w, int h); void lovrGraphicsSetViewport(int x, int y, int w, int h);
void lovrGraphicsBindFramebuffer(int framebuffer); void lovrGraphicsBindFramebuffer(int framebuffer);
Texture* lovrGraphicsGetTexture(); Texture* lovrGraphicsGetTexture();
void lovrGraphicsBindTexture(Texture* texture); void lovrGraphicsBindTexture(Texture* texture, TextureType type, int slot);
void lovrGraphicsSetDefaultShader(DefaultShader defaultShader); void lovrGraphicsSetDefaultShader(DefaultShader defaultShader);
Shader* lovrGraphicsGetActiveShader(); Shader* lovrGraphicsGetActiveShader();
void lovrGraphicsBindProgram(uint32_t program); void lovrGraphicsBindProgram(uint32_t program);

46
src/graphics/material.c Normal file
View File

@ -0,0 +1,46 @@
#include "graphics/graphics.h"
#include "graphics/material.h"
Material* lovrMaterialCreate(MaterialData* materialData, int isDefault) {
Material* material = lovrAlloc(sizeof(Material), lovrMaterialDestroy);
if (!material) return NULL;
material->materialData = materialData;
material->isDefault = isDefault;
for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) {
if (materialData->textures[i]) {
material->textures[i] = lovrTextureCreate(TEXTURE_2D, &materialData->textures[i], 1);
} else {
material->textures[i] = NULL;
}
}
return material;
}
void lovrMaterialDestroy(const Ref* ref) {
Material* material = containerof(ref, Material);
for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) {
if (material->textures[i]) {
lovrRelease(&material->textures[i]->ref);
}
}
free(material);
}
Color lovrMaterialGetColor(Material* material, MaterialColor colorType) {
return material->materialData->colors[colorType];
}
void lovrMaterialSetColor(Material* material, MaterialColor colorType, Color color) {
material->materialData->colors[colorType] = color;
}
Texture* lovrMaterialGetTexture(Material* material, MaterialTexture textureType) {
return material->textures[textureType];
}
void lovrMaterialSetTexture(Material* material, MaterialTexture textureType, Texture* texture) {
material->textures[textureType] = texture;
}

19
src/graphics/material.h Normal file
View File

@ -0,0 +1,19 @@
#include "util.h"
#include "graphics/texture.h"
#include "loaders/material.h"
#pragma once
typedef struct {
Ref ref;
MaterialData* materialData;
Texture* textures[MAX_MATERIAL_TEXTURES];
int isDefault;
} Material;
Material* lovrMaterialCreate(MaterialData* materialData, int isDefault);
void lovrMaterialDestroy(const Ref* ref);
Color lovrMaterialGetColor(Material* material, MaterialColor colorType);
void lovrMaterialSetColor(Material* material, MaterialColor colorType, Color color);
Texture* lovrMaterialGetTexture(Material* material, MaterialTexture textureType);
void lovrMaterialSetTexture(Material* material, MaterialTexture textureType, Texture* texture);

View File

@ -1,5 +1,6 @@
#include "graphics/mesh.h" #include "graphics/mesh.h"
#include "graphics/graphics.h" #include "graphics/graphics.h"
#include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -43,7 +44,6 @@ Mesh* lovrMeshCreate(size_t count, MeshFormat* format, MeshDrawMode drawMode, Me
Mesh* mesh = lovrAlloc(sizeof(Mesh), lovrMeshDestroy); Mesh* mesh = lovrAlloc(sizeof(Mesh), lovrMeshDestroy);
if (!mesh) return NULL; if (!mesh) return NULL;
vec_init(&mesh->map);
vec_init(&mesh->format); vec_init(&mesh->format);
if (format) { if (format) {
@ -79,10 +79,12 @@ Mesh* lovrMeshCreate(size_t count, MeshFormat* format, MeshDrawMode drawMode, Me
mesh->vao = 0; mesh->vao = 0;
mesh->vbo = 0; mesh->vbo = 0;
mesh->ibo = 0; mesh->ibo = 0;
mesh->indices = NULL;
mesh->indexCount = 0;
mesh->indexSize = count > USHRT_MAX ? sizeof(uint32_t) : sizeof(uint16_t);
mesh->isRangeEnabled = 0; mesh->isRangeEnabled = 0;
mesh->rangeStart = 0; mesh->rangeStart = 0;
mesh->rangeCount = mesh->count; mesh->rangeCount = mesh->count;
mesh->texture = NULL;
mesh->lastShader = NULL; mesh->lastShader = NULL;
glGenBuffers(1, &mesh->vbo); glGenBuffers(1, &mesh->vbo);
@ -100,14 +102,11 @@ Mesh* lovrMeshCreate(size_t count, MeshFormat* format, MeshDrawMode drawMode, Me
void lovrMeshDestroy(const Ref* ref) { void lovrMeshDestroy(const Ref* ref) {
Mesh* mesh = containerof(ref, Mesh); Mesh* mesh = containerof(ref, Mesh);
if (mesh->texture) {
lovrRelease(&mesh->texture->ref);
}
glDeleteBuffers(1, &mesh->vbo); glDeleteBuffers(1, &mesh->vbo);
glDeleteBuffers(1, &mesh->ibo); glDeleteBuffers(1, &mesh->ibo);
glDeleteVertexArrays(1, &mesh->vao); glDeleteVertexArrays(1, &mesh->vao);
vec_deinit(&mesh->map);
vec_deinit(&mesh->format); vec_deinit(&mesh->format);
free(mesh->indices);
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
free(mesh->data); free(mesh->data);
#endif #endif
@ -119,21 +118,28 @@ void lovrMeshDraw(Mesh* mesh, mat4 transform) {
lovrMeshUnmap(mesh); lovrMeshUnmap(mesh);
} }
lovrGraphicsPush(); if (transform) {
lovrGraphicsMatrixTransform(MATRIX_MODEL, transform); lovrGraphicsPush();
lovrGraphicsBindTexture(mesh->texture); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
}
lovrGraphicsSetDefaultShader(SHADER_DEFAULT); lovrGraphicsSetDefaultShader(SHADER_DEFAULT);
lovrGraphicsPrepare(); lovrGraphicsPrepare();
lovrGraphicsBindVertexArray(mesh->vao); lovrGraphicsBindVertexArray(mesh->vao);
lovrMeshBindAttributes(mesh); lovrMeshBindAttributes(mesh);
size_t start = mesh->rangeStart; size_t start = mesh->rangeStart;
size_t count = mesh->rangeCount; size_t count = mesh->rangeCount;
if (mesh->map.length > 0) { if (mesh->indexCount > 0) {
glDrawElements(mesh->drawMode, mesh->map.length, GL_UNSIGNED_INT, (GLvoid*) start); count = mesh->isRangeEnabled ? mesh->rangeCount : mesh->indexCount;
GLenum indexType = mesh->indexSize == sizeof(uint16_t) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
glDrawElements(mesh->drawMode, count, indexType, (GLvoid*) (start * mesh->indexSize));
} else { } else {
glDrawArrays(mesh->drawMode, start, count); glDrawArrays(mesh->drawMode, start, count);
} }
lovrGraphicsPop();
if (transform) {
lovrGraphicsPop();
}
} }
MeshFormat lovrMeshGetVertexFormat(Mesh* mesh) { MeshFormat lovrMeshGetVertexFormat(Mesh* mesh) {
@ -144,9 +150,8 @@ MeshDrawMode lovrMeshGetDrawMode(Mesh* mesh) {
return mesh->drawMode; return mesh->drawMode;
} }
int lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode) { void lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode) {
mesh->drawMode = drawMode; mesh->drawMode = drawMode;
return 0;
} }
int lovrMeshGetVertexCount(Mesh* mesh) { int lovrMeshGetVertexCount(Mesh* mesh) {
@ -157,21 +162,22 @@ int lovrMeshGetVertexSize(Mesh* mesh) {
return mesh->stride; return mesh->stride;
} }
unsigned int* lovrMeshGetVertexMap(Mesh* mesh, size_t* count) { void* lovrMeshGetVertexMap(Mesh* mesh, size_t* count) {
*count = mesh->map.length; *count = mesh->indexCount;
return mesh->map.data; return mesh->indices;
} }
void lovrMeshSetVertexMap(Mesh* mesh, unsigned int* map, size_t count) { void lovrMeshSetVertexMap(Mesh* mesh, void* data, size_t count) {
if (count == 0 || !map) { if (!data || count == 0) {
vec_clear(&mesh->map); mesh->indexCount = 0;
} else { return;
vec_clear(&mesh->map);
vec_pusharr(&mesh->map, map, count);
lovrGraphicsBindVertexArray(mesh->vao);
lovrGraphicsBindIndexBuffer(mesh->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(unsigned int), mesh->map.data, GL_STATIC_DRAW);
} }
mesh->indices = data;
mesh->indexCount = count;
lovrGraphicsBindVertexArray(mesh->vao);
lovrGraphicsBindIndexBuffer(mesh->ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indexCount * mesh->indexSize, mesh->indices, GL_STATIC_DRAW);
} }
int lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name) { int lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name) {
@ -223,32 +229,12 @@ void lovrMeshGetDrawRange(Mesh* mesh, int* start, int* count) {
*count = mesh->rangeCount; *count = mesh->rangeCount;
} }
int lovrMeshSetDrawRange(Mesh* mesh, int start, int count) { void lovrMeshSetDrawRange(Mesh* mesh, int start, int count) {
if (start < 0 || count < 0 || (size_t) start + count > mesh->count) { size_t limit = mesh->indexCount > 0 ? mesh->indexCount : mesh->count;
return 1; int isValidRange = start >= 0 && count >= 0 && (size_t) start + count <= limit;
} lovrAssert(isValidRange, "Invalid mesh draw range [%d, %d]", start + 1, start + count + 1);
mesh->rangeStart = start; mesh->rangeStart = start;
mesh->rangeCount = count; mesh->rangeCount = count;
return 0;
}
Texture* lovrMeshGetTexture(Mesh* mesh) {
return mesh->texture;
}
void lovrMeshSetTexture(Mesh* mesh, Texture* texture) {
if (mesh->texture != texture) {
if (mesh->texture) {
lovrRelease(&mesh->texture->ref);
}
mesh->texture = texture;
if (mesh->texture) {
lovrRetain(&mesh->texture->ref);
}
}
} }
void* lovrMeshMap(Mesh* mesh, int start, size_t count, int read, int write) { void* lovrMeshMap(Mesh* mesh, int start, size_t count, int read, int write) {

View File

@ -49,11 +49,12 @@ typedef struct {
GLuint vao; GLuint vao;
GLuint vbo; GLuint vbo;
GLuint ibo; GLuint ibo;
vec_uint_t map; void* indices;
size_t indexCount;
size_t indexSize;
int isRangeEnabled; int isRangeEnabled;
int rangeStart; int rangeStart;
int rangeCount; int rangeCount;
Texture* texture;
Shader* lastShader; Shader* lastShader;
} Mesh; } Mesh;
@ -62,17 +63,17 @@ void lovrMeshDestroy(const Ref* ref);
void lovrMeshDraw(Mesh* mesh, mat4 transform); void lovrMeshDraw(Mesh* mesh, mat4 transform);
MeshFormat lovrMeshGetVertexFormat(Mesh* mesh); MeshFormat lovrMeshGetVertexFormat(Mesh* mesh);
MeshDrawMode lovrMeshGetDrawMode(Mesh* mesh); MeshDrawMode lovrMeshGetDrawMode(Mesh* mesh);
int lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode); void lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode);
int lovrMeshGetVertexCount(Mesh* mesh); int lovrMeshGetVertexCount(Mesh* mesh);
int lovrMeshGetVertexSize(Mesh* mesh); int lovrMeshGetVertexSize(Mesh* mesh);
unsigned int* lovrMeshGetVertexMap(Mesh* mesh, size_t* count); void* lovrMeshGetVertexMap(Mesh* mesh, size_t* count);
void lovrMeshSetVertexMap(Mesh* mesh, unsigned int* map, size_t count); void lovrMeshSetVertexMap(Mesh* mesh, void* data, size_t count);
int lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name); int lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name);
void lovrMeshSetAttributeEnabled(Mesh* mesh, const char* name, int enabled); void lovrMeshSetAttributeEnabled(Mesh* mesh, const char* name, int enabled);
int lovrMeshIsRangeEnabled(Mesh* mesh); int lovrMeshIsRangeEnabled(Mesh* mesh);
void lovrMeshSetRangeEnabled(Mesh* mesh, char isEnabled); void lovrMeshSetRangeEnabled(Mesh* mesh, char isEnabled);
void lovrMeshGetDrawRange(Mesh* mesh, int* start, int* count); void lovrMeshGetDrawRange(Mesh* mesh, int* start, int* count);
int lovrMeshSetDrawRange(Mesh* mesh, int start, int count); void lovrMeshSetDrawRange(Mesh* mesh, int start, int count);
Texture* lovrMeshGetTexture(Mesh* mesh); Texture* lovrMeshGetTexture(Mesh* mesh);
void lovrMeshSetTexture(Mesh* mesh, Texture* texture); void lovrMeshSetTexture(Mesh* mesh, Texture* texture);
void* lovrMeshMap(Mesh* mesh, int start, size_t count, int read, int write); void* lovrMeshMap(Mesh* mesh, int start, size_t count, int read, int write);

View File

@ -3,65 +3,31 @@
#include "math/mat4.h" #include "math/mat4.h"
#include "math/vec3.h" #include "math/vec3.h"
#include <stdlib.h> #include <stdlib.h>
#include <float.h>
static void visitNode(Model* model, ModelData* modelData, ModelNode* node, mat4 transform, vec_float_t* vertices, vec_uint_t* indices) { static void renderNode(Model* model, int nodeIndex) {
float newTransform[16]; ModelNode* node = &model->modelData->nodes[nodeIndex];
Material* currentMaterial = lovrGraphicsGetMaterial();
int useMaterials = currentMaterial->isDefault;
if (transform) { lovrGraphicsPush();
mat4_set(newTransform, transform); lovrGraphicsMatrixTransform(MATRIX_MODEL, node->transform);
} else {
mat4_identity(newTransform); for (int i = 0; i < node->primitives.length; i++) {
ModelPrimitive* primitive = &model->modelData->primitives[node->primitives.data[i]];
if (useMaterials) {
lovrGraphicsSetMaterial(model->materials[primitive->material]);
}
lovrMeshSetDrawRange(model->mesh, primitive->drawStart, primitive->drawCount);
lovrMeshDraw(model->mesh, NULL);
} }
mat4_multiply(newTransform, node->transform); for (int i = 0; i < node->children.length; i++) {
renderNode(model, node->children.data[i]);
int indexOffset = vertices->length / 3;
// Meshes
for (int m = 0; m < node->meshes.length; m++) {
ModelMesh* mesh = modelData->meshes.data[node->meshes.data[m]];
// Transformed vertices
for (int v = 0; v < mesh->vertices.length; v++) {
ModelVertex vertex = mesh->vertices.data[v];
float vec[3] = { vertex.x, vertex.y, vertex.z };
mat4_transform(newTransform, vec);
vec_pusharr(vertices, vec, 3);
model->aabb[0] = MIN(model->aabb[0], vec[0]);
model->aabb[1] = MAX(model->aabb[1], vec[0]);
model->aabb[2] = MIN(model->aabb[2], vec[1]);
model->aabb[3] = MAX(model->aabb[3], vec[1]);
model->aabb[4] = MIN(model->aabb[4], vec[2]);
model->aabb[5] = MAX(model->aabb[5], vec[2]);
if (modelData->hasNormals) {
ModelVertex normal = mesh->normals.data[v];
vec_push(vertices, normal.x);
vec_push(vertices, normal.y);
vec_push(vertices, normal.z);
}
if (modelData->hasTexCoords) {
ModelVertex texCoord = mesh->texCoords.data[v];
vec_push(vertices, texCoord.x);
vec_push(vertices, texCoord.y);
}
}
// Face vertex indices
for (int f = 0; f < mesh->faces.length; f++) {
ModelFace face = mesh->faces.data[f];
vec_push(indices, face.indices[0] + indexOffset);
vec_push(indices, face.indices[1] + indexOffset);
vec_push(indices, face.indices[2] + indexOffset);
}
} }
for (int c = 0; c < node->children.length; c++) { lovrGraphicsPop();
visitNode(model, modelData, node->children.data[c], newTransform, vertices, indices); if (useMaterials) {
lovrGraphicsSetMaterial(currentMaterial);
} }
} }
@ -70,85 +36,61 @@ Model* lovrModelCreate(ModelData* modelData) {
if (!model) return NULL; if (!model) return NULL;
model->modelData = modelData; model->modelData = modelData;
model->aabb[0] = FLT_MAX;
model->aabb[1] = FLT_MIN;
model->aabb[2] = FLT_MAX;
model->aabb[3] = FLT_MIN;
model->aabb[4] = FLT_MAX;
model->aabb[5] = FLT_MIN;
vec_float_t vertices;
vec_init(&vertices);
vec_uint_t indices;
vec_init(&indices);
visitNode(model, modelData, modelData->root, NULL, &vertices, &indices);
MeshFormat format; MeshFormat format;
vec_init(&format); vec_init(&format);
int components = 3; MeshAttribute attribute = { .name = "lovrPosition", .type = MESH_FLOAT, .count = 3 };
MeshAttribute position = { .name = "lovrPosition", .type = MESH_FLOAT, .count = 3 }; vec_push(&format, attribute);
vec_push(&format, position);
if (modelData->hasNormals) { if (modelData->hasNormals) {
MeshAttribute normal = { .name = "lovrNormal", .type = MESH_FLOAT, .count = 3 }; MeshAttribute attribute = { .name = "lovrNormal", .type = MESH_FLOAT, .count = 3 };
vec_push(&format, normal); vec_push(&format, attribute);
components += 3;
} }
if (modelData->hasTexCoords) { if (modelData->hasUVs) {
MeshAttribute texCoord = { .name = "lovrTexCoord", .type = MESH_FLOAT, .count = 2 }; MeshAttribute attribute = { .name = "lovrTexCoord", .type = MESH_FLOAT, .count = 2 };
vec_push(&format, texCoord); vec_push(&format, attribute);
components += 2;
} }
model->mesh = lovrMeshCreate(vertices.length / components, &format, MESH_TRIANGLES, MESH_STATIC); model->mesh = lovrMeshCreate(modelData->vertexCount, &format, MESH_TRIANGLES, MESH_STATIC);
void* data = lovrMeshMap(model->mesh, 0, vertices.length / components, 0, 1); void* data = lovrMeshMap(model->mesh, 0, modelData->vertexCount, 0, 1);
memcpy(data, vertices.data, vertices.length * sizeof(float)); memcpy(data, modelData->vertices, modelData->vertexCount * modelData->vertexSize * sizeof(float));
lovrMeshUnmap(model->mesh); lovrMeshUnmap(model->mesh);
lovrMeshSetVertexMap(model->mesh, indices.data, indices.length); lovrMeshSetVertexMap(model->mesh, modelData->indices, modelData->indexCount);
lovrMeshSetRangeEnabled(model->mesh, 1);
model->texture = NULL; model->materials = malloc(modelData->materialCount * sizeof(Material*));
for (int i = 0; i < modelData->materialCount; i++) {
model->materials[i] = lovrMaterialCreate(&modelData->materials[i], 0);
}
vec_deinit(&format); vec_deinit(&format);
vec_deinit(&vertices);
vec_deinit(&indices);
return model; return model;
} }
void lovrModelDestroy(const Ref* ref) { void lovrModelDestroy(const Ref* ref) {
Model* model = containerof(ref, Model); Model* model = containerof(ref, Model);
if (model->texture) { for (int i = 0; i < model->modelData->materialCount; i++) {
lovrRelease(&model->texture->ref); lovrRelease(&model->materials[i]->ref);
} }
free(model->materials);
lovrModelDataDestroy(model->modelData); lovrModelDataDestroy(model->modelData);
lovrRelease(&model->mesh->ref); lovrRelease(&model->mesh->ref);
free(model); free(model);
} }
void lovrModelDraw(Model* model, mat4 transform) { void lovrModelDraw(Model* model, mat4 transform) {
lovrMeshDraw(model->mesh, transform); if (model->modelData->nodeCount == 0) {
} return;
Texture* lovrModelGetTexture(Model* model) {
return model->texture;
}
void lovrModelSetTexture(Model* model, Texture* texture) {
if (model->texture) {
lovrRelease(&model->texture->ref);
} }
model->texture = texture; lovrGraphicsPush();
lovrMeshSetTexture(model->mesh, model->texture); lovrGraphicsMatrixTransform(MATRIX_MODEL, transform);
renderNode(model, 0);
if (model->texture) { lovrGraphicsPop();
lovrRetain(&model->texture->ref);
}
} }
float* lovrModelGetAABB(Model* model) { Mesh* lovrModelGetMesh(Model* model) {
return model->aabb; return model->mesh;
} }

View File

@ -1,4 +1,5 @@
#include "loaders/model.h" #include "loaders/model.h"
#include "graphics/material.h"
#include "graphics/mesh.h" #include "graphics/mesh.h"
#include "graphics/texture.h" #include "graphics/texture.h"
#include "math/math.h" #include "math/math.h"
@ -11,13 +12,10 @@ typedef struct {
Ref ref; Ref ref;
ModelData* modelData; ModelData* modelData;
Mesh* mesh; Mesh* mesh;
Texture* texture; Material** materials;
float aabb[6];
} Model; } Model;
Model* lovrModelCreate(ModelData* modelData); Model* lovrModelCreate(ModelData* modelData);
void lovrModelDestroy(const Ref* ref); void lovrModelDestroy(const Ref* ref);
void lovrModelDraw(Model* model, mat4 transform); void lovrModelDraw(Model* model, mat4 transform);
Texture* lovrModelGetTexture(Model* model); Mesh* lovrModelGetMesh(Model* model);
void lovrModelSetTexture(Model* model, Texture* texture);
float* lovrModelGetAABB(Model* model);

View File

@ -1,90 +1,66 @@
#include "graphics/shader.h" #include "graphics/shader.h"
#include "graphics/shaders.h"
#include "graphics/graphics.h" #include "graphics/graphics.h"
#include "math/mat4.h" #include "math/mat4.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
static const char* lovrShaderVertexPrefix = "" static UniformType getUniformType(GLenum type, const char* debug) {
#ifdef EMSCRIPTEN switch (type) {
"#version 300 es \n" case GL_FLOAT:
"precision mediump float; \n" case GL_FLOAT_VEC2:
#else case GL_FLOAT_VEC3:
"#version 150 \n" case GL_FLOAT_VEC4:
#endif return UNIFORM_FLOAT;
"in vec3 lovrPosition; \n"
"in vec3 lovrNormal; \n"
"in vec2 lovrTexCoord; \n"
"out vec2 texCoord; \n"
"uniform mat4 lovrModel; \n"
"uniform mat4 lovrView; \n"
"uniform mat4 lovrProjection; \n"
"uniform mat4 lovrTransform; \n"
"uniform mat3 lovrNormalMatrix; \n";
static const char* lovrShaderFragmentPrefix = "" case GL_INT:
#ifdef EMSCRIPTEN case GL_INT_VEC2:
"#version 300 es \n" case GL_INT_VEC3:
"precision mediump float; \n" case GL_INT_VEC4:
#else return UNIFORM_INT;
"#version 150 \n"
"in vec4 gl_FragCoord; \n"
#endif
"in vec2 texCoord; \n"
"out vec4 lovrFragColor; \n"
"uniform vec4 lovrColor; \n"
"uniform sampler2D lovrTexture; \n";
static const char* lovrShaderVertexSuffix = "" case GL_FLOAT_MAT2:
"void main() { \n" case GL_FLOAT_MAT3:
" texCoord = lovrTexCoord; \n" case GL_FLOAT_MAT4:
" gl_Position = position(lovrProjection, lovrTransform, vec4(lovrPosition, 1.0)); \n" return UNIFORM_MATRIX;
"}";
static const char* lovrShaderFragmentSuffix = "" case GL_SAMPLER_2D:
"void main() { \n" case GL_SAMPLER_CUBE:
" lovrFragColor = color(lovrColor, lovrTexture, texCoord); \n" return UNIFORM_SAMPLER;
"}";
static const char* lovrDefaultVertexShader = "" default:
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n" lovrThrow("Unsupported type for uniform '%s'", debug);
" return projection * transform * vertex; \n" return UNIFORM_FLOAT;
"}"; }
}
static const char* lovrDefaultFragmentShader = "" static int getUniformComponents(GLenum type) {
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n" switch (type) {
" return graphicsColor * texture(image, uv); \n" case GL_FLOAT:
"}"; case GL_INT:
case GL_SAMPLER_2D:
case GL_SAMPLER_CUBE:
return 1;
static const char* lovrSkyboxVertexShader = "" case GL_FLOAT_VEC2:
"out vec3 texturePosition; \n" case GL_INT_VEC2:
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n" case GL_FLOAT_MAT2:
" texturePosition = vertex.xyz; \n" return 2;
" return projection * transform * vertex; \n"
"}";
static const char* lovrSkyboxFragmentShader = "" case GL_FLOAT_VEC3:
"in vec3 texturePosition; \n" case GL_INT_VEC3:
"uniform samplerCube cube; \n" case GL_FLOAT_MAT3:
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n" return 3;
" return graphicsColor * texture(cube, texturePosition); \n"
"}";
static const char* lovrFontFragmentShader = "" case GL_FLOAT_VEC4:
"float median(float r, float g, float b) { \n" case GL_INT_VEC4:
" return max(min(r, g), min(max(r, g), b)); \n" case GL_FLOAT_MAT4:
"} \n" return 4;
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n"
" vec3 col = texture(image, uv).rgb; \n"
" float sdf = median(col.r, col.g, col.b); \n"
" float w = fwidth(sdf); \n"
" float alpha = smoothstep(.5 - w, .5 + w, sdf); \n"
" return vec4(graphicsColor.rgb, graphicsColor.a * alpha); \n"
"}";
static const char* lovrNoopVertexShader = "" default:
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n" return 1;
" return vertex; \n" }
"}"; }
static GLuint compileShader(GLenum type, const char* source) { static GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type); GLuint shader = glCreateShader(type);
@ -160,46 +136,103 @@ Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource) {
// Link // Link
GLuint id = linkShaders(vertexShader, fragmentShader); GLuint id = linkShaders(vertexShader, fragmentShader);
shader->id = id;
// Compute information about uniforms lovrGraphicsBindProgram(id);
// Uniform introspection
GLint uniformCount; GLint uniformCount;
int textureSlot = 0;
GLsizei bufferSize = LOVR_MAX_UNIFORM_LENGTH / sizeof(GLchar); GLsizei bufferSize = LOVR_MAX_UNIFORM_LENGTH / sizeof(GLchar);
map_init(&shader->uniforms); map_init(&shader->uniforms);
glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount);
for (int i = 0; i < uniformCount; i++) { for (int i = 0; i < uniformCount; i++) {
Uniform uniform; Uniform uniform;
glGetActiveUniform(id, i, bufferSize, NULL, &uniform.count, &uniform.type, uniform.name); glGetActiveUniform(id, i, bufferSize, NULL, &uniform.count, &uniform.glType, uniform.name);
char* subscript = strchr(uniform.name, '['); char* subscript = strchr(uniform.name, '[');
if (subscript) { if (subscript) {
*subscript = '\0'; *subscript = '\0';
} }
uniform.location = glGetUniformLocation(id, uniform.name);
uniform.index = i; uniform.index = i;
uniform.location = glGetUniformLocation(id, uniform.name);
uniform.type = getUniformType(uniform.glType, uniform.name);
uniform.components = getUniformComponents(uniform.glType);
uniform.baseTextureSlot = (uniform.type == UNIFORM_SAMPLER) ? textureSlot : -1;
switch (uniform.type) {
case UNIFORM_FLOAT:
uniform.size = uniform.components * uniform.count * sizeof(float);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_INT:
uniform.size = uniform.components * uniform.count * sizeof(int);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_MATRIX:
uniform.size = uniform.components * uniform.components * uniform.count * sizeof(int);
uniform.value.data = malloc(uniform.size);
break;
case UNIFORM_SAMPLER:
uniform.size = uniform.components * uniform.count * MAX(sizeof(Texture*), sizeof(int));
uniform.value.data = malloc(uniform.size);
// Use the value for ints to bind texture slots, but use the value for textures afterwards.
for (int i = 0; i < uniform.count; i++) {
uniform.value.ints[i] = uniform.baseTextureSlot + i;
}
glUniform1iv(uniform.location, uniform.count, uniform.value.ints);
break;
}
memset(uniform.value.data, 0, uniform.size);
size_t offset = 0;
for (int j = 0; j < uniform.count; j++) {
int location = uniform.location;
if (uniform.count > 1) {
char name[LOVR_MAX_UNIFORM_LENGTH];
snprintf(name, LOVR_MAX_UNIFORM_LENGTH, "%s[%d]", uniform.name, j);
location = glGetUniformLocation(id, name);
}
switch (uniform.type) {
case UNIFORM_FLOAT:
glGetUniformfv(id, location, &uniform.value.floats[offset]);
offset += uniform.components;
break;
case UNIFORM_INT:
glGetUniformiv(id, location, &uniform.value.ints[offset]);
offset += uniform.components;
break;
case UNIFORM_MATRIX:
glGetUniformfv(id, location, &uniform.value.floats[offset]);
offset += uniform.components * uniform.components;
break;
default:
break;
}
}
map_set(&shader->uniforms, uniform.name, uniform); map_set(&shader->uniforms, uniform.name, uniform);
textureSlot += (uniform.type == UNIFORM_SAMPLER) ? uniform.count : 0;
} }
// Initial state
shader->id = id;
mat4_identity(shader->model);
mat4_identity(shader->view);
mat4_identity(shader->projection);
shader->color = (Color) { 0, 0, 0, 0 };
// Send initial uniform values to shader
lovrGraphicsBindProgram(id);
lovrShaderBind(shader, shader->model, shader->view, shader->projection, shader->color, 1);
return shader; return shader;
} }
Shader* lovrShaderCreateDefault(DefaultShader type) { Shader* lovrShaderCreateDefault(DefaultShader type) {
switch (type) { switch (type) {
case SHADER_DEFAULT: return lovrShaderCreate(NULL, NULL); case SHADER_DEFAULT: return lovrShaderCreate(NULL, NULL);
case SHADER_SKYBOX: { case SHADER_SKYBOX: return lovrShaderCreate(lovrSkyboxVertexShader, lovrSkyboxFragmentShader); break;
Shader* shader = lovrShaderCreate(lovrSkyboxVertexShader, lovrSkyboxFragmentShader);
lovrShaderSendInt(shader, lovrShaderGetUniformId(shader, "cube"), 1);
return shader;
}
case SHADER_FONT: return lovrShaderCreate(NULL, lovrFontFragmentShader); case SHADER_FONT: return lovrShaderCreate(NULL, lovrFontFragmentShader);
case SHADER_FULLSCREEN: return lovrShaderCreate(lovrNoopVertexShader, NULL); case SHADER_FULLSCREEN: return lovrShaderCreate(lovrNoopVertexShader, NULL);
default: lovrThrow("Unknown default shader type"); default: lovrThrow("Unknown default shader type");
@ -213,64 +246,54 @@ void lovrShaderDestroy(const Ref* ref) {
free(shader); free(shader);
} }
void lovrShaderBind(Shader* shader, mat4 model, mat4 view, mat4 projection, Color color, int force) { void lovrShaderBind(Shader* shader) {
int dirtyModel = force || memcmp(shader->model, model, 16 * sizeof(float)); map_iter_t iter = map_iter(&shader->uniforms);
int dirtyView = force || memcmp(shader->view, view, 16 * sizeof(float)); const char* key;
int dirtyTransform = dirtyModel || dirtyView; while ((key = map_next(&shader->uniforms, &iter)) != NULL) {
int dirtyProjection = force || memcmp(shader->projection, projection, 16 * sizeof(float)); Uniform* uniform = map_get(&shader->uniforms, key);
int dirtyColor = force || memcmp(&shader->color, &color, sizeof(Color));
if (dirtyModel) { if (uniform->type != UNIFORM_SAMPLER && !uniform->dirty) {
int uniformId = lovrShaderGetUniformId(shader, "lovrModel"); continue;
lovrShaderSendFloatMat4(shader, uniformId, model);
memcpy(shader->model, model, 16 * sizeof(float));
}
if (dirtyView) {
int uniformId = lovrShaderGetUniformId(shader, "lovrView");
lovrShaderSendFloatMat4(shader, uniformId, view);
memcpy(shader->view, view, 16 * sizeof(float));
}
if (dirtyTransform) {
float matrix[16];
int uniformId = lovrShaderGetUniformId(shader, "lovrTransform");
mat4_multiply(mat4_set(matrix, view), model);
if (uniformId > -1) {
lovrShaderSendFloatMat4(shader, uniformId, matrix);
} }
if (mat4_invert(matrix)) { uniform->dirty = 0;
mat4_transpose(matrix); int count = uniform->count;
} else { void* data = uniform->value.data;
mat4_identity(matrix);
switch (uniform->type) {
case UNIFORM_FLOAT:
switch (uniform->components) {
case 1: glUniform1fv(uniform->location, count, data); break;
case 2: glUniform2fv(uniform->location, count, data); break;
case 3: glUniform3fv(uniform->location, count, data); break;
case 4: glUniform4fv(uniform->location, count, data); break;
}
break;
case UNIFORM_INT:
switch (uniform->components) {
case 1: glUniform1iv(uniform->location, count, data); break;
case 2: glUniform2iv(uniform->location, count, data); break;
case 3: glUniform3iv(uniform->location, count, data); break;
case 4: glUniform4iv(uniform->location, count, data); break;
}
break;
case UNIFORM_MATRIX:
switch (uniform->components) {
case 2: glUniformMatrix2fv(uniform->location, count, GL_FALSE, data); break;
case 3: glUniformMatrix3fv(uniform->location, count, GL_FALSE, data); break;
case 4: glUniformMatrix4fv(uniform->location, count, GL_FALSE, data); break;
}
break;
case UNIFORM_SAMPLER:
for (int i = 0; i < count; i++) {
TextureType type = uniform->glType == GL_SAMPLER_2D ? TEXTURE_2D : TEXTURE_CUBE;
lovrGraphicsBindTexture(uniform->value.textures[i], type, uniform->baseTextureSlot + i);
}
break;
} }
float normalMatrix[9] = {
matrix[0], matrix[1], matrix[2],
matrix[4], matrix[5], matrix[6],
matrix[8], matrix[9], matrix[10]
};
uniformId = lovrShaderGetUniformId(shader, "lovrNormalMatrix");
if (uniformId > -1) {
lovrShaderSendFloatMat3(shader, uniformId, normalMatrix);
}
}
if (dirtyProjection) {
int uniformId = lovrShaderGetUniformId(shader, "lovrProjection");
lovrShaderSendFloatMat4(shader, uniformId, projection);
memcpy(shader->projection, projection, 16 * sizeof(float));
}
if (dirtyColor) {
int uniformId = lovrShaderGetUniformId(shader, "lovrColor");
float c[4] = { color.r / 255., color.g / 255., color.b / 255., color.a / 255. };
lovrShaderSendFloatVec4(shader, uniformId, 1, c);
shader->color = color;
} }
} }
@ -282,51 +305,39 @@ int lovrShaderGetAttributeId(Shader* shader, const char* name) {
return glGetAttribLocation(shader->id, name); return glGetAttribLocation(shader->id, name);
} }
int lovrShaderGetUniformId(Shader* shader, const char* name) { Uniform* lovrShaderGetUniform(Shader* shader, const char* name) {
Uniform* uniform = map_get(&shader->uniforms, name); return map_get(&shader->uniforms, name);
return uniform ? uniform->location : -1;
} }
int lovrShaderGetUniformType(Shader* shader, const char* name, GLenum* type, int* count) { static void lovrShaderSetUniform(Shader* shader, const char* name, UniformType type, void* data, int count, size_t size, const char* debug) {
Uniform* uniform = map_get(&shader->uniforms, name); Uniform* uniform = map_get(&shader->uniforms, name);
if (!uniform) { if (!uniform) {
return 1; return;
} }
*type = uniform->type; lovrAssert(uniform->type == type, "Unable to send %ss to uniform %s", debug, uniform->name);
*count = uniform->count; lovrAssert(count * size == uniform->size, "Expected %d %ss for uniform %s, got %d", uniform->count, debug, uniform->name, count);
return 0;
if (!uniform->dirty && !memcmp(uniform->value.data, data, count * size)) {
return;
}
memcpy(uniform->value.data, data, count * size);
uniform->dirty = 1;
} }
void lovrShaderSendInt(Shader* shader, int id, int value) { void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count) {
glUniform1i(id, value); lovrShaderSetUniform(shader, name, UNIFORM_FLOAT, data, count, sizeof(float), "float");
} }
void lovrShaderSendFloat(Shader* shader, int id, float value) { void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count) {
glUniform1f(id, value); lovrShaderSetUniform(shader, name, UNIFORM_INT, data, count, sizeof(int), "int");
} }
void lovrShaderSendFloatVec2(Shader* shader, int id, int count, float* vector) { void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count) {
glUniform2fv(id, count, vector); lovrShaderSetUniform(shader, name, UNIFORM_MATRIX, data, count, sizeof(float), "float");
} }
void lovrShaderSendFloatVec3(Shader* shader, int id, int count, float* vector) { void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count) {
glUniform3fv(id, count, vector); lovrShaderSetUniform(shader, name, UNIFORM_SAMPLER, data, count, sizeof(Texture*), "texture");
}
void lovrShaderSendFloatVec4(Shader* shader, int id, int count, float* vector) {
glUniform4fv(id, count, vector);
}
void lovrShaderSendFloatMat2(Shader* shader, int id, float* matrix) {
glUniformMatrix2fv(id, 1, GL_FALSE, matrix);
}
void lovrShaderSendFloatMat3(Shader* shader, int id, float* matrix) {
glUniformMatrix3fv(id, 1, GL_FALSE, matrix);
}
void lovrShaderSendFloatMat4(Shader* shader, int id, float* matrix) {
glUniformMatrix4fv(id, 1, GL_FALSE, matrix);
} }

View File

@ -1,3 +1,4 @@
#include "graphics/texture.h"
#include "math/math.h" #include "math/math.h"
#include "lib/map/map.h" #include "lib/map/map.h"
#include "lib/glfw.h" #include "lib/glfw.h"
@ -10,6 +11,20 @@
#define LOVR_SHADER_TEX_COORD 2 #define LOVR_SHADER_TEX_COORD 2
#define LOVR_MAX_UNIFORM_LENGTH 256 #define LOVR_MAX_UNIFORM_LENGTH 256
typedef enum {
UNIFORM_FLOAT,
UNIFORM_MATRIX,
UNIFORM_INT,
UNIFORM_SAMPLER
} UniformType;
typedef union {
void* data;
int* ints;
float* floats;
Texture** textures;
} UniformValue;
typedef enum { typedef enum {
SHADER_DEFAULT, SHADER_DEFAULT,
SHADER_SKYBOX, SHADER_SKYBOX,
@ -19,10 +34,16 @@ typedef enum {
typedef struct { typedef struct {
GLchar name[LOVR_MAX_UNIFORM_LENGTH]; GLchar name[LOVR_MAX_UNIFORM_LENGTH];
GLenum glType;
int index; int index;
int location; int location;
GLenum type;
int count; int count;
int components;
size_t size;
UniformType type;
UniformValue value;
int baseTextureSlot;
int dirty;
} Uniform; } Uniform;
typedef map_t(Uniform) map_uniform_t; typedef map_t(Uniform) map_uniform_t;
@ -40,15 +61,10 @@ typedef struct {
Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource); Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource);
Shader* lovrShaderCreateDefault(DefaultShader type); Shader* lovrShaderCreateDefault(DefaultShader type);
void lovrShaderDestroy(const Ref* ref); void lovrShaderDestroy(const Ref* ref);
void lovrShaderBind(Shader* shader, mat4 model, mat4 view, mat4 projection, Color color, int force); void lovrShaderBind(Shader* shader);
int lovrShaderGetAttributeId(Shader* shader, const char* name); int lovrShaderGetAttributeId(Shader* shader, const char* name);
int lovrShaderGetUniformId(Shader* shader, const char* name); Uniform* lovrShaderGetUniform(Shader* shader, const char* name);
int lovrShaderGetUniformType(Shader* shader, const char* name, GLenum* type, int* count); void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count);
void lovrShaderSendInt(Shader* shader, int id, int value); void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count);
void lovrShaderSendFloat(Shader* shader, int id, float value); void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count);
void lovrShaderSendFloatVec2(Shader* shader, int id, int count, float* vector); void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count);
void lovrShaderSendFloatVec3(Shader* shader, int id, int count,float* vector);
void lovrShaderSendFloatVec4(Shader* shader, int id, int count, float* vector);
void lovrShaderSendFloatMat2(Shader* shader, int id, float* matrix);
void lovrShaderSendFloatMat3(Shader* shader, int id, float* matrix);
void lovrShaderSendFloatMat4(Shader* shader, int id, float* matrix);

93
src/graphics/shaders.c Normal file
View File

@ -0,0 +1,93 @@
#include "graphics/shaders.h"
const char* lovrShaderColorUniforms[] = {
"lovrDiffuseColor"
};
const char* lovrShaderTextureUniforms[] = {
"lovrDiffuseTexture",
"lovrEnvironmentTexture"
};
const char* lovrShaderVertexPrefix = ""
#ifdef EMSCRIPTEN
"#version 300 es \n"
"precision mediump float; \n"
#else
"#version 150 \n"
#endif
"in vec3 lovrPosition; \n"
"in vec3 lovrNormal; \n"
"in vec2 lovrTexCoord; \n"
"out vec2 texCoord; \n"
"uniform mat4 lovrModel; \n"
"uniform mat4 lovrView; \n"
"uniform mat4 lovrProjection; \n"
"uniform mat4 lovrTransform; \n"
"uniform mat3 lovrNormalMatrix; \n";
const char* lovrShaderFragmentPrefix = ""
#ifdef EMSCRIPTEN
"#version 300 es \n"
"precision mediump float; \n"
#else
"#version 150 \n"
"in vec4 gl_FragCoord; \n"
#endif
"in vec2 texCoord; \n"
"out vec4 lovrFragColor; \n"
"uniform vec4 lovrColor; \n"
"uniform vec4 lovrDiffuseColor; \n"
"uniform sampler2D lovrDiffuseTexture; \n"
"uniform samplerCube lovrEnvironmentTexture; \n";
const char* lovrShaderVertexSuffix = ""
"void main() { \n"
" texCoord = lovrTexCoord; \n"
" gl_Position = position(lovrProjection, lovrTransform, vec4(lovrPosition, 1.0)); \n"
"}";
const char* lovrShaderFragmentSuffix = ""
"void main() { \n"
" lovrFragColor = color(lovrColor, lovrDiffuseTexture, texCoord); \n"
"}";
const char* lovrDefaultVertexShader = ""
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n"
" return projection * transform * vertex; \n"
"}";
const char* lovrDefaultFragmentShader = ""
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n"
" return graphicsColor * lovrDiffuseColor * texture(image, uv); \n"
"}";
const char* lovrSkyboxVertexShader = ""
"out vec3 texturePosition; \n"
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n"
" texturePosition = vertex.xyz; \n"
" return projection * transform * vertex; \n"
"}";
const char* lovrSkyboxFragmentShader = ""
"in vec3 texturePosition; \n"
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n"
" return graphicsColor * texture(lovrEnvironmentTexture, texturePosition); \n"
"}";
const char* lovrFontFragmentShader = ""
"float median(float r, float g, float b) { \n"
" return max(min(r, g), min(max(r, g), b)); \n"
"} \n"
"vec4 color(vec4 graphicsColor, sampler2D image, vec2 uv) { \n"
" vec3 col = texture(image, uv).rgb; \n"
" float sdf = median(col.r, col.g, col.b); \n"
" float w = fwidth(sdf); \n"
" float alpha = smoothstep(.5 - w, .5 + w, sdf); \n"
" return vec4(graphicsColor.rgb, graphicsColor.a * alpha); \n"
"}";
const char* lovrNoopVertexShader = ""
"vec4 position(mat4 projection, mat4 transform, vec4 vertex) { \n"
" return vertex; \n"
"}";

14
src/graphics/shaders.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
extern const char* lovrShaderColorUniforms[];
extern const char* lovrShaderTextureUniforms[];
extern const char* lovrShaderVertexPrefix;
extern const char* lovrShaderVertexSuffix;
extern const char* lovrShaderFragmentPrefix;
extern const char* lovrShaderFragmentSuffix;
extern const char* lovrDefaultVertexShader;
extern const char* lovrDefaultFragmentShader;
extern const char* lovrSkyboxVertexShader;
extern const char* lovrSkyboxFragmentShader;
extern const char* lovrFontFragmentShader;
extern const char* lovrNoopVertexShader;

View File

@ -1,56 +0,0 @@
#include "graphics/skybox.h"
#include "lib/stb/stb_image.h"
#include <stdlib.h>
Skybox* lovrSkyboxCreate(Blob** blobs, SkyboxType type) {
Skybox* skybox = lovrAlloc(sizeof(Skybox), lovrSkyboxDestroy);
if (!skybox) return NULL;
skybox->type = type;
GLenum binding;
int count;
if (type == SKYBOX_CUBE) {
binding = GL_TEXTURE_CUBE_MAP;
count = 6;
} else {
binding = GL_TEXTURE_2D;
count = 1;
}
glGenTextures(1, &skybox->texture);
glBindTexture(binding, skybox->texture);
for (int i = 0; i < count; i++) {
int width, height, channels;
stbi_set_flip_vertically_on_load(0);
unsigned char* image = stbi_load_from_memory(blobs[i]->data, blobs[i]->size, &width, &height, &channels, 3);
lovrAssert(image, "Could not load skybox image %d", i);
if (type == SKYBOX_CUBE) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
}
free(image);
}
glTexParameteri(binding, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(binding, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(binding, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(binding, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (type == SKYBOX_CUBE) {
glTexParameteri(binding, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
return skybox;
}
void lovrSkyboxDestroy(const Ref* ref) {
Skybox* skybox = containerof(ref, Skybox);
glDeleteTextures(1, &skybox->texture);
free(skybox);
}

View File

@ -1,19 +0,0 @@
#include "filesystem/blob.h"
#include "lib/glad/glad.h"
#include "util.h"
#pragma once
typedef enum {
SKYBOX_CUBE,
SKYBOX_PANORAMA
} SkyboxType;
typedef struct {
Ref ref;
SkyboxType type;
GLuint texture;
} Skybox;
Skybox* lovrSkyboxCreate(Blob** blobs, SkyboxType type);
void lovrSkyboxDestroy(const Ref* ref);

View File

@ -6,9 +6,9 @@
#include <stdio.h> #include <stdio.h>
static void lovrTextureCreateStorage(Texture* texture) { static void lovrTextureCreateStorage(Texture* texture) {
TextureData* textureData = texture->textureData; TextureData* textureData = texture->slices[0];
if (textureData->format.compressed || !textureData->mipmaps.generated) { if (textureData->format.compressed || !textureData->mipmaps.generated || texture->type == TEXTURE_CUBE) {
return; return;
} }
@ -32,30 +32,50 @@ static void lovrTextureCreateStorage(Texture* texture) {
#endif #endif
} }
Texture* lovrTextureCreate(TextureData* textureData) { static void validateSlices(TextureType type, TextureData* slices[6], int sliceCount) {
if (type == TEXTURE_CUBE) {
lovrAssert(sliceCount == 6, "Cube textures must have 6 images");
int width = slices[0]->width;
int height = slices[0]->height;
lovrAssert(width == height, "Cube textures must be square");
for (int i = 1; i < sliceCount; i++) {
int hasSameDimensions = slices[i]->width == width && slices[i]->height == height;
lovrAssert(hasSameDimensions, "All textures in a cube texture must have the same dimensions");
}
} else if (type == TEXTURE_2D) {
lovrAssert(sliceCount == 1, "2D textures can only contain a single image");
} else {
lovrThrow("Unknown texture type");
}
}
Texture* lovrTextureCreate(TextureType type, TextureData* slices[6], int sliceCount) {
Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy); Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy);
if (!texture) return NULL; if (!texture) return NULL;
texture->type = type;
validateSlices(type, slices, sliceCount);
texture->sliceCount = sliceCount;
memcpy(texture->slices, slices, sliceCount * sizeof(TextureData*));
texture->framebuffer = 0; texture->framebuffer = 0;
texture->depthBuffer = 0; texture->depthBuffer = 0;
texture->textureData = textureData;
glGenTextures(1, &texture->id); glGenTextures(1, &texture->id);
lovrGraphicsBindTexture(texture); lovrGraphicsBindTexture(texture, type, 0);
lovrTextureCreateStorage(texture); lovrTextureCreateStorage(texture);
lovrTextureRefresh(texture); lovrTextureRefresh(texture);
lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter()); lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter());
WrapMode wrapMode = (type == TEXTURE_CUBE) ? WRAP_CLAMP : WRAP_REPEAT;
lovrTextureSetWrap(texture, WRAP_REPEAT, WRAP_REPEAT); lovrTextureSetWrap(texture, (TextureWrap) { .s = wrapMode, .t = wrapMode, .r = wrapMode });
return texture; return texture;
} }
Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProjection projection, int msaa) { Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProjection projection, int msaa) {
Texture* texture = lovrTextureCreate(textureData); Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1);
if (!texture) return NULL; if (!texture) return NULL;
int width = texture->textureData->width; int width = texture->width;
int height = texture->textureData->height; int height = texture->height;
texture->projection = projection; texture->projection = projection;
texture->msaa = msaa; texture->msaa = msaa;
@ -102,7 +122,9 @@ Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProje
void lovrTextureDestroy(const Ref* ref) { void lovrTextureDestroy(const Ref* ref) {
Texture* texture = containerof(ref, Texture); Texture* texture = containerof(ref, Texture);
lovrTextureDataDestroy(texture->textureData); for (int i = 0; i < texture->sliceCount; i++) {
lovrTextureDataDestroy(texture->slices[i]);
}
if (texture->framebuffer) { if (texture->framebuffer) {
glDeleteFramebuffers(1, &texture->framebuffer); glDeleteFramebuffers(1, &texture->framebuffer);
} }
@ -112,23 +134,19 @@ void lovrTextureDestroy(const Ref* ref) {
void lovrTextureBindFramebuffer(Texture* texture) { void lovrTextureBindFramebuffer(Texture* texture) {
lovrAssert(texture->framebuffer, "Texture cannot be used as a canvas"); lovrAssert(texture->framebuffer, "Texture cannot be used as a canvas");
int w = texture->textureData->width;
int h = texture->textureData->height;
lovrGraphicsBindFramebuffer(texture->framebuffer); lovrGraphicsBindFramebuffer(texture->framebuffer);
lovrGraphicsSetViewport(0, 0, w, h); lovrGraphicsSetViewport(0, 0, texture->width, texture->height);
if (texture->projection == PROJECTION_ORTHOGRAPHIC) { if (texture->projection == PROJECTION_ORTHOGRAPHIC) {
float projection[16]; float projection[16];
mat4_orthographic(projection, 0, w, 0, h, -1, 1); mat4_orthographic(projection, 0, texture->width, 0, texture->height, -1, 1);
lovrGraphicsSetProjection(projection); lovrGraphicsSetProjection(projection);
} else if (texture->projection == PROJECTION_PERSPECTIVE) { } else if (texture->projection == PROJECTION_PERSPECTIVE) {
mat4 projection = lovrGraphicsGetProjection(); mat4 projection = lovrGraphicsGetProjection();
float b = projection[5]; float b = projection[5];
float c = projection[10]; float c = projection[10];
float d = projection[14]; float d = projection[14];
float aspect = (float) w / h; float aspect = (float) texture->width / texture->height;
float k = (c - 1.f) / (c + 1.f); float k = (c - 1.f) / (c + 1.f);
float near = (d * (1.f - k)) / (2.f * k); float near = (d * (1.f - k)) / (2.f * k);
float far = k * near; float far = k * near;
@ -145,53 +163,52 @@ void lovrTextureResolveMSAA(Texture* texture) {
return; return;
} }
int w = texture->textureData->width; int width = texture->width;
int h = texture->textureData->height; int height = texture->height;
glBindFramebuffer(GL_READ_FRAMEBUFFER, texture->framebuffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, texture->framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, texture->resolveFramebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, texture->resolveFramebuffer);
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
} }
void lovrTextureRefresh(Texture* texture) { void lovrTextureRefresh(Texture* texture) {
TextureData* textureData = texture->textureData; lovrGraphicsBindTexture(texture, texture->type, 0);
GLenum glInternalFormat = textureData->format.glInternalFormat;
GLenum glFormat = textureData->format.glFormat;
lovrGraphicsBindTexture(texture);
if (textureData->format.compressed) { validateSlices(texture->type, texture->slices, texture->sliceCount);
Mipmap m; int i; texture->width = texture->slices[0]->width;
vec_foreach(&textureData->mipmaps.list, m, i) { texture->height = texture->slices[0]->height;
glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, m.width, m.height, 0, m.size, m.data);
} for (int i = 0; i < texture->sliceCount; i++) {
} else { TextureData* textureData = texture->slices[i];
int w = textureData->width; GLenum glInternalFormat = textureData->format.glInternalFormat;
int h = textureData->height; GLenum glFormat = textureData->format.glFormat;
glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, textureData->data); GLenum binding = (texture->type == TEXTURE_CUBE) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + i : GL_TEXTURE_2D;
if (textureData->mipmaps.generated) {
glGenerateMipmap(GL_TEXTURE_2D); if (textureData->format.compressed) {
Mipmap m; int i;
vec_foreach(&textureData->mipmaps.list, m, i) {
glCompressedTexImage2D(binding, i, glInternalFormat, m.width, m.height, 0, m.size, m.data);
}
} else {
int w = textureData->width;
int h = textureData->height;
glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, textureData->data);
if (textureData->mipmaps.generated) {
glGenerateMipmap(GL_TEXTURE_2D); // TODO
}
} }
} }
} }
int lovrTextureGetHeight(Texture* texture) {
return texture->textureData->height;
}
int lovrTextureGetWidth(Texture* texture) {
return texture->textureData->width;
}
TextureFilter lovrTextureGetFilter(Texture* texture) { TextureFilter lovrTextureGetFilter(Texture* texture) {
return texture->filter; return texture->filter;
} }
void lovrTextureSetFilter(Texture* texture, TextureFilter filter) { void lovrTextureSetFilter(Texture* texture, TextureFilter filter) {
int hasMipmaps = texture->textureData->format.compressed || texture->textureData->mipmaps.generated; int hasMipmaps = texture->slices[0]->format.compressed || texture->slices[0]->mipmaps.generated;
float anisotropy = filter.mode == FILTER_ANISOTROPIC ? MAX(filter.anisotropy, 1.) : 1.; float anisotropy = filter.mode == FILTER_ANISOTROPIC ? MAX(filter.anisotropy, 1.) : 1.;
lovrGraphicsBindTexture(texture); lovrGraphicsBindTexture(texture, texture->type, 0);
texture->filter = filter; texture->filter = filter;
switch (filter.mode) { switch (filter.mode) {
@ -225,15 +242,16 @@ void lovrTextureSetFilter(Texture* texture, TextureFilter filter) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
} }
void lovrTextureGetWrap(Texture* texture, WrapMode* horizontal, WrapMode* vertical) { TextureWrap lovrTextureGetWrap(Texture* texture) {
*horizontal = texture->wrapHorizontal; return texture->wrap;
*vertical = texture->wrapVertical;
} }
void lovrTextureSetWrap(Texture* texture, WrapMode horizontal, WrapMode vertical) { void lovrTextureSetWrap(Texture* texture, TextureWrap wrap) {
texture->wrapHorizontal = horizontal; texture->wrap = wrap;
texture->wrapVertical = vertical; lovrGraphicsBindTexture(texture, texture->type, 0);
lovrGraphicsBindTexture(texture); glTexParameteri(texture->type, GL_TEXTURE_WRAP_S, wrap.s);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, horizontal); glTexParameteri(texture->type, GL_TEXTURE_WRAP_T, wrap.t);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vertical); if (texture->type == TEXTURE_CUBE) {
glTexParameteri(texture->type, GL_TEXTURE_WRAP_R, wrap.r);
}
} }

View File

@ -4,6 +4,11 @@
#pragma once #pragma once
typedef enum {
TEXTURE_2D = GL_TEXTURE_2D,
TEXTURE_CUBE = GL_TEXTURE_CUBE_MAP
} TextureType;
typedef enum { typedef enum {
FILTER_NEAREST, FILTER_NEAREST,
FILTER_BILINEAR, FILTER_BILINEAR,
@ -22,6 +27,12 @@ typedef enum {
WRAP_MIRRORED_REPEAT = GL_MIRRORED_REPEAT WRAP_MIRRORED_REPEAT = GL_MIRRORED_REPEAT
} WrapMode; } WrapMode;
typedef struct {
WrapMode s;
WrapMode t;
WrapMode r;
} TextureWrap;
typedef enum { typedef enum {
PROJECTION_ORTHOGRAPHIC, PROJECTION_ORTHOGRAPHIC,
PROJECTION_PERSPECTIVE PROJECTION_PERSPECTIVE
@ -29,7 +40,11 @@ typedef enum {
typedef struct { typedef struct {
Ref ref; Ref ref;
TextureData* textureData; TextureType type;
TextureData* slices[6];
int sliceCount;
int width;
int height;
GLuint id; GLuint id;
GLuint msaaId; GLuint msaaId;
GLuint framebuffer; GLuint framebuffer;
@ -37,22 +52,19 @@ typedef struct {
GLuint depthBuffer; GLuint depthBuffer;
TextureProjection projection; TextureProjection projection;
TextureFilter filter; TextureFilter filter;
WrapMode wrapHorizontal; TextureWrap wrap;
WrapMode wrapVertical;
int msaa; int msaa;
} Texture; } Texture;
GLenum lovrTextureGetGLFormat(TextureFormat format); GLenum lovrTextureGetGLFormat(TextureFormat format);
Texture* lovrTextureCreate(TextureData* textureData); Texture* lovrTextureCreate(TextureType type, TextureData* data[6], int count);
Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProjection projection, int msaa); Texture* lovrTextureCreateWithFramebuffer(TextureData* textureData, TextureProjection projection, int msaa);
void lovrTextureDestroy(const Ref* ref); void lovrTextureDestroy(const Ref* ref);
void lovrTextureBindFramebuffer(Texture* texture); void lovrTextureBindFramebuffer(Texture* texture);
void lovrTextureResolveMSAA(Texture* texture); void lovrTextureResolveMSAA(Texture* texture);
void lovrTextureRefresh(Texture* texture); void lovrTextureRefresh(Texture* texture);
int lovrTextureGetHeight(Texture* texture);
int lovrTextureGetWidth(Texture* texture);
TextureFilter lovrTextureGetFilter(Texture* texture); TextureFilter lovrTextureGetFilter(Texture* texture);
void lovrTextureSetFilter(Texture* texture, TextureFilter filter); void lovrTextureSetFilter(Texture* texture, TextureFilter filter);
void lovrTextureGetWrap(Texture* texture, WrapMode* horizontal, WrapMode* vertical); TextureWrap lovrTextureGetWrap(Texture* texture);
void lovrTextureSetWrap(Texture* texture, WrapMode horizontal, WrapMode vertical); void lovrTextureSetWrap(Texture* texture, TextureWrap wrap);

View File

@ -383,10 +383,6 @@ static ModelData* fakeControllerNewModelData(Controller* controller) {
return NULL; return NULL;
} }
static TextureData* fakeControllerNewTextureData(Controller* controller) {
return NULL;
}
static void fakeRenderTo(headsetRenderCallback callback, void* userdata) { static void fakeRenderTo(headsetRenderCallback callback, void* userdata) {
// float head[16], transform[16], projection[16]; // float head[16], transform[16], projection[16];
@ -491,7 +487,6 @@ HeadsetImpl lovrHeadsetFakeDriver = {
fakeControllerIsTouched, fakeControllerIsTouched,
fakeControllerVibrate, fakeControllerVibrate,
fakeControllerNewModelData, fakeControllerNewModelData,
fakeControllerNewTextureData,
fakeRenderTo, fakeRenderTo,
fakeUpdate, fakeUpdate,
}; };

View File

@ -229,13 +229,6 @@ ModelData* lovrHeadsetControllerNewModelData(Controller* controller)
} }
} }
TextureData* lovrHeadsetControllerNewTextureData(Controller* controller) {
if( headset && controller) {
return headset->controllerNewTextureData(controller);
} else {
return NULL;
}
}
void lovrHeadsetRenderTo(headsetRenderCallback callback, void* userdata) { void lovrHeadsetRenderTo(headsetRenderCallback callback, void* userdata) {
if (headset) { if (headset) {

View File

@ -90,7 +90,6 @@ typedef struct {
int (*controllerIsTouched)(Controller* controller, ControllerButton button); int (*controllerIsTouched)(Controller* controller, ControllerButton button);
void (*controllerVibrate)(Controller* controller, float duration, float power); void (*controllerVibrate)(Controller* controller, float duration, float power);
ModelData* (*controllerNewModelData)(Controller* controller); ModelData* (*controllerNewModelData)(Controller* controller);
TextureData* (*controllerNewTextureData)(Controller* controller);
void (*renderTo)(headsetRenderCallback callback, void* userdata); void (*renderTo)(headsetRenderCallback callback, void* userdata);
void (*update)(float dt); void (*update)(float dt);
} HeadsetImpl; } HeadsetImpl;
@ -130,7 +129,6 @@ int lovrHeadsetControllerIsDown(Controller* controller, ControllerButton button)
int lovrHeadsetControllerIsTouched(Controller* controller, ControllerButton button); int lovrHeadsetControllerIsTouched(Controller* controller, ControllerButton button);
void lovrHeadsetControllerVibrate(Controller* controller, float duration, float power); void lovrHeadsetControllerVibrate(Controller* controller, float duration, float power);
ModelData* lovrHeadsetControllerNewModelData(Controller* controller); ModelData* lovrHeadsetControllerNewModelData(Controller* controller);
TextureData* lovrHeadsetControllerNewTextureData(Controller* controller);
void lovrHeadsetRenderTo(headsetRenderCallback callback, void* userdata); void lovrHeadsetRenderTo(headsetRenderCallback callback, void* userdata);
void lovrHeadsetUpdate(float dt); void lovrHeadsetUpdate(float dt);

View File

@ -656,77 +656,64 @@ static ModelData* openvrControllerNewModelData(Controller* controller) {
} }
} }
RenderModel_t* vrModel = state.deviceModels[id]; // Load texture
ModelData* modelData = malloc(sizeof(ModelData));
if (!modelData) return NULL;
ModelMesh* mesh = malloc(sizeof(ModelMesh));
vec_init(&modelData->meshes);
vec_push(&modelData->meshes, mesh);
vec_init(&mesh->faces);
for (uint32_t i = 0; i < vrModel->unTriangleCount; i++) {
ModelFace face;
face.indices[0] = vrModel->rIndexData[3 * i + 0];
face.indices[1] = vrModel->rIndexData[3 * i + 1];
face.indices[2] = vrModel->rIndexData[3 * i + 2];
vec_push(&mesh->faces, face);
}
vec_init(&mesh->vertices);
vec_init(&mesh->normals);
vec_init(&mesh->texCoords);
for (size_t i = 0; i < vrModel->unVertexCount; i++) {
float* position = vrModel->rVertexData[i].vPosition.v;
float* normal = vrModel->rVertexData[i].vNormal.v;
float* texCoords = vrModel->rVertexData[i].rfTextureCoord;
ModelVertex v;
v.x = position[0];
v.y = position[1];
v.z = position[2];
vec_push(&mesh->vertices, v);
v.x = normal[0];
v.y = normal[1];
v.z = normal[2];
vec_push(&mesh->normals, v);
v.x = texCoords[0];
v.y = texCoords[1];
v.z = 0.f;
vec_push(&mesh->texCoords, v);
}
ModelNode* root = malloc(sizeof(ModelNode));
vec_init(&root->meshes);
vec_push(&root->meshes, 0);
vec_init(&root->children);
mat4_identity(root->transform);
modelData->root = root;
modelData->hasNormals = 1;
modelData->hasTexCoords = 1;
return modelData;
}
static TextureData* openvrControllerNewTextureData(Controller* controller) {
if (!state.isInitialized || !controller) return NULL;
int id = controller->id;
if (!state.deviceModels[id]) {
openvrControllerNewModelData(controller);
}
if (!state.deviceTextures[id]) { if (!state.deviceTextures[id]) {
while (state.renderModels->LoadTexture_Async(state.deviceModels[id]->diffuseTextureId, &state.deviceTextures[id]) == EVRRenderModelError_VRRenderModelError_Loading) { while (state.renderModels->LoadTexture_Async(state.deviceModels[id]->diffuseTextureId, &state.deviceTextures[id]) == EVRRenderModelError_VRRenderModelError_Loading) {
lovrSleep(.001); lovrSleep(.001);
} }
} }
RenderModel_t* vrModel = state.deviceModels[id];
ModelData* modelData = malloc(sizeof(ModelData));
if (!modelData) return NULL;
modelData->indexCount = vrModel->unTriangleCount;
modelData->indices = malloc(modelData->indexCount * sizeof(unsigned int));
memcpy(modelData->indices, vrModel->rIndexData, modelData->indexCount * sizeof(unsigned int));
modelData->vertexCount = vrModel->unVertexCount;
modelData->vertexSize = 8;
modelData->vertices = malloc(modelData->vertexCount * modelData->vertexSize * sizeof(float));
int vertex = 0;
for (size_t i = 0; i < vrModel->unVertexCount; i++) {
float* position = vrModel->rVertexData[i].vPosition.v;
float* normal = vrModel->rVertexData[i].vNormal.v;
float* texCoords = vrModel->rVertexData[i].rfTextureCoord;
modelData->vertices[vertex++] = position[0];
modelData->vertices[vertex++] = position[1];
modelData->vertices[vertex++] = position[2];
modelData->vertices[vertex++] = normal[0];
modelData->vertices[vertex++] = normal[1];
modelData->vertices[vertex++] = normal[2];
modelData->vertices[vertex++] = texCoords[0];
modelData->vertices[vertex++] = texCoords[1];
}
modelData->nodeCount = 1;
modelData->primitiveCount = 1;
modelData->materialCount = 1;
modelData->nodes = malloc(1 * sizeof(ModelNode));
modelData->primitives = malloc(1 * sizeof(ModelPrimitive));
modelData->materials = malloc(1 * sizeof(MaterialData));
// Geometry
ModelNode* root = &modelData->nodes[0];
root->parent = -1;
mat4_identity(root->transform);
vec_init(&root->children);
vec_init(&root->primitives);
vec_push(&root->primitives, 0);
modelData->primitives[0].material = 0;
modelData->primitives[0].drawStart = 0;
modelData->primitives[0].drawCount = modelData->vertexCount;
// Material
RenderModel_TextureMap_t* vrTexture = state.deviceTextures[id]; RenderModel_TextureMap_t* vrTexture = state.deviceTextures[id];
TextureData* textureData = malloc(sizeof(TextureData)); TextureData* textureData = malloc(sizeof(TextureData));
@ -743,7 +730,14 @@ static TextureData* openvrControllerNewTextureData(Controller* controller) {
textureData->data = memcpy(malloc(size), vrTexture->rubTextureMapData, size);; textureData->data = memcpy(malloc(size), vrTexture->rubTextureMapData, size);;
textureData->mipmaps.generated = 1; textureData->mipmaps.generated = 1;
textureData->blob = NULL; textureData->blob = NULL;
return textureData;
modelData->materials[0] = *lovrMaterialDataCreateEmpty();
modelData->materials[0].textures[TEXTURE_DIFFUSE] = textureData;
modelData->hasNormals = 1;
modelData->hasUVs = 1;
return modelData;
} }
static void openvrRenderTo(headsetRenderCallback callback, void* userdata) { static void openvrRenderTo(headsetRenderCallback callback, void* userdata) {
@ -758,7 +752,7 @@ static void openvrRenderTo(headsetRenderCallback callback, void* userdata) {
float head[16], transform[16], projection[16]; float head[16], transform[16], projection[16];
float (*matrix)[4]; float (*matrix)[4];
lovrGraphicsPushCanvas(); lovrGraphicsPushView();
state.isRendering = 1; state.isRendering = 1;
state.compositor->WaitGetPoses(state.renderPoses, 16, NULL, 0); state.compositor->WaitGetPoses(state.renderPoses, 16, NULL, 0);
@ -789,7 +783,8 @@ static void openvrRenderTo(headsetRenderCallback callback, void* userdata) {
lovrTextureResolveMSAA(state.texture); lovrTextureResolveMSAA(state.texture);
// OpenVR changes the OpenGL texture binding, so we reset it after rendering // OpenVR changes the OpenGL texture binding, so we reset it after rendering
Texture* oldTexture = lovrGraphicsGetTexture(); glActiveTexture(GL_TEXTURE0);
Texture* oldTexture = lovrGraphicsGetTexture(0);
// Submit // Submit
uintptr_t texture = (uintptr_t) state.texture->id; uintptr_t texture = (uintptr_t) state.texture->id;
@ -803,7 +798,7 @@ static void openvrRenderTo(headsetRenderCallback callback, void* userdata) {
} }
state.isRendering = 0; state.isRendering = 0;
lovrGraphicsPopCanvas(); lovrGraphicsPopView();
if (state.isMirrored) { if (state.isMirrored) {
Color oldColor = lovrGraphicsGetColor(); Color oldColor = lovrGraphicsGetColor();
@ -861,7 +856,6 @@ HeadsetImpl lovrHeadsetOpenVRDriver = {
openvrControllerIsTouched, openvrControllerIsTouched,
openvrControllerVibrate, openvrControllerVibrate,
openvrControllerNewModelData, openvrControllerNewModelData,
openvrControllerNewTextureData,
openvrRenderTo, openvrRenderTo,
openvrUpdate, openvrUpdate,
}; };

View File

@ -249,10 +249,6 @@ static ModelData* webvrControllerNewModelData(Controller* controller) {
return NULL; return NULL;
} }
static TextureData* webvrControllerNewTextureData(Controller* controller) {
return NULL;
}
static void webvrRenderTo(headsetRenderCallback callback, void* userdata) { static void webvrRenderTo(headsetRenderCallback callback, void* userdata) {
state.renderCallback = callback; state.renderCallback = callback;
emscripten_vr_set_render_callback(onRequestAnimationFrame, userdata); emscripten_vr_set_render_callback(onRequestAnimationFrame, userdata);
@ -293,7 +289,6 @@ HeadsetImpl lovrHeadsetWebVRDriver = {
webvrControllerIsTouched, webvrControllerIsTouched,
webvrControllerVibrate, webvrControllerVibrate,
webvrControllerNewModelData, webvrControllerNewModelData,
webvrControllerNewTextureData,
webvrRenderTo, webvrRenderTo,
webvrUpdate, webvrUpdate,
}; };

19
src/loaders/material.c Normal file
View File

@ -0,0 +1,19 @@
#include "loaders/material.h"
MaterialData* lovrMaterialDataCreateEmpty() {
MaterialData* materialData = malloc(sizeof(MaterialData));
for (int i = 0; i < MAX_MATERIAL_COLORS; i++) {
materialData->colors[i] = (Color) { 0xff, 0xff, 0xff, 0xff };
}
for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) {
materialData->textures[i] = NULL;
}
return materialData;
}
void lovrMaterialDataDestroy(MaterialData* materialData) {
free(materialData);
}

22
src/loaders/material.h Normal file
View File

@ -0,0 +1,22 @@
#include "loaders/texture.h"
#pragma once
typedef enum {
COLOR_DIFFUSE,
MAX_MATERIAL_COLORS
} MaterialColor;
typedef enum {
TEXTURE_DIFFUSE,
TEXTURE_ENVIRONMENT_MAP,
MAX_MATERIAL_TEXTURES
} MaterialTexture;
typedef struct {
Color colors[MAX_MATERIAL_COLORS];
TextureData* textures[MAX_MATERIAL_TEXTURES];
} MaterialData;
MaterialData* lovrMaterialDataCreateEmpty();
void lovrMaterialDataDestroy(MaterialData* materialData);

View File

@ -1,30 +1,171 @@
#include "loaders/model.h" #include "loaders/model.h"
#include "filesystem/filesystem.h"
#include "filesystem/file.h"
#include "math/math.h"
#include "math/mat4.h" #include "math/mat4.h"
#include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <assimp/cfileio.h>
#include <assimp/cimport.h> #include <assimp/cimport.h>
#include <assimp/config.h>
#include <assimp/scene.h> #include <assimp/scene.h>
#include <assimp/mesh.h> #include <assimp/mesh.h>
#include <assimp/matrix4x4.h> #include <assimp/matrix4x4.h>
#include <assimp/vector3.h> #include <assimp/vector3.h>
#include <assimp/postprocess.h> #include <assimp/postprocess.h>
static void assimpNodeTraversal(ModelNode* node, struct aiNode* assimpNode) { static void normalizePath(const char* path, char* dst, size_t size) {
if (path[0] == '/') {
strncpy(dst, path, size);
return;
}
memset(dst, 0, size);
while (*path != '\0') {
if (*path == '/') {
path++;
continue;
}
if (*path == '.') {
if (path[1] == '\0' || path[1] == '/') {
path++;
continue;
}
if (path[1] == '.' && (path[2] == '\0' || path[2] == '/')) {
path += 2;
while ((--dst)[-1] != '/');
continue;
}
}
while (*path != '\0' && *path != '/') {
*dst++ = *path++;
}
*dst++ = '/';
}
*--dst = '\0';
}
// Blob IO (to avoid reading data twice)
static unsigned long assimpBlobRead(struct aiFile* assimpFile, char* buffer, size_t size, size_t count) {
Blob* blob = (Blob*) assimpFile->UserData;
char* data = blob->data;
size_t bytes = MIN(count * size * sizeof(char), blob->size - blob->seek);
memcpy(buffer, data + blob->seek, bytes);
return bytes;
}
static size_t assimpBlobGetSize(struct aiFile* assimpFile) {
Blob* blob = (Blob*) assimpFile->UserData;
return blob->size;
}
static aiReturn assimpBlobSeek(struct aiFile* assimpFile, unsigned long position, enum aiOrigin origin) {
Blob* blob = (Blob*) assimpFile->UserData;
switch (origin) {
case aiOrigin_SET: blob->seek = position; break;
case aiOrigin_CUR: blob->seek += position; break;
case aiOrigin_END: blob->seek = blob->size - position; break;
default: return aiReturn_FAILURE;
}
return blob->seek < blob->size ? aiReturn_SUCCESS : aiReturn_FAILURE;
}
static unsigned long assimpBlobTell(struct aiFile* assimpFile) {
Blob* blob = (Blob*) assimpFile->UserData;
return blob->seek;
}
// File IO (for reading referenced materials/textures)
static unsigned long assimpFileRead(struct aiFile* assimpFile, char* buffer, size_t size, size_t count) {
File* file = (File*) assimpFile->UserData;
unsigned long bytes = lovrFileRead(file, buffer, size, count);
return bytes;
}
static size_t assimpFileGetSize(struct aiFile* assimpFile) {
File* file = (File*) assimpFile->UserData;
return lovrFileGetSize(file);
}
static aiReturn assimpFileSeek(struct aiFile* assimpFile, unsigned long position, enum aiOrigin origin) {
File* file = (File*) assimpFile->UserData;
return lovrFileSeek(file, position) ? aiReturn_FAILURE : aiReturn_SUCCESS;
}
static unsigned long assimpFileTell(struct aiFile* assimpFile) {
File* file = (File*) assimpFile->UserData;
return lovrFileTell(file);
}
static struct aiFile* assimpFileOpen(struct aiFileIO* io, const char* path, const char* mode) {
struct aiFile* assimpFile = malloc(sizeof(struct aiFile));
Blob* blob = (Blob*) io->UserData;
if (!strcmp(blob->name, path)) {
assimpFile->ReadProc = assimpBlobRead;
assimpFile->FileSizeProc = assimpBlobGetSize;
assimpFile->SeekProc = assimpBlobSeek;
assimpFile->TellProc = assimpBlobTell;
assimpFile->UserData = (void*) blob;
} else {
char normalizedPath[LOVR_PATH_MAX];
normalizePath(path, normalizedPath, LOVR_PATH_MAX);
File* file = lovrFileCreate(path);
if (lovrFileOpen(file, OPEN_READ)) {
return NULL;
}
assimpFile->ReadProc = assimpFileRead;
assimpFile->FileSizeProc = assimpFileGetSize;
assimpFile->SeekProc = assimpFileSeek;
assimpFile->TellProc = assimpFileTell;
assimpFile->UserData = (void*) file;
}
return assimpFile;
}
static void assimpFileClose(struct aiFileIO* io, struct aiFile* assimpFile) {
void* blob = io->UserData;
if (assimpFile->UserData != blob) {
File* file = (File*) assimpFile->UserData;
lovrFileClose(file);
lovrRelease(&file->ref);
}
free(assimpFile);
}
static void assimpSumChildren(struct aiNode* assimpNode, int* totalChildren) {
(*totalChildren)++;
for (unsigned int i = 0; i < assimpNode->mNumChildren; i++) {
assimpSumChildren(assimpNode->mChildren[i], totalChildren);
}
}
static void assimpNodeTraversal(ModelData* modelData, struct aiNode* assimpNode, int* nodeId) {
int currentIndex = *nodeId;
ModelNode* node = &modelData->nodes[currentIndex];
// Transform // Transform
struct aiMatrix4x4 m = assimpNode->mTransformation; struct aiMatrix4x4 m = assimpNode->mTransformation;
aiTransposeMatrix4(&m); aiTransposeMatrix4(&m);
mat4_set(node->transform, (float*) &m); mat4_set(node->transform, (float*) &m);
// Meshes // Primitives
vec_init(&node->meshes); vec_init(&node->primitives);
vec_pusharr(&node->meshes, assimpNode->mMeshes, assimpNode->mNumMeshes); vec_pusharr(&node->primitives, assimpNode->mMeshes, assimpNode->mNumMeshes);
// Children // Children
vec_init(&node->children); vec_init(&node->children);
for (unsigned int n = 0; n < assimpNode->mNumChildren; n++) { for (unsigned int n = 0; n < assimpNode->mNumChildren; n++) {
ModelNode* child = malloc(sizeof(ModelNode)); (*nodeId)++;
assimpNodeTraversal(child, assimpNode->mChildren[n]); vec_push(&node->children, *nodeId);
vec_push(&node->children, child); ModelNode* child = &modelData->nodes[*nodeId];
child->parent = currentIndex;
assimpNodeTraversal(modelData, assimpNode->mChildren[n], nodeId);
} }
} }
@ -32,104 +173,160 @@ ModelData* lovrModelDataCreate(Blob* blob) {
ModelData* modelData = malloc(sizeof(ModelData)); ModelData* modelData = malloc(sizeof(ModelData));
if (!modelData) return NULL; if (!modelData) return NULL;
modelData->hasNormals = 0; struct aiFileIO assimpIO;
modelData->hasTexCoords = 0; assimpIO.OpenProc = assimpFileOpen;
assimpIO.CloseProc = assimpFileClose;
assimpIO.UserData = (void*) blob;
struct aiPropertyStore* propertyStore = aiCreatePropertyStore();
aiSetImportPropertyInteger(propertyStore, AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_OptimizeGraph | aiProcess_FlipUVs; unsigned int flags = aiProcessPreset_TargetRealtime_MaxQuality | aiProcess_OptimizeGraph | aiProcess_FlipUVs;
const struct aiScene* scene = aiImportFileFromMemory(blob->data, blob->size, flags, NULL); const struct aiScene* scene = aiImportFileExWithProperties(blob->name, flags, &assimpIO, propertyStore);
aiReleasePropertyStore(propertyStore);
if (!scene) {
lovrThrow("Unable to load model from '%s': %s\n", blob->name, aiGetErrorString());
}
modelData->nodeCount = 0;
modelData->vertexCount = 0;
modelData->indexCount = 0;
modelData->hasNormals = 0;
modelData->hasUVs = 0;
// Meshes
vec_init(&modelData->meshes);
for (unsigned int m = 0; m < scene->mNumMeshes; m++) { for (unsigned int m = 0; m < scene->mNumMeshes; m++) {
struct aiMesh* assimpMesh = scene->mMeshes[m]; struct aiMesh* assimpMesh = scene->mMeshes[m];
ModelMesh* mesh = malloc(sizeof(ModelMesh)); modelData->vertexCount += assimpMesh->mNumVertices;
vec_push(&modelData->meshes, mesh); modelData->indexCount += assimpMesh->mNumFaces * 3;
modelData->hasNormals |= assimpMesh->mNormals != NULL;
modelData->hasUVs |= assimpMesh->mTextureCoords[0] != NULL;
}
// Faces // Allocate
vec_init(&mesh->faces); int indexSize = modelData->vertexCount > USHRT_MAX ? sizeof(uint32_t) : sizeof(uint16_t);
modelData->primitiveCount = scene->mNumMeshes;
modelData->primitives = malloc(modelData->primitiveCount * sizeof(ModelPrimitive));
modelData->vertexSize = 3 + (modelData->hasNormals ? 3 : 0) + (modelData->hasUVs ? 2 : 0);
modelData->vertices = malloc(modelData->vertexSize * modelData->vertexCount * sizeof(float));
modelData->indices = malloc(modelData->indexCount * indexSize);
// Load
int vertex = 0;
int index = 0;
for (unsigned int m = 0; m < scene->mNumMeshes; m++) {
struct aiMesh* assimpMesh = scene->mMeshes[m];
modelData->primitives[m].material = assimpMesh->mMaterialIndex;
modelData->primitives[m].drawStart = index;
modelData->primitives[m].drawCount = 0;
// Indices
for (unsigned int f = 0; f < assimpMesh->mNumFaces; f++) { for (unsigned int f = 0; f < assimpMesh->mNumFaces; f++) {
struct aiFace assimpFace = assimpMesh->mFaces[f]; struct aiFace assimpFace = assimpMesh->mFaces[f];
lovrAssert(assimpFace.mNumIndices == 3, "Only triangular faces are supported");
// Skip lines and points, polygons are triangulated modelData->primitives[m].drawCount += assimpFace.mNumIndices;
if (assimpFace.mNumIndices != 3) {
continue;
}
ModelFace face; if (indexSize == sizeof(uint16_t)) {
for (unsigned int i = 0; i < assimpFace.mNumIndices; i++) { uint16_t* indices = modelData->indices;
face.indices[i] = assimpFace.mIndices[i]; for (unsigned int i = 0; i < assimpFace.mNumIndices; i++) {
indices[index++] = (vertex / modelData->vertexSize) + assimpFace.mIndices[i];
}
} else if (indexSize == sizeof(uint32_t)) {
uint32_t* indices = modelData->indices;
for (unsigned int i = 0; i < assimpFace.mNumIndices; i++) {
indices[index++] = (vertex / modelData->vertexSize) + assimpFace.mIndices[i];
}
} }
vec_push(&mesh->faces, face);
} }
// Vertices // Vertices
vec_init(&mesh->vertices);
for (unsigned int v = 0; v < assimpMesh->mNumVertices; v++) { for (unsigned int v = 0; v < assimpMesh->mNumVertices; v++) {
ModelVertex vertex; modelData->vertices[vertex++] = assimpMesh->mVertices[v].x;
vertex.x = assimpMesh->mVertices[v].x; modelData->vertices[vertex++] = assimpMesh->mVertices[v].y;
vertex.y = assimpMesh->mVertices[v].y; modelData->vertices[vertex++] = assimpMesh->mVertices[v].z;
vertex.z = assimpMesh->mVertices[v].z;
vec_push(&mesh->vertices, vertex);
}
// Normals if (modelData->hasNormals) {
lovrAssert(assimpMesh->mNormals, "Model must have normals"); if (assimpMesh->mNormals) {
modelData->vertices[vertex++] = assimpMesh->mNormals[v].x;
modelData->vertices[vertex++] = assimpMesh->mNormals[v].y;
modelData->vertices[vertex++] = assimpMesh->mNormals[v].z;
} else {
modelData->vertices[vertex++] = 0;
modelData->vertices[vertex++] = 0;
modelData->vertices[vertex++] = 0;
}
}
modelData->hasNormals = 1; if (modelData->hasUVs) {
vec_init(&mesh->normals); if (assimpMesh->mTextureCoords[0]) {
for (unsigned int n = 0; n < assimpMesh->mNumVertices; n++) { modelData->vertices[vertex++] = assimpMesh->mTextureCoords[0][v].x;
ModelVertex normal; modelData->vertices[vertex++] = assimpMesh->mTextureCoords[0][v].y;
normal.x = assimpMesh->mNormals[n].x; } else {
normal.y = assimpMesh->mNormals[n].y; modelData->vertices[vertex++] = 0;
normal.z = assimpMesh->mNormals[n].z; modelData->vertices[vertex++] = 0;
vec_push(&mesh->normals, normal); }
}
modelData->hasTexCoords = modelData->hasTexCoords || assimpMesh->mTextureCoords[0] != NULL;
if (assimpMesh->mTextureCoords[0]) {
vec_init(&mesh->texCoords);
for (unsigned int i = 0; i < assimpMesh->mNumVertices; i++) {
ModelVertex texCoord;
texCoord.x = assimpMesh->mTextureCoords[0][i].x;
texCoord.y = assimpMesh->mTextureCoords[0][i].y;
vec_push(&mesh->texCoords, texCoord);
} }
} }
} }
// Materials
modelData->materialCount = scene->mNumMaterials;
modelData->materials = malloc(modelData->materialCount * sizeof(MaterialData));
for (unsigned int m = 0; m < scene->mNumMaterials; m++) {
MaterialData* materialData = lovrMaterialDataCreateEmpty();
struct aiMaterial* material = scene->mMaterials[m];
struct aiColor4D color;
struct aiString str;
if (aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &color) == aiReturn_SUCCESS) {
materialData->colors[COLOR_DIFFUSE].r = color.r * 255;
materialData->colors[COLOR_DIFFUSE].g = color.g * 255;
materialData->colors[COLOR_DIFFUSE].b = color.b * 255;
materialData->colors[COLOR_DIFFUSE].a = color.a * 255;
}
if (aiGetMaterialTexture(material, aiTextureType_DIFFUSE, 0, &str, NULL, NULL, NULL, NULL, NULL, NULL) == aiReturn_SUCCESS) {
char* path = str.data;
char normalizedPath[LOVR_PATH_MAX];
normalizePath(path, normalizedPath, LOVR_PATH_MAX);
size_t size;
void* data = lovrFilesystemRead(path, &size);
if (data) {
Blob* blob = lovrBlobCreate(data, size, path);
materialData->textures[TEXTURE_DIFFUSE] = lovrTextureDataFromBlob(blob);
}
}
modelData->materials[m] = *materialData;
}
// Nodes // Nodes
modelData->root = malloc(sizeof(ModelNode)); modelData->nodeCount = 0;
assimpNodeTraversal(modelData->root, scene->mRootNode); assimpSumChildren(scene->mRootNode, &modelData->nodeCount);
modelData->nodes = malloc(modelData->nodeCount * sizeof(ModelNode));
modelData->nodes[0].parent = -1;
int nodeIndex = 0;
assimpNodeTraversal(modelData, scene->mRootNode, &nodeIndex);
aiReleaseImport(scene); aiReleaseImport(scene);
return modelData; return modelData;
} }
void lovrModelDataDestroy(ModelData* modelData) { void lovrModelDataDestroy(ModelData* modelData) {
for (int i = 0; i < modelData->meshes.length; i++) { for (int i = 0; i < modelData->nodeCount; i++) {
ModelMesh* mesh = modelData->meshes.data[i]; vec_deinit(&modelData->nodes[i].children);
vec_deinit(&mesh->faces); vec_deinit(&modelData->nodes[i].primitives);
vec_deinit(&mesh->vertices);
vec_deinit(&mesh->normals);
if (modelData->hasTexCoords) {
vec_deinit(&mesh->texCoords);
}
free(mesh);
} }
vec_void_t nodes; for (int i = 0; i < modelData->materialCount; i++) {
vec_init(&nodes); lovrMaterialDataDestroy(&modelData->materials[i]);
vec_push(&nodes, modelData->root);
while (nodes.length > 0) {
ModelNode* node = vec_first(&nodes);
vec_extend(&nodes, &node->children);
vec_deinit(&node->meshes);
vec_deinit(&node->children);
vec_splice(&nodes, 0, 1);
free(node);
} }
vec_deinit(&modelData->meshes); free(modelData->nodes);
vec_deinit(&nodes); free(modelData->primitives);
free(modelData->materials);
free(modelData->vertices);
free(modelData->indices);
free(modelData); free(modelData);
} }

View File

@ -1,43 +1,37 @@
#include "filesystem/blob.h" #include "filesystem/blob.h"
#include "loaders/material.h"
#include "util.h" #include "util.h"
#include "lib/vec/vec.h" #include "lib/vec/vec.h"
#pragma once #pragma once
typedef struct { typedef struct {
float x; int material;
float y; int drawStart;
float z; int drawCount;
} ModelVertex; } ModelPrimitive;
typedef vec_t(ModelVertex) vec_model_vertex_t; typedef struct ModelNode {
typedef struct {
unsigned int indices[3];
} ModelFace;
typedef vec_t(ModelFace) vec_model_face_t;
typedef struct {
vec_model_face_t faces;
vec_model_vertex_t vertices;
vec_model_vertex_t normals;
vec_model_vertex_t texCoords;
} ModelMesh;
typedef vec_t(ModelMesh*) vec_model_mesh_t;
typedef struct {
float transform[16]; float transform[16];
vec_uint_t meshes; int parent;
vec_void_t children; vec_uint_t children;
vec_uint_t primitives;
} ModelNode; } ModelNode;
typedef struct { typedef struct {
ModelNode* root; ModelNode* nodes;
vec_model_mesh_t meshes; ModelPrimitive* primitives;
MaterialData* materials;
float* vertices;
void* indices;
int nodeCount;
int primitiveCount;
int materialCount;
int vertexCount;
int vertexSize;
int indexCount;
int hasNormals; int hasNormals;
int hasTexCoords; int hasUVs;
} ModelData; } ModelData;
ModelData* lovrModelDataCreate(Blob* blob); ModelData* lovrModelDataCreate(Blob* blob);

View File

@ -1,6 +1,5 @@
#include "filesystem/blob.h" #include "filesystem/blob.h"
#include "lib/glad/glad.h" #include "lib/glfw.h"
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#pragma once #pragma once

View File

@ -148,3 +148,27 @@ void* luax_optenum(lua_State* L, int index, const char* fallback, map_int_t* map
return value; return value;
} }
Color luax_checkcolor(lua_State* L, int index) {
Color color = { 0xff, 0xff, 0xff, 0xff };
if (lua_istable(L, 1)) {
for (int i = 1; i <= 4; i++) {
lua_rawgeti(L, 1, i);
}
color.r = luaL_checknumber(L, -4);
color.g = luaL_checknumber(L, -3);
color.b = luaL_checknumber(L, -2);
color.a = luaL_optnumber(L, -1, 255);
lua_pop(L, 4);
} else if (lua_gettop(L) >= index + 2) {
color.r = lua_tointeger(L, index);
color.g = lua_tointeger(L, index + 1);
color.b = lua_tointeger(L, index + 2);
color.a = lua_isnoneornil(L, index + 3) ? 255 : lua_tointeger(L, index + 3);
} else {
luaL_error(L, "Invalid color, expected 3 numbers, 4 numbers, or a table");
}
return color;
}

View File

@ -2,6 +2,7 @@
#include <lauxlib.h> #include <lauxlib.h>
#include <lualib.h> #include <lualib.h>
#include "lib/map/map.h" #include "lib/map/map.h"
#include "util.h"
#pragma once #pragma once
@ -35,3 +36,4 @@ void luax_registerobject(lua_State* L, void* object);
void luax_pushenum(lua_State* L, map_int_t* map, int value); void luax_pushenum(lua_State* L, map_int_t* map, int value);
void* luax_checkenum(lua_State* L, int index, map_int_t* map, const char* typeName); void* luax_checkenum(lua_State* L, int index, map_int_t* map, const char* typeName);
void* luax_optenum(lua_State* L, int index, const char* fallback, map_int_t* map, const char* typeName); void* luax_optenum(lua_State* L, int index, const char* fallback, map_int_t* map, const char* typeName);
Color luax_checkcolor(lua_State* L, int index);

View File

@ -2,6 +2,7 @@
#include "math/quat.h" #include "math/quat.h"
#include "math/vec3.h" #include "math/vec3.h"
#include <math.h> #include <math.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
// m0 m4 m8 m12 // m0 m4 m8 m12