Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions VM/src/lapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2092,13 +2092,13 @@ CLANG_NOOPT void GCC_NOOPT lua_fixallcollectable(lua_State *L)

if (key_str)
{
if (!strcmp(key_str, "vector"))
if (!strcmp(key_str, "vector") || !strcmp(key_str, "quaternion") || !strcmp(key_str, "rotation") || !strcmp(key_str, "uuid"))
{
// vector has a special metatable with `__call` which should be fixable.
// vector quaternion rotation and uuid have a special metatable with `__call` which should be fixable.
if (ttistable(global_val))
{
LuaTable *vector_mt = hvalue(global_val)->metatable;
ASSERT_IN_DBG(try_fix_table(vector_mt));
LuaTable *mt = hvalue(global_val)->metatable;
ASSERT_IN_DBG(try_fix_table(mt));
}
}
}
Expand Down
234 changes: 222 additions & 12 deletions VM/src/llsl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,211 @@ static void make_weak_uuid_table(lua_State *L)
lua_setmetatable(L, -2);
}

// ServerLua: callable quaternion module
static int quaternion_call(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_remove(L, 1);
return lsl_quaternion_ctor(L);
}

static inline float quaternion_dot(const float* a, const float* b) {
return ((a)[0] * (b)[0] + (a)[1] * (b)[1] + (a)[2] * (b)[2] + (a)[3] * (b)[3]);
}

