Skip to content

Commit ffe7276

Browse files
authored
Convert globals uuid and quaternion to tables, to match vector's implementation (#30)
Convert globals `uuid` and `quaternion` to `table`s, to match `vector`'s implementation --------- Signed-off-by: WolfGangS <flamin2k8@gmail.com>
1 parent 1d99096 commit ffe7276

File tree

5 files changed

+279
-16
lines changed

5 files changed

+279
-16
lines changed

VM/src/lapi.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,13 +2092,13 @@ CLANG_NOOPT void GCC_NOOPT lua_fixallcollectable(lua_State *L)
20922092

20932093
if (key_str)
20942094
{
2095-
if (!strcmp(key_str, "vector"))
2095+
if (!strcmp(key_str, "vector") || !strcmp(key_str, "quaternion") || !strcmp(key_str, "rotation") || !strcmp(key_str, "uuid"))
20962096
{
2097-
// vector has a special metatable with `__call` which should be fixable.
2097+
// vector quaternion rotation and uuid have a special metatable with `__call` which should be fixable.
20982098
if (ttistable(global_val))
20992099
{
2100-
LuaTable *vector_mt = hvalue(global_val)->metatable;
2101-
ASSERT_IN_DBG(try_fix_table(vector_mt));
2100+
LuaTable *mt = hvalue(global_val)->metatable;
2101+
ASSERT_IN_DBG(try_fix_table(mt));
21022102
}
21032103
}
21042104
}

VM/src/llsl.cpp

Lines changed: 222 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,211 @@ static void make_weak_uuid_table(lua_State *L)
14221422
lua_setmetatable(L, -2);
14231423
}
14241424

