Vector API improvements;

This commit is contained in:
bjorn 2019-01-06 03:25:00 -08:00
parent c7fa8f8497
commit 40c7deaefe
8 changed files with 1220 additions and 861 deletions

View File

@ -25,6 +25,7 @@ ffi.cdef [[
vec3* vec3_normalize(vec3* v);
float vec3_length(vec3* v);
float vec3_distance(vec3* v, vec3* u);
vec3* vec3_cross(vec3* v, vec3* u);
vec3* vec3_lerp(vec3* v, vec3* u, float t);
@ -114,6 +115,56 @@ ffi.metatype(vec3, {
return vec3(v:unpack())
end,
add = function(v, u)
checkvec3(v)
checkvec3(u, 1)
v.x = v.x + u.x
v.y = v.y + u.y
v.z = v.z + u.z
return v
end,
sub = function(v, u)
checkvec3(v)
checkvec3(u, 1)
v.x = v.x - u.x
v.y = v.y - u.y
v.z = v.z - u.z
return v
end,
mul = function(v, u)
checkvec3(v)
if type(u) == 'number' then
v.x = v.x * u
v.y = v.y * u
v.z = v.z * u
return v
else
checkvec3(u, 1)
v.x = v.x * u.x
v.y = v.y * u.y
v.z = v.z * u.z
return v
end
end,
div = function(v, u)
checkvec3(v)
if type(u) == 'number' then
v.x = v.x / u
v.y = v.y / u
v.z = v.z / u
return v
else
checkvec3(u, 1)
v.x = v.x / u.x
v.y = v.y / u.y
v.z = v.z / u.z
return v
end
end,
length = function(v)
checkvec3(v)
return C.vec3_length(v)
@ -125,31 +176,73 @@ ffi.metatype(vec3, {
return v
end,
distance = function(v, u)
checkvec3(v)
checkvec3(u, 1)
return C.vec3_distance(v, u)
end,
dot = function(v, u)
checkvec3(v)
checkvec3(u)
checkvec3(u, 1)
return v.x * u.x + v.y * u.y + v.z * u.z
end,
cross = function(v, u)
checkvec3(v)
checkvec3(u)
checkvec3(u, 1)
C.vec3_cross(v, u)
return v
end,
lerp = function(v, u, t)
checkvec3(v)
checkvec3(u)
checkvec3(u, 1)
C.vec3_lerp(v, u, t)
return v
end
},
__add = function(v, u) return math.vec3(v.x + u.x, v.y + u.y, v.z + u.z) end,
__sub = function(v, u) return math.vec3(v.x - u.x, v.y - u.y, v.z - u.z) end,
__mul = function(v, u) return math.vec3(v.x * u.x, v.y * u.y, v.z * u.z) end,
__div = function(v, u) return math.vec3(v.x / u.x, v.y / u.y, v.z / u.z) end,
__add = function(v, u)
checkvec3(v, 1)
checkvec3(u, 2)
return math.vec3(v.x + u.x, v.y + u.y, v.z + u.z)
end,
__sub = function(v, u)
checkvec3(v, 1)
checkvec3(u, 2)
return math.vec3(v.x - u.x, v.y - u.y, v.z - u.z)
end,
__mul = function(v, u)
if type(v) == 'number' then
checkvec3(u, 2)
return math.vec3(v * u.x, v * u.y, v * u.z)
elseif type(u) == 'number' then
checkvec3(v, 1)
return math.vec3(v.x * u, v.y * u, v.z * u)
else
checkvec3(v, 1)
checkvec3(u, 2, 'vec3 or number')
return math.vec3(v.x * u.x, v.y * u.y, v.z * u.z)
end
end,
__div = function(v, u)
if type(v) == 'number' then
checkvec3(u, 2)
return math.vec3(v / u.x, v / u.y, v / u.z)
elseif type(u) == 'number' then
checkvec3(v, 1)
return math.vec3(v.x / u, v.y / u, v.z / u)
else
checkvec3(v, 1)
checkvec3(u, 'vec3 or number')
return math.vec3(v.x / u.x, v.y / u.y, v.z / u.z)
end
end,
__unm = function(v) return math.vec3(-v.x, -v.y, -v.z) end,
__len = function(v) return C.vec3_length(v) end,
__tostring = function(v) return string.format('(%f, %f, %f)', v.x, v.y, v.z) end
@ -180,7 +273,7 @@ ffi.metatype(quat, {
C.quat_fromAngleAxis(q, x, y, z, w)
end
else
local axis = checkvec3(y)
local axis = checkvec3(y, 2)
C.quat_fromAngleAxis(q, x, y.x, y.y, y.z)
end
elseif istype(vec3, x) then
@ -194,8 +287,6 @@ ffi.metatype(quat, {
q.x, q.y, q.z, q.w = x.x, x.y, x.z, x.w
elseif istype(mat4, x) then
C.quat_fromMat4(q, x)
else
error('Expected a vec3, quat, mat4, or number')
end
return q
end,
@ -213,6 +304,18 @@ ffi.metatype(quat, {
return quat(quat, q:unpack())
end,
mul = function(q, r)
checkquat(q)
if istype(vec3, r) then
C.quat_rotate(q, r)
return r
else
checkquat(r, 1, 'vec3 or quat')
C.quat_mul(q, r)
return q
end
end,
normalize = function(q)
checkquat(q)
C.quat_normalize(q)
@ -220,22 +323,20 @@ ffi.metatype(quat, {
slerp = function(q, r, t)
checkquat(q)
checkquat(r)
checkquat(r, 1)
C.quat_slerp(q, r, t)
end
},
__mul = function(q, r)
checkquat(q)
checkquat(q, 1)
if istype(vec3, r) then
local v = math.vec3(r)
C.quat_rotate(q, v)
return v
else
checkquat(r, 'Expected a vec3 or quat')
local out = math.quat()
C.quat_mul(C.quat_init(out, q), r)
return out
checkquat(r, 2, 'vec3 or quat')
return C.quat_mul(math.quat(q), r)
end
end,
@ -370,13 +471,13 @@ ffi.metatype(mat4, {
},
__mul = function(m, n)
checkmat4(m)
checkmat4(m, 1)
if istype(mat4, n) then
local out = math.mat4(m)
C.mat4_multiply(out, n)
return out
else
checkvec3(n, 1, 'mat4 or vec3')
checkvec3(n, 2, 'mat4 or vec3')
local f = new('float[3]', n.x, n.y, n.z)
C.mat4_transform(m, f + 0, f + 1, f + 2)
return math.vec3(f[0], f[1], f[2])

File diff suppressed because it is too large Load Diff

View File

@ -121,6 +121,21 @@ static int l_lovrQuatSave(lua_State* L) {
return 1;
}
static int l_lovrQuatMul(lua_State* L) {
quat q = luax_checkmathtype(L, 1, MATH_QUAT, NULL);
MathType type;
float* r = luax_tomathtype(L, 2, &type);
if (!r) return luaL_typerror(L, 2, "quat or vec3");
if (type == MATH_VEC3) {
quat_rotate(q, r);
lua_settop(L, 2);
} else {
quat_mul(q, r);
lua_settop(L, 1);
}
return 1;
}
static int l_lovrQuatNormalize(lua_State* L) {
quat q = luax_checkmathtype(L, 1, MATH_QUAT, NULL);
quat_normalize(q);
@ -141,6 +156,7 @@ static int l_lovrQuat__mul(lua_State* L) {
quat q = luax_checkmathtype(L, 1, MATH_QUAT, NULL);
MathType type;
float* r = luax_tomathtype(L, 2, &type);
if (!r) return luaL_typerror(L, 2, "quat or vec3");
if (type == MATH_VEC3) {
vec3 out = lovrPoolAllocate(lovrMathGetPool(), MATH_VEC3);
quat_rotate(q, vec3_init(out, r));
@ -170,6 +186,7 @@ const luaL_Reg lovrQuat[] = {
{ "set", l_lovrQuatSet },
{ "copy", l_lovrQuatCopy },
{ "save", l_lovrQuatSave },
{ "mul", l_lovrQuatMul },
{ "normalize", l_lovrQuatNormalize },
{ "slerp", l_lovrQuatSlerp },
{ "__mul", l_lovrQuat__mul },

View File

@ -98,6 +98,46 @@ static int l_lovrVec3Save(lua_State* L) {
return 1;
}
static int l_lovrVec3Add(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3_add(v, u);
lua_settop(L, 1);
return 1;
}
static int l_lovrVec3Sub(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3_sub(v, u);
lua_settop(L, 1);
return 1;
}
static int l_lovrVec3Mul(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
if (lua_type(L, 2) == LUA_TNUMBER) {
vec3_scale(v, lua_tonumber(L, 2));
} else {
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, "vec3 or number");
v[0] = v[0] * u[0], v[1] = v[1] * u[1], v[2] = v[2] * u[2];
}
lua_settop(L, 1);
return 1;
}
static int l_lovrVec3Div(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
if (lua_type(L, 2) == LUA_TNUMBER) {
vec3_scale(v, 1.f / (float) lua_tonumber(L, 2));
} else {
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, "vec3 or number");
v[0] = v[0] / u[0], v[1] = v[1] / u[1], v[2] = v[2] / u[2];
}
lua_settop(L, 1);
return 1;
}
static int l_lovrVec3Length(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
lua_pushnumber(L, vec3_length(v));
@ -111,6 +151,13 @@ static int l_lovrVec3Normalize(lua_State* L) {
return 1;
}
static int l_lovrVec3Distance(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
lua_pushnumber(L, vec3_distance(v, u));
return 1;
}
static int l_lovrVec3Dot(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
@ -139,7 +186,7 @@ static int l_lovrVec3__add(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3 out = lovrPoolAllocate(lovrMathGetPool(), MATH_VEC3);
out[0] = v[0] + u[0], out[1] = v[1] + u[1], out[2] = v[2] + u[2];
vec3_add(vec3_init(out, v), u);
luax_pushlightmathtype(L, out, MATH_VEC3);
return 1;
}
@ -148,17 +195,21 @@ static int l_lovrVec3__sub(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3 out = lovrPoolAllocate(lovrMathGetPool(), MATH_VEC3);
out[0] = v[0] - u[0], out[1] = v[1] - u[1], out[2] = v[2] - u[2];
vec3_sub(vec3_init(out, v), u);
luax_pushlightmathtype(L, out, MATH_VEC3);
return 1;
}
static int l_lovrVec3__mul(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 out = lovrPoolAllocate(lovrMathGetPool(), MATH_VEC3);
if (lua_type(L, 2) == LUA_TNUMBER) {
if (lua_type(L, 1) == LUA_TNUMBER) {
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3_scale(vec3_init(out, u), lua_tonumber(L, 1));
} else if (lua_type(L, 2) == LUA_TNUMBER) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3_scale(vec3_init(out, v), lua_tonumber(L, 2));
} else {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, "vec3 or number");
out[0] = v[0] * u[0], out[1] = v[1] * u[1], out[2] = v[2] * u[2];
}
@ -167,11 +218,15 @@ static int l_lovrVec3__mul(lua_State* L) {
}
static int l_lovrVec3__div(lua_State* L) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 out = lovrPoolAllocate(lovrMathGetPool(), MATH_VEC3);
if (lua_type(L, 2) == LUA_TNUMBER) {
vec3_scale(vec3_init(out, v), 1 / lua_tonumber(L, 2));
if (lua_type(L, 1) == LUA_TNUMBER) {
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, NULL);
vec3_scale(vec3_init(out, u), 1. / lua_tonumber(L, 1));
} else if (lua_type(L, 2) == LUA_TNUMBER) {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3_scale(vec3_init(out, v), 1. / lua_tonumber(L, 2));
} else {
vec3 v = luax_checkmathtype(L, 1, MATH_VEC3, NULL);
vec3 u = luax_checkmathtype(L, 2, MATH_VEC3, "vec3 or number");
out[0] = v[0] / u[0], out[1] = v[1] / u[1], out[2] = v[2] / u[2];
}
@ -204,8 +259,13 @@ const luaL_Reg lovrVec3[] = {
{ "set", l_lovrVec3Set },
{ "copy", l_lovrVec3Copy },
{ "save", l_lovrVec3Save },
{ "add", l_lovrVec3Add },
{ "sub", l_lovrVec3Sub },
{ "mul", l_lovrVec3Mul },
{ "div", l_lovrVec3Div },
{ "length", l_lovrVec3Length },
{ "normalize", l_lovrVec3Normalize },
{ "distance", l_lovrVec3Distance },
{ "dot", l_lovrVec3Dot },
{ "cross", l_lovrVec3Cross },
{ "lerp", l_lovrVec3Lerp },

View File

@ -26,6 +26,13 @@ vec3 vec3_add(vec3 v, vec3 u) {
return v;
}
vec3 vec3_sub(vec3 v, vec3 u) {
v[0] -= u[0];
v[1] -= u[1];
v[2] -= u[2];
return v;
}
vec3 vec3_scale(vec3 v, float s) {
v[0] *= s;
v[1] *= s;
@ -42,6 +49,13 @@ float vec3_length(vec3 v) {
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
float vec3_distance(vec3 v, vec3 u) {
float dx = v[0] - u[0];
float dy = v[1] - u[1];
float dz = v[2] - u[2];
return sqrt(dx * dx + dy * dy + dz * dz);
}
float vec3_dot(vec3 v, vec3 u) {
return v[0] * u[0] + v[1] * u[1] + v[2] * u[2];
}

View File

@ -2,59 +2,67 @@
#pragma once
#ifdef _WIN32
#define MAF_EXPORT __declspec(dllexport)
#else
#define MAF_EXPORT
#endif
typedef float* vec3;
typedef float* quat;
typedef float* mat4;
// vec3
LOVR_EXPORT vec3 vec3_init(vec3 v, vec3 u);
LOVR_EXPORT vec3 vec3_set(vec3 v, float x, float y, float z);
LOVR_EXPORT vec3 vec3_add(vec3 v, vec3 u);
LOVR_EXPORT vec3 vec3_scale(vec3 v, float s);
LOVR_EXPORT vec3 vec3_normalize(vec3 v);
LOVR_EXPORT float vec3_length(vec3 v);
LOVR_EXPORT float vec3_dot(vec3 v, vec3 u);
LOVR_EXPORT vec3 vec3_cross(vec3 v, vec3 u);
LOVR_EXPORT vec3 vec3_lerp(vec3 v, vec3 u, float t);
MAF_EXPORT vec3 vec3_init(vec3 v, vec3 u);
MAF_EXPORT vec3 vec3_set(vec3 v, float x, float y, float z);
MAF_EXPORT vec3 vec3_add(vec3 v, vec3 u);
MAF_EXPORT vec3 vec3_sub(vec3 v, vec3 u);
MAF_EXPORT vec3 vec3_scale(vec3 v, float s);
MAF_EXPORT vec3 vec3_normalize(vec3 v);
MAF_EXPORT float vec3_length(vec3 v);
MAF_EXPORT float vec3_distance(vec3 v, vec3 u);
MAF_EXPORT float vec3_dot(vec3 v, vec3 u);
MAF_EXPORT vec3 vec3_cross(vec3 v, vec3 u);
MAF_EXPORT vec3 vec3_lerp(vec3 v, vec3 u, float t);
// quat
LOVR_EXPORT quat quat_init(quat q, quat r);
LOVR_EXPORT quat quat_set(quat q, float x, float y, float z, float w);
LOVR_EXPORT quat quat_fromAngleAxis(quat q, float angle, float ax, float ay, float az);
LOVR_EXPORT quat quat_fromMat4(quat q, mat4 m);
LOVR_EXPORT quat quat_mul(quat q, quat r);
LOVR_EXPORT quat quat_normalize(quat q);
LOVR_EXPORT float quat_length(quat q);
LOVR_EXPORT quat quat_slerp(quat q, quat r, float t);
LOVR_EXPORT void quat_rotate(quat q, vec3 v);
LOVR_EXPORT void quat_getAngleAxis(quat q, float* angle, float* x, float* y, float* z);
LOVR_EXPORT quat quat_between(quat q, vec3 u, vec3 v);
MAF_EXPORT quat quat_init(quat q, quat r);
MAF_EXPORT quat quat_set(quat q, float x, float y, float z, float w);
MAF_EXPORT quat quat_fromAngleAxis(quat q, float angle, float ax, float ay, float az);
MAF_EXPORT quat quat_fromMat4(quat q, mat4 m);
MAF_EXPORT quat quat_mul(quat q, quat r);
MAF_EXPORT quat quat_normalize(quat q);
MAF_EXPORT float quat_length(quat q);
MAF_EXPORT quat quat_slerp(quat q, quat r, float t);
MAF_EXPORT void quat_rotate(quat q, vec3 v);
MAF_EXPORT void quat_getAngleAxis(quat q, float* angle, float* x, float* y, float* z);
MAF_EXPORT quat quat_between(quat q, vec3 u, vec3 v);
// mat4
#define MAT4_IDENTITY { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }
#define mat4_init mat4_set
LOVR_EXPORT mat4 mat4_set(mat4 m, mat4 n);
LOVR_EXPORT mat4 mat4_fromMat34(mat4 m, float (*n)[4]);
LOVR_EXPORT mat4 mat4_fromMat44(mat4 m, float (*n)[4]);
LOVR_EXPORT mat4 mat4_identity(mat4 m);
LOVR_EXPORT mat4 mat4_invert(mat4 m);
LOVR_EXPORT mat4 mat4_transpose(mat4 m);
LOVR_EXPORT mat4 mat4_multiply(mat4 m, mat4 n);
LOVR_EXPORT mat4 mat4_translate(mat4 m, float x, float y, float z);
LOVR_EXPORT mat4 mat4_rotate(mat4 m, float angle, float x, float y, float z);
LOVR_EXPORT mat4 mat4_rotateQuat(mat4 m, quat q);
LOVR_EXPORT mat4 mat4_scale(mat4 m, float x, float y, float z);
LOVR_EXPORT void mat4_getPose(mat4 m, float* x, float* y, float* z, float* angle, float* ax, float* ay, float* az);
LOVR_EXPORT void mat4_getTransform(mat4 m, float* x, float* y, float* z, float* sx, float* sy, float* sz, float* angle, float* ax, float* ay, float* az);
LOVR_EXPORT mat4 mat4_setTransform(mat4 m, float x, float y, float z, float sx, float sy, float sz, float angle, float ax, float ay, float az);
LOVR_EXPORT mat4 mat4_orthographic(mat4 m, float left, float right, float top, float bottom, float near, float far);
LOVR_EXPORT mat4 mat4_perspective(mat4 m, float near, float far, float fov, float aspect);
LOVR_EXPORT mat4 mat4_lookAt(mat4 m, vec3 from, vec3 to, vec3 up);
LOVR_EXPORT void mat4_transform(mat4 m, float* x, float* y, float* z);
LOVR_EXPORT void mat4_transformDirection(mat4 m, float* x, float* y, float* z);
MAF_EXPORT mat4 mat4_set(mat4 m, mat4 n);
MAF_EXPORT mat4 mat4_fromMat34(mat4 m, float (*n)[4]);
MAF_EXPORT mat4 mat4_fromMat44(mat4 m, float (*n)[4]);
MAF_EXPORT mat4 mat4_identity(mat4 m);
MAF_EXPORT mat4 mat4_invert(mat4 m);
MAF_EXPORT mat4 mat4_transpose(mat4 m);
MAF_EXPORT mat4 mat4_multiply(mat4 m, mat4 n);
MAF_EXPORT mat4 mat4_translate(mat4 m, float x, float y, float z);
MAF_EXPORT mat4 mat4_rotate(mat4 m, float angle, float x, float y, float z);
MAF_EXPORT mat4 mat4_rotateQuat(mat4 m, quat q);
MAF_EXPORT mat4 mat4_scale(mat4 m, float x, float y, float z);
MAF_EXPORT void mat4_getPose(mat4 m, float* x, float* y, float* z, float* angle, float* ax, float* ay, float* az);
MAF_EXPORT void mat4_getTransform(mat4 m, float* x, float* y, float* z, float* sx, float* sy, float* sz, float* angle, float* ax, float* ay, float* az);
MAF_EXPORT mat4 mat4_setTransform(mat4 m, float x, float y, float z, float sx, float sy, float sz, float angle, float ax, float ay, float az);
MAF_EXPORT mat4 mat4_orthographic(mat4 m, float left, float right, float top, float bottom, float near, float far);
MAF_EXPORT mat4 mat4_perspective(mat4 m, float near, float far, float fov, float aspect);
MAF_EXPORT mat4 mat4_lookAt(mat4 m, vec3 from, vec3 to, vec3 up);
MAF_EXPORT void mat4_transform(mat4 m, float* x, float* y, float* z);
MAF_EXPORT void mat4_transformDirection(mat4 m, float* x, float* y, float* z);
#ifdef LOVR_USE_SSE
LOVR_EXPORT mat4 mat4_invertPose(mat4 m);
MAF_EXPORT mat4 mat4_invertPose(mat4 m);
#else
#define mat4_invertPose mat4_invert
#endif

View File

@ -2,8 +2,8 @@
#include "platform.h"
#include <stdlib.h>
LOVR_THREADLOCAL lovrErrorHandler lovrErrorCallback = NULL;
LOVR_THREADLOCAL void* lovrErrorUserdata = NULL;
_Thread_local lovrErrorHandler lovrErrorCallback = NULL;
_Thread_local void* lovrErrorUserdata = NULL;
void lovrSetErrorCallback(lovrErrorHandler callback, void* userdata) {
lovrErrorCallback = callback;

View File

@ -6,12 +6,12 @@
#ifdef _WIN32
#define LOVR_EXPORT __declspec(dllexport)
#define LOVR_NORETURN __declspec(noreturn)
#define LOVR_THREADLOCAL __declspec(thread)
#define _Noreturn __declspec(noreturn)
#define _Thread_local __declspec(thread)
#else
#define LOVR_EXPORT
#define LOVR_NORETURN __attribute__((noreturn))
#define LOVR_THREADLOCAL __thread
#define _Noreturn __attribute__((noreturn))
#define _Thread_local __thread
#endif
#define CHECK_SIZEOF(T) int(*_o)[sizeof(T)]=1
@ -33,11 +33,11 @@ typedef struct ref {
typedef struct { float r, g, b, a; } Color;
typedef void (*lovrErrorHandler)(void* userdata, const char* format, va_list args);
extern LOVR_THREADLOCAL lovrErrorHandler lovrErrorCallback;
extern LOVR_THREADLOCAL void* lovrErrorUserdata;
extern _Thread_local lovrErrorHandler lovrErrorCallback;
extern _Thread_local void* lovrErrorUserdata;
void lovrSetErrorCallback(lovrErrorHandler callback, void* context);
void LOVR_NORETURN lovrThrow(const char* format, ...);
void _Noreturn lovrThrow(const char* format, ...);
void* _lovrAlloc(const char* type, size_t size, void (*destructor)(void*));
void lovrRetain(void* object);
void lovrRelease(void* object);