static int lua_quaternion_normalize(lua_State *L)
{
const float* quat = luaSL_checkquaternion(L, 1);
float invNorm = 1.0f / sqrtf(quaternion_dot(quat, quat));
luaSL_pushquaternion(L, quat[0] * invNorm, quat[1] * invNorm, quat[2] * invNorm, quat[3] * invNorm);
return 1;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3d26304 thanks!


static int lua_quaternion_magnitude(lua_State *L)
{
const float* quat = luaSL_checkquaternion(L, 1);
lua_pushnumber(L, sqrtf(quaternion_dot(quat, quat)));
return 1;
}


static int lua_quaternion_dot(lua_State *L)
{
const float* a = luaSL_checkquaternion(L, 1);
const float* b = luaSL_checkquaternion(L, 2);
lua_pushnumber(L, quaternion_dot(a, b));
return 1;
}

static int lua_quaternion_slerp(lua_State *L)
{
const float* a = luaSL_checkquaternion(L, 1);
const float* b = luaSL_checkquaternion(L, 2);
const float u = luaL_checknumber(L, 3);

float cos_t = quaternion_dot(a, b);

bool bflip = false;
if (cos_t < 0.0f)
{
cos_t = -cos_t;
bflip = true;
}

float alpha;
float beta;
if(1.0f - cos_t < 0.00001f)
{
beta = 1.0f - u;
alpha = u;
}
else
{
float theta = acosf(cos_t);
float sin_t = sinf(theta);
beta = sinf(theta - u*theta) / sin_t;
alpha = sinf(u*theta) / sin_t;
}

if (bflip)
{
beta = -beta;
}

luaSL_pushquaternion(L,
beta*a[0] + alpha*b[0],
beta*a[1] + alpha*b[1],
beta*a[2] + alpha*b[2],
beta*a[3] + alpha*b[3]);
return 1;
}

static int lua_quaternion_conjugate(lua_State *L)
{
const float* quat = luaSL_checkquaternion(L, 1);
luaSL_pushquaternion(L, -quat[0], -quat[1], -quat[2], quat[3]);
return 1;
}


static inline void push_rotated_vector(lua_State *L, const float* vec) {
const float* quat = luaSL_checkquaternion(L, 1);
float res[3] = {0.0f};
rot_vec(vec, quat, res);
float invSqrt = 1.0f / sqrtf(res[0] * res[0] + res[1] * res[1] + res[2] * res[2]);
lua_pushvector(L, res[0] * invSqrt, res[1] * invSqrt, res[2] * invSqrt);
}

static int lua_quaternion_tofwd(lua_State *L)
{
const float vec[3] = {1.0f, 0.0f, 0.0f};
push_rotated_vector(L, vec);
return 1;
}

static int lua_quaternion_toleft(lua_State *L)
{
const float vec[3] = {0.0f, 1.0f, 0.0f};
push_rotated_vector(L, vec);
return 1;
}

static int lua_quaternion_toup(lua_State *L)
{
const float vec[3] = {0.0f, 0.0f, 1.0f};
push_rotated_vector(L, vec);
return 1;
}

static const luaL_Reg quaternionlib[] = {
{"create", lsl_quaternion_ctor},
{"normalize", lua_quaternion_normalize},
{"magnitude", lua_quaternion_magnitude},
{"dot", lua_quaternion_dot},
{"slerp", lua_quaternion_slerp},
{"conjugate", lua_quaternion_conjugate},
{"tofwd", lua_quaternion_tofwd},
{"toleft", lua_quaternion_toleft},
{"toup", lua_quaternion_toup},
{NULL, NULL},
};

int luaopen_sl_quaternion(lua_State* L, const char* name)
{
[[maybe_unused]] int old_top = lua_gettop(L);
lua_newtable(L);
luaL_register(L, NULL, quaternionlib);

luaSL_pushquaternion(L, 0.0, 0.0, 0.0, 1.0);
lua_setfield(L, -2, "identity");

// ServerLua: `quaternion()` is an alias to `quaternion.create()`, so we need to add a metatable
// to the quaternion module which allows calling it.
lua_newtable(L);
lua_pushcfunction(L, quaternion_call, "__call");
lua_setfield(L, -2, "__call");

// We need to override __iter so generalized iteration doesn't try to use __call.
lua_rawgetfield(L, LUA_BASEGLOBALSINDEX, "pairs");
// This is confusing at first, but we want a unique function identity
// when this shows up anywhere other than globals, otherwise we can
// muck up Ares serialization.
luau_dupcclosure(L, -1, "__iter");
lua_replace(L, -2);
lua_rawsetfield(L, -2, "__iter");

lua_setreadonly(L, -1, true);
lua_setmetatable(L, -2);

lua_setglobal(L, name);

LUAU_ASSERT(lua_gettop(L) == old_top);
return 1;
}

// ServerLua: callable uuid module
static int uuid_call(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_remove(L, 1);
return lua_uuid_ctor(L);
}

static const luaL_Reg uuidlib[] = {
{"create", lua_uuid_ctor},
{NULL, NULL},
};

int luaopen_sl_uuid(lua_State* L)
{
[[maybe_unused]] int old_top = lua_gettop(L);
lua_newtable(L);
luaL_register(L, NULL, uuidlib);

// ServerLua: `uuid()` is an alias to `uuid.create()`, so we need to add a metatable
// to the uuid module which allows calling it.
lua_newtable(L);
lua_pushcfunction(L, uuid_call, "__call");
lua_setfield(L, -2, "__call");

// We need to override __iter so generalized iteration doesn't try to use __call.
lua_rawgetfield(L, LUA_BASEGLOBALSINDEX, "pairs");
// This is confusing at first, but we want a unique function identity
// when this shows up anywhere other than globals, otherwise we can
// muck up Ares serialization.
luau_dupcclosure(L, -1, "__iter");
lua_replace(L, -2);
lua_rawsetfield(L, -2, "__iter");

lua_setreadonly(L, -1, true);
lua_setmetatable(L, -2);

lua_setglobal(L, "uuid");

LUAU_ASSERT(lua_gettop(L) == old_top);
return 1;
}

int luaopen_sl(lua_State* L, int expose_internal_funcs)
{
if (!LUAU_IS_SL_VM(L))
Expand All @@ -1447,23 +1652,14 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
int top = lua_gettop(L);

// Load these into the global namespace
lua_pushcfunction(L, lsl_quaternion_ctor, "quaternion");
luau_dupcclosure(L, -1, "rotation");
// Alias it as "rotation"
lua_setglobal(L, "rotation");
lua_setglobal(L, "quaternion");

if (LUAU_IS_LSL_VM(L))
{
lua_pushcfunction(L, lsl_key_ctor, "uuid");
luau_dupcclosure(L, -1, "touuid");
lua_setglobal(L, "touuid");
lua_setglobal(L, "uuid");
}
else
{
lua_pushcfunction(L, lua_uuid_ctor, "uuid");
}
luau_dupcclosure(L, -1, "touuid");
lua_setglobal(L, "touuid");
lua_setglobal(L, "uuid");

lua_pushcfunction(L, lsl_to_vector, "tovector");
lua_setglobal(L, "tovector");
Expand Down Expand Up @@ -1524,6 +1720,15 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
lua_pop(L, 1);
LUAU_ASSERT(lua_gettop(L) == top);

if (!LUAU_IS_LSL_VM(L))
{
// Create uuid module table
luaopen_sl_uuid(L);
LUAU_ASSERT(lua_gettop(L) == top);
lua_pushcfunction(L, lua_uuid_ctor, "touuid");
lua_setglobal(L, "touuid");
}

//////
/// Quaternions
//////
Expand Down Expand Up @@ -1570,6 +1775,11 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
lua_pop(L, 1);
LUAU_ASSERT(lua_gettop(L) == top);

// Create quaternion module table
luaopen_sl_quaternion(L, "quaternion");
luaopen_sl_quaternion(L, "rotation");
LUAU_ASSERT(lua_gettop(L) == top);

//////
/// DetectedEvent
//////
Expand Down
14 changes: 14 additions & 0 deletions tests/SLConformance.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,20 @@ TEST_CASE("Push UUID string")
});
}