1425+
// ServerLua: callable quaternion module
1426+
static int quaternion_call(lua_State *L)
1427+
{
1428+
luaL_checktype(L, 1, LUA_TTABLE);
1429+
lua_remove(L, 1);
1430+
return lsl_quaternion_ctor(L);
1431+
}
1432+
1433+
static inline float quaternion_dot(const float* a, const float* b) {
1434+
return ((a)[0] * (b)[0] + (a)[1] * (b)[1] + (a)[2] * (b)[2] + (a)[3] * (b)[3]);
1435+
}
1436+
1437+
static int lua_quaternion_normalize(lua_State *L)
1438+
{
1439+
const float* quat = luaSL_checkquaternion(L, 1);
1440+
float invNorm = 1.0f / sqrtf(quaternion_dot(quat, quat));
1441+
luaSL_pushquaternion(L, quat[0] * invNorm, quat[1] * invNorm, quat[2] * invNorm, quat[3] * invNorm);
1442+
return 1;
1443+
}
1444+
1445+
static int lua_quaternion_magnitude(lua_State *L)
1446+
{
1447+
const float* quat = luaSL_checkquaternion(L, 1);
1448+
lua_pushnumber(L, sqrtf(quaternion_dot(quat, quat)));
1449+
return 1;
1450+
}
1451+
1452+
1453+
static int lua_quaternion_dot(lua_State *L)
1454+
{
1455+
const float* a = luaSL_checkquaternion(L, 1);
1456+
const float* b = luaSL_checkquaternion(L, 2);
1457+
lua_pushnumber(L, quaternion_dot(a, b));
1458+
return 1;
1459+
}
1460+
1461+
static int lua_quaternion_slerp(lua_State *L)
1462+
{
1463+
const float* a = luaSL_checkquaternion(L, 1);
1464+
const float* b = luaSL_checkquaternion(L, 2);
1465+
const float u = luaL_checknumber(L, 3);
1466+
1467+
float cos_t = quaternion_dot(a, b);
1468+
1469+
bool bflip = false;
1470+
if (cos_t < 0.0f)
1471+
{
1472+
cos_t = -cos_t;
1473+
bflip = true;
1474+
}
1475+
1476+
float alpha;
1477+
float beta;
1478+
if(1.0f - cos_t < 0.00001f)
1479+
{
1480+
beta = 1.0f - u;
1481+
alpha = u;
1482+
}
1483+
else
1484+
{
1485+
float theta = acosf(cos_t);
1486+
float sin_t = sinf(theta);
1487+
beta = sinf(theta - u*theta) / sin_t;
1488+
alpha = sinf(u*theta) / sin_t;
1489+
}
1490+
1491+
if (bflip)
1492+
{
1493+
beta = -beta;
1494+
}
1495+
1496+
luaSL_pushquaternion(L,
1497+
beta*a[0] + alpha*b[0],
1498+
beta*a[1] + alpha*b[1],
1499+
beta*a[2] + alpha*b[2],
1500+
beta*a[3] + alpha*b[3]);
1501+
return 1;
1502+
}
1503+
1504+
static int lua_quaternion_conjugate(lua_State *L)
1505+
{
1506+
const float* quat = luaSL_checkquaternion(L, 1);
1507+
luaSL_pushquaternion(L, -quat[0], -quat[1], -quat[2], quat[3]);
1508+
return 1;
1509+
}
1510+
1511+
1512+
static inline void push_rotated_vector(lua_State *L, const float* vec) {
1513+
const float* quat = luaSL_checkquaternion(L, 1);
1514+
float res[3] = {0.0f};
1515+
rot_vec(vec, quat, res);
1516+
float invSqrt = 1.0f / sqrtf(res[0] * res[0] + res[1] * res[1] + res[2] * res[2]);
1517+
lua_pushvector(L, res[0] * invSqrt, res[1] * invSqrt, res[2] * invSqrt);
1518+
}
1519+
1520+
static int lua_quaternion_tofwd(lua_State *L)
1521+
{
1522+
const float vec[3] = {1.0f, 0.0f, 0.0f};
1523+
push_rotated_vector(L, vec);
1524+
return 1;
1525+
}
1526+
1527+
static int lua_quaternion_toleft(lua_State *L)
1528+
{
1529+
const float vec[3] = {0.0f, 1.0f, 0.0f};
1530+
push_rotated_vector(L, vec);
1531+
return 1;
1532+
}
1533+
1534+
static int lua_quaternion_toup(lua_State *L)
1535+
{
1536+
const float vec[3] = {0.0f, 0.0f, 1.0f};
1537+
push_rotated_vector(L, vec);
1538+
return 1;
1539+
}
1540+
1541+
static const luaL_Reg quaternionlib[] = {
1542+
{"create", lsl_quaternion_ctor},
1543+
{"normalize", lua_quaternion_normalize},
1544+
{"magnitude", lua_quaternion_magnitude},
1545+
{"dot", lua_quaternion_dot},
1546+
{"slerp", lua_quaternion_slerp},
1547+
{"conjugate", lua_quaternion_conjugate},
1548+
{"tofwd", lua_quaternion_tofwd},
1549+
{"toleft", lua_quaternion_toleft},
1550+
{"toup", lua_quaternion_toup},
1551+
{NULL, NULL},
1552+
};
1553+
1554+
int luaopen_sl_quaternion(lua_State* L, const char* name)
1555+
{
1556+
[[maybe_unused]] int old_top = lua_gettop(L);
1557+
lua_newtable(L);
1558+
luaL_register(L, NULL, quaternionlib);
1559+
1560+
luaSL_pushquaternion(L, 0.0, 0.0, 0.0, 1.0);
1561+
lua_setfield(L, -2, "identity");
1562+
1563+
// ServerLua: `quaternion()` is an alias to `quaternion.create()`, so we need to add a metatable
1564+
// to the quaternion module which allows calling it.
1565+
lua_newtable(L);
1566+
lua_pushcfunction(L, quaternion_call, "__call");
1567+
lua_setfield(L, -2, "__call");
1568+
1569+
// We need to override __iter so generalized iteration doesn't try to use __call.
1570+
lua_rawgetfield(L, LUA_BASEGLOBALSINDEX, "pairs");
1571+
// This is confusing at first, but we want a unique function identity
1572+
// when this shows up anywhere other than globals, otherwise we can
1573+
// muck up Ares serialization.
1574+
luau_dupcclosure(L, -1, "__iter");
1575+
lua_replace(L, -2);
1576+
lua_rawsetfield(L, -2, "__iter");
1577+
1578+
lua_setreadonly(L, -1, true);
1579+
lua_setmetatable(L, -2);
1580+
1581+
lua_setglobal(L, name);
1582+
1583+
LUAU_ASSERT(lua_gettop(L) == old_top);
1584+
return 1;
1585+
}
1586+
1587+
// ServerLua: callable uuid module
1588+
static int uuid_call(lua_State *L)
1589+
{
1590+
luaL_checktype(L, 1, LUA_TTABLE);
1591+
lua_remove(L, 1);
1592+
return lua_uuid_ctor(L);
1593+
}
1594+
1595+
static const luaL_Reg uuidlib[] = {
1596+
{"create", lua_uuid_ctor},
1597+
{NULL, NULL},
1598+
};
1599+
1600+
int luaopen_sl_uuid(lua_State* L)
1601+
{
1602+
[[maybe_unused]] int old_top = lua_gettop(L);
1603+
lua_newtable(L);
1604+
luaL_register(L, NULL, uuidlib);
1605+
1606+
// ServerLua: `uuid()` is an alias to `uuid.create()`, so we need to add a metatable
1607+
// to the uuid module which allows calling it.
1608+
lua_newtable(L);
1609+
lua_pushcfunction(L, uuid_call, "__call");
1610+
lua_setfield(L, -2, "__call");
1611+
1612+
// We need to override __iter so generalized iteration doesn't try to use __call.
1613+
lua_rawgetfield(L, LUA_BASEGLOBALSINDEX, "pairs");
1614+
// This is confusing at first, but we want a unique function identity
1615+
// when this shows up anywhere other than globals, otherwise we can
1616+
// muck up Ares serialization.
1617+
luau_dupcclosure(L, -1, "__iter");
1618+
lua_replace(L, -2);
1619+
lua_rawsetfield(L, -2, "__iter");
1620+
1621+
lua_setreadonly(L, -1, true);
1622+
lua_setmetatable(L, -2);
1623+
1624+
lua_setglobal(L, "uuid");
1625+
1626+
LUAU_ASSERT(lua_gettop(L) == old_top);
1627+
return 1;
1628+
}
1629+
14251630
int luaopen_sl(lua_State* L, int expose_internal_funcs)
14261631
{
14271632
if (!LUAU_IS_SL_VM(L))
@@ -1432,23 +1637,14 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
14321637
int top = lua_gettop(L);
14331638

14341639
// Load these into the global namespace
1435-
lua_pushcfunction(L, lsl_quaternion_ctor, "quaternion");
1436-
luau_dupcclosure(L, -1, "rotation");
1437-
// Alias it as "rotation"
1438-
lua_setglobal(L, "rotation");
1439-
lua_setglobal(L, "quaternion");
14401640

14411641
if (LUAU_IS_LSL_VM(L))
14421642
{
14431643
lua_pushcfunction(L, lsl_key_ctor, "uuid");
1644+
luau_dupcclosure(L, -1, "touuid");
1645+
lua_setglobal(L, "touuid");
1646+
lua_setglobal(L, "uuid");
14441647
}
1445-
else
1446-
{
1447-
lua_pushcfunction(L, lua_uuid_ctor, "uuid");
1448-
}
1449-
luau_dupcclosure(L, -1, "touuid");
1450-
lua_setglobal(L, "touuid");
1451-
lua_setglobal(L, "uuid");
14521648

14531649
lua_pushcfunction(L, lsl_to_vector, "tovector");
14541650
lua_setglobal(L, "tovector");
@@ -1509,6 +1705,15 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
15091705
lua_pop(L, 1);
15101706
LUAU_ASSERT(lua_gettop(L) == top);
15111707

1708+
if (!LUAU_IS_LSL_VM(L))
1709+
{
1710+
// Create uuid module table
1711+
luaopen_sl_uuid(L);
1712+
LUAU_ASSERT(lua_gettop(L) == top);
1713+
lua_pushcfunction(L, lua_uuid_ctor, "touuid");
1714+
lua_setglobal(L, "touuid");
1715+
}
1716+
15121717
//////
15131718
/// Quaternions
15141719
//////
@@ -1555,6 +1760,11 @@ int luaopen_sl(lua_State* L, int expose_internal_funcs)
15551760
lua_pop(L, 1);
15561761
LUAU_ASSERT(lua_gettop(L) == top);
15571762

1763+
// Create quaternion module table
1764+
luaopen_sl_quaternion(L, "quaternion");
1765+
luaopen_sl_quaternion(L, "rotation");
1766+
LUAU_ASSERT(lua_gettop(L) == top);
1767+
15581768
//////
15591769
/// DetectedEvent
15601770
//////

tests/SLConformance.test.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,20 @@ TEST_CASE("Push UUID string")
345345
});
346346
}
347347

348+
static int give_quaternion(lua_State *L)
349+
{
350+
luaSL_pushquaternion(L, 1.0, 2.0, 3.0, 4.0);
351+
return 1;
352+
}
353+
354+
TEST_CASE("Push Quaternion")
355+
{
356+
runConformance("quaternion.lua", nullptr, [](lua_State *L) {
357+
lua_pushcfunction(L, give_quaternion, "give_quaternion");
358+
lua_setglobal(L, "give_quaternion");
359+
});
360+
}
361+
348362
static int get_num_table_keys(lua_State *L, int idx)
349363
{
350364
lua_pushnil(L);

tests/conformance/quaternion.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
local quat = give_quaternion()
2+
local yaw_ninety = quaternion(0,0,0.7071068,0.7071068)
3+
4+
assert(quat == quaternion(1, 2, 3, 4))
5+
6+
-- Test string cast is expected format and precision
7+
assert(`{quat}` == "<1, 2, 3, 4>")
8+
assert(`{yaw_ninety}` == "<0, 0, 0.707107, 0.707107>")
9+
10+
-- Test both quaternion and rotation modules exist and can be used for creation the same way
11+
assert(quaternion(1, 2, 3, 4) == quaternion(1, 2, 3, 4))
12+
assert(quaternion.create(4, 3, 2, 1) == rotation.create(4, 3, 2, 1))
13+
14+
-- Test quaternion module functions
15+
assert(quaternion.normalize(quaternion(3, 5, 2, 1)) == quaternion(0.4803844690322876, 0.8006408214569092, 0.3202563226222992, 0.1601281613111496))
16+
17+
assert(quaternion.magnitude(quaternion(0, 0, 0, 1)) == 1)
18+
19+
assert(quaternion.dot(quaternion(1, 2, 3, 4),quaternion(0, 0, 0, 1)) == 4)
20+
21+
assert(quaternion.conjugate(quat) == quaternion(-quat.x, -quat.y, -quat.z, quat.s))
22+
23+
assert(`{quaternion.slerp(yaw_ninety, quaternion(0, 0, 0, 1), 0.5)}` == "<0, 0, 0.382683, 0.92388>")
24+
25+
-- string cast to deal with precision
26+
assert(`{quaternion.tofwd(yaw_ninety)}` == "<0, 1, 0>")
27+
assert(`{quaternion.toleft(yaw_ninety)}` == "<-1, 0, 0>")
28+
assert(`{quaternion.toup(yaw_ninety)}` == "<0, 0, 1>")
29+
30+
-- Check rotation module has same implementation as quaternion module
31+
for k,_ in quaternion do
32+
assert(rotation[k] ~= nil)
33+
end
34+
35+
return "OK"

tests/conformance/uuid.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ assert(key == expected_key)
77
assert(tostring(key) == expected_str)
88
assert(#expected_key.bytes == 16)
99

10+
local to_key = touuid(expected_str)
11+
assert(to_key == key)
12+
1013
local expected_key_clone = uuid(buffer.fromstring(expected_key.bytes))
1114
-- This should end up with the same UUID identity because of UUID interning.
1215
assert(expected_key_clone == expected_key)
@@ -21,6 +24,7 @@ assert(tab[expected_key] == 2)
2124

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

0 commit comments

Comments
 (0)