static int give_quaternion(lua_State *L)
{
luaSL_pushquaternion(L, 1.0, 2.0, 3.0, 4.0);
return 1;
}

TEST_CASE("Push Quaternion")
{
runConformance("quaternion.lua", nullptr, [](lua_State *L) {
lua_pushcfunction(L, give_quaternion, "give_quaternion");
lua_setglobal(L, "give_quaternion");
});
}

static int get_num_table_keys(lua_State *L, int idx)
{
lua_pushnil(L);
Expand Down
35 changes: 35 additions & 0 deletions tests/conformance/quaternion.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local quat = give_quaternion()
local yaw_ninety = quaternion(0,0,0.7071068,0.7071068)

assert(quat == quaternion(1, 2, 3, 4))

-- Test string cast is expected format and precision
assert(`{quat}` == "<1, 2, 3, 4>")
assert(`{yaw_ninety}` == "<0, 0, 0.707107, 0.707107>")

-- Test both quaternion and rotation modules exist and can be used for creation the same way
assert(quaternion(1, 2, 3, 4) == quaternion(1, 2, 3, 4))
assert(quaternion.create(4, 3, 2, 1) == rotation.create(4, 3, 2, 1))

-- Test quaternion module functions
assert(quaternion.normalize(quaternion(3, 5, 2, 1)) == quaternion(0.4803844690322876, 0.8006408214569092, 0.3202563226222992, 0.1601281613111496))

assert(quaternion.magnitude(quaternion(0, 0, 0, 1)) == 1)

assert(quaternion.dot(quaternion(1, 2, 3, 4),quaternion(0, 0, 0, 1)) == 4)

assert(quaternion.conjugate(quat) == quaternion(-quat.x, -quat.y, -quat.z, quat.s))

assert(`{quaternion.slerp(yaw_ninety, quaternion(0, 0, 0, 1), 0.5)}` == "<0, 0, 0.382683, 0.92388>")

-- string cast to deal with precision
assert(`{quaternion.tofwd(yaw_ninety)}` == "<0, 1, 0>")
assert(`{quaternion.toleft(yaw_ninety)}` == "<-1, 0, 0>")
assert(`{quaternion.toup(yaw_ninety)}` == "<0, 0, 1>")

-- Check rotation module has same implementation as quaternion module
for k,_ in quaternion do
assert(rotation[k] ~= nil)
end

return "OK"
4 changes: 4 additions & 0 deletions tests/conformance/uuid.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ assert(key == expected_key)
assert(tostring(key) == expected_str)
assert(#expected_key.bytes == 16)

local to_key = touuid(expected_str)
assert(to_key == key)

local expected_key_clone = uuid(buffer.fromstring(expected_key.bytes))
-- This should end up with the same UUID identity because of UUID interning.
assert(expected_key_clone == expected_key)
Expand All @@ -21,6 +24,7 @@ assert(tab[expected_key] == 2)

-- Invalid values result in nil
assert(uuid('foo') == nil)
assert(touuid('foo') == nil)
-- But the blank string is special-cased to mean `NULL_KEY`
assert(uuid('') == uuid('00000000-0000-0000-0000-000000000000'))

Expand Down