| #ifdef WITH_RIVE_SCRIPTING | |
| #include "rive/math/mat4.hpp" | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include <cstdlib> | |
| #include <cstring> | |
| using namespace rive; | |
| static ScriptedMat4* lua_pushmat4(lua_State* L, const Mat4& mat) | |
| { | |
| return lua_newrive<ScriptedMat4>(L, mat); | |
| } | |
| static ScriptedMat4* lua_pushmat4(lua_State* L) | |
| { | |
| return lua_newrive<ScriptedMat4>(L); | |
| } | |
| // Mat4.values(c0x, c0y, c0z, c0w, c1x, ..., c3w) — column-major. | |
| static int mat4_values(lua_State* L) | |
| { | |
| auto out = lua_pushmat4(L); | |
| float* m = out->value.values(); | |
| for (int i = 0; i < 16; ++i) | |
| { | |
| m[i] = float(luaL_checknumber(L, 1 + i)); | |
| } | |
| return 1; | |
| } | |
| static int mat4_identity(lua_State* L) | |
| { | |
| lua_pushmat4(L, Mat4::identity()); | |
| return 1; | |
| } | |
| static int mat4_fromTranslation(lua_State* L) | |
| { | |
| float x = float(luaL_checknumber(L, 1)); | |
| float y = float(luaL_checknumber(L, 2)); | |
| float z = float(luaL_checknumber(L, 3)); | |
| lua_pushmat4(L, Mat4::fromTranslation(x, y, z)); | |
| return 1; | |
| } | |
| static int mat4_fromScale(lua_State* L) | |
| { | |
| float sx = float(luaL_checknumber(L, 1)); | |
| float sy = lua_isnumber(L, 2) ? float(luaL_checknumber(L, 2)) : sx; | |
| float sz = lua_isnumber(L, 3) ? float(luaL_checknumber(L, 3)) : sx; | |
| lua_pushmat4(L, Mat4::fromScale(sx, sy, sz)); | |
| return 1; | |
| } | |
| static int mat4_fromRotationX(lua_State* L) | |
| { | |
| lua_pushmat4(L, Mat4::fromRotationX(float(luaL_checknumber(L, 1)))); | |
| return 1; | |
| } | |
| static int mat4_fromRotationY(lua_State* L) | |
| { | |
| lua_pushmat4(L, Mat4::fromRotationY(float(luaL_checknumber(L, 1)))); | |
| return 1; | |
| } | |
| static int mat4_fromRotationZ(lua_State* L) | |
| { | |
| lua_pushmat4(L, Mat4::fromRotationZ(float(luaL_checknumber(L, 1)))); | |
| return 1; | |
| } | |
| static int mat4_perspective(lua_State* L) | |
| { | |
| float fov = float(luaL_checknumber(L, 1)); | |
| float aspect = float(luaL_checknumber(L, 2)); | |
| float n = float(luaL_checknumber(L, 3)); | |
| float f = float(luaL_checknumber(L, 4)); | |
| lua_pushmat4(L, Mat4::perspective(fov, aspect, n, f, /*zeroToOne=*/true)); | |
| return 1; | |
| } | |
| // Reverse-Z infinite-far perspective. See Mat4::perspectiveReverseZ. | |
| static int mat4_perspectiveReverseZ(lua_State* L) | |
| { | |
| float fov = float(luaL_checknumber(L, 1)); | |
| float aspect = float(luaL_checknumber(L, 2)); | |
| float n = float(luaL_checknumber(L, 3)); | |
| lua_pushmat4(L, Mat4::perspectiveReverseZ(fov, aspect, n)); | |
| return 1; | |
| } | |
| static int mat4_lookAt(lua_State* L) | |
| { | |
| const float* eye = luaL_checkvector(L, 1); | |
| const float* center = luaL_checkvector(L, 2); | |
| const float* up = luaL_checkvector(L, 3); | |
| lua_pushmat4(L, Mat4::lookAt(eye, center, up)); | |
| return 1; | |
| } | |
| static int mat4_ortho(lua_State* L) | |
| { | |
| float l = float(luaL_checknumber(L, 1)); | |
| float r = float(luaL_checknumber(L, 2)); | |
| float b = float(luaL_checknumber(L, 3)); | |
| float t = float(luaL_checknumber(L, 4)); | |
| float n = float(luaL_checknumber(L, 5)); | |
| float f = float(luaL_checknumber(L, 6)); | |
| lua_pushmat4(L, Mat4::ortho(l, r, b, t, n, f, /*zeroToOne=*/true)); | |
| return 1; | |
| } | |
| // In-place: Mat4.multiply(out, a, b) -> out = a * b. Returns out. | |
| // Avoids per-call userdata allocation in tight loops. | |
| static int mat4_static_multiply(lua_State* L) | |
| { | |
| auto out = lua_torive<ScriptedMat4>(L, 1); | |
| auto a = lua_torive<ScriptedMat4>(L, 2); | |
| auto b = lua_torive<ScriptedMat4>(L, 3); | |
| out->value = Mat4::multiply(a->value, b->value); | |
| lua_pushvalue(L, 1); | |
| return 1; | |
| } | |
| // In-place: Mat4.multiplyAffine(out, a, b) -> out = a * b, assuming both | |
| // inputs are affine (bottom row [0,0,0,1]). Faster than `multiply` (skips | |
| // the bottom-row work). | |
| static int mat4_static_multiplyAffine(lua_State* L) | |
| { | |
| auto out = lua_torive<ScriptedMat4>(L, 1); | |
| auto a = lua_torive<ScriptedMat4>(L, 2); | |
| auto b = lua_torive<ScriptedMat4>(L, 3); | |
| out->value = Mat4::multiplyAffine(a->value, b->value); | |
| lua_pushvalue(L, 1); | |
| return 1; | |
| } | |
| static int mat4_static_invert(lua_State* L) | |
| { | |
| auto out = lua_torive<ScriptedMat4>(L, 1); | |
| auto in = lua_torive<ScriptedMat4>(L, 2); | |
| lua_pushboolean(L, in->value.invert(&out->value)); | |
| return 1; | |
| } | |
| // In-place: Mat4.invertAffine(out, in) — closed-form affine inverse. | |
| // Returns true if invertible. Caller must ensure the input is affine. | |
| static int mat4_static_invertAffine(lua_State* L) | |
| { | |
| auto out = lua_torive<ScriptedMat4>(L, 1); | |
| auto in = lua_torive<ScriptedMat4>(L, 2); | |
| lua_pushboolean(L, in->value.invertAffine(&out->value)); | |
| return 1; | |
| } | |
| // Field index lookup. Supports m11..m44 (row,col 1-indexed) and 1..16 | |
| // (column-major linear index, 1-indexed). | |
| static int mat4_index_field(lua_State* L, | |
| ScriptedMat4* mat, | |
| const char* name, | |
| size_t namelen) | |
| { | |
| if (namelen == 3 && name[0] == 'm') | |
| { | |
| int row = name[1] - '0'; | |
| int col = name[2] - '0'; | |
| if (row >= 1 && row <= 4 && col >= 1 && col <= 4) | |
| { | |
| // m[row][col] 1-indexed; column-major storage means | |
| // index = (col-1)*4 + (row-1). | |
| lua_pushnumber(L, mat->value[(col - 1) * 4 + (row - 1)]); | |
| return 1; | |
| } | |
| } | |
| if (namelen >= 1 && namelen <= 2) | |
| { | |
| char* end = nullptr; | |
| long n = std::strtol(name, &end, 10); | |
| if (end && *end == '\0' && n >= 1 && n <= 16) | |
| { | |
| lua_pushnumber(L, mat->value[n - 1]); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int mat4_newindex_field(lua_State* L, | |
| ScriptedMat4* mat, | |
| const char* name, | |
| size_t namelen, | |
| float value) | |
| { | |
| if (namelen == 3 && name[0] == 'm') | |
| { | |
| int row = name[1] - '0'; | |
| int col = name[2] - '0'; | |
| if (row >= 1 && row <= 4 && col >= 1 && col <= 4) | |
| { | |
| mat->value[(col - 1) * 4 + (row - 1)] = value; | |
| return 0; | |
| } | |
| } | |
| if (namelen >= 1 && namelen <= 2) | |
| { | |
| char* end = nullptr; | |
| long n = std::strtol(name, &end, 10); | |
| if (end && *end == '\0' && n >= 1 && n <= 16) | |
| { | |
| mat->value[n - 1] = value; | |
| return 0; | |
| } | |
| } | |
| return -1; | |
| } | |
| // Direct field getters for the "mRC" matrix-element names (m11..m44). The | |
| // slow path's strtol-based numeric-string fallback ('1'..'16') is still | |
| // reachable for callers that prefer the array-style form. | |
| template <int ColumnMajorIndex> | |
| static void mat4_direct_m(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedMat4*)udata)->value[ColumnMajorIndex]); | |
| } | |
| static int mat4_index(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| if (mat4_index_field(L, mat, name, namelen) == 1) | |
| return 1; | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| name, | |
| ScriptedMat4::luaName); | |
| return 0; | |
| } | |
| static int mat4_newindex(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| float value = float(luaL_checknumber(L, 3)); | |
| if (mat4_newindex_field(L, mat, name, namelen, value) == 0) | |
| return 0; | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| name, | |
| ScriptedMat4::luaName); | |
| return 0; | |
| } | |
| static int mat4_mul(lua_State* L) | |
| { | |
| auto a = lua_torive<ScriptedMat4>(L, 1); | |
| auto b = lua_torive<ScriptedMat4>(L, 2); | |
| lua_pushmat4(L, Mat4::multiply(a->value, b->value)); | |
| return 1; | |
| } | |
| static int mat4_eq(lua_State* L) | |
| { | |
| auto a = lua_torive<ScriptedMat4>(L, 1); | |
| auto b = lua_torive<ScriptedMat4>(L, 2); | |
| lua_pushboolean(L, a->value == b->value); | |
| return 1; | |
| } | |
| static int mat4_invert(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| Mat4 result; | |
| if (mat->value.invert(&result)) | |
| { | |
| lua_pushmat4(L, result); | |
| return 1; | |
| } | |
| lua_pushnil(L); | |
| return 1; | |
| } | |
| static int mat4_invertAffine(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| Mat4 result; | |
| if (mat->value.invertAffine(&result)) | |
| { | |
| lua_pushmat4(L, result); | |
| return 1; | |
| } | |
| lua_pushnil(L); | |
| return 1; | |
| } | |
| static int mat4_transpose(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| lua_pushmat4(L, mat->value.transposed()); | |
| return 1; | |
| } | |
| // mat:transformPoint(x, y, z) -> vector(x', y', z') (w=1, perspective | |
| // divide) | |
| static int mat4_transformPoint(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| float x = float(luaL_checknumber(L, 2)); | |
| float y = float(luaL_checknumber(L, 3)); | |
| float z = float(luaL_checknumber(L, 4)); | |
| float out[4]; | |
| mat->value.transformVec4(out, x, y, z, 1.f); | |
| if (out[3] != 0.f && out[3] != 1.f) | |
| { | |
| float inv = 1.f / out[3]; | |
| lua_pushvector(L, out[0] * inv, out[1] * inv, out[2] * inv); | |
| } | |
| else | |
| { | |
| lua_pushvector(L, out[0], out[1], out[2]); | |
| } | |
| return 1; | |
| } | |
| // mat:transformVec4(x, y, z, w) -> x', y', z', w' (no perspective divide) | |
| // Useful for clip-space transforms where the caller wants the homogeneous w | |
| // preserved. | |
| static int mat4_transformVec4(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| float x = float(luaL_checknumber(L, 2)); | |
| float y = float(luaL_checknumber(L, 3)); | |
| float z = float(luaL_checknumber(L, 4)); | |
| float w = float(luaL_checknumber(L, 5)); | |
| float out[4]; | |
| mat->value.transformVec4(out, x, y, z, w); | |
| lua_pushnumber(L, out[0]); | |
| lua_pushnumber(L, out[1]); | |
| lua_pushnumber(L, out[2]); | |
| lua_pushnumber(L, out[3]); | |
| return 4; | |
| } | |
| // mat:writeToBuffer(buf, byteOffset) — direct 64-byte memcpy of the | |
| // column-major matrix into a Luau buffer (uniform-buffer-friendly). | |
| static int mat4_writeToBuffer(lua_State* L) | |
| { | |
| auto mat = lua_torive<ScriptedMat4>(L, 1); | |
| size_t bufLen = 0; | |
| void* buf = luaL_checkbuffer(L, 2, &bufLen); | |
| int off = int(luaL_checkinteger(L, 3)); | |
| if (off < 0 || size_t(off) + 64 > bufLen) | |
| { | |
| luaL_error(L, "Mat4:writeToBuffer offset out of range"); | |
| return 0; | |
| } | |
| std::memcpy(static_cast<uint8_t*>(buf) + off, mat->value.values(), 64); | |
| return 0; | |
| } | |
| static int mat4_namecall(lua_State* L) | |
| { | |
| int atom; | |
| if (lua_namecallatom(L, &atom)) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::invert: | |
| return mat4_invert(L); | |
| case (int)LuaAtoms::invertAffine: | |
| return mat4_invertAffine(L); | |
| case (int)LuaAtoms::transpose: | |
| return mat4_transpose(L); | |
| case (int)LuaAtoms::transformPoint: | |
| return mat4_transformPoint(L); | |
| case (int)LuaAtoms::transformVec4: | |
| return mat4_transformVec4(L); | |
| case (int)LuaAtoms::writeToBuffer: | |
| return mat4_writeToBuffer(L); | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| luaL_checkstring(L, 1), | |
| ScriptedMat4::luaName); | |
| return 0; | |
| } | |
| static const luaL_Reg mat4StaticMethods[] = { | |
| {"identity", mat4_identity}, | |
| {"values", mat4_values}, | |
| {"fromTranslation", mat4_fromTranslation}, | |
| {"fromScale", mat4_fromScale}, | |
| {"fromRotationX", mat4_fromRotationX}, | |
| {"fromRotationY", mat4_fromRotationY}, | |
| {"fromRotationZ", mat4_fromRotationZ}, | |
| {"perspective", mat4_perspective}, | |
| {"perspectiveReverseZ", mat4_perspectiveReverseZ}, | |
| {"lookAt", mat4_lookAt}, | |
| {"ortho", mat4_ortho}, | |
| {"multiply", mat4_static_multiply}, | |
| {"multiplyAffine", mat4_static_multiplyAffine}, | |
| {"invert", mat4_static_invert}, | |
| {"invertAffine", mat4_static_invertAffine}, | |
| {nullptr, nullptr}}; | |
| int luaopen_rive_mat4(lua_State* L) | |
| { | |
| luaL_register(L, ScriptedMat4::luaName, mat4StaticMethods); | |
| lua_register_rive<ScriptedMat4>(L); | |
| lua_pushcfunction(L, mat4_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, mat4_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, mat4_mul, nullptr); | |
| lua_setfield(L, -2, "__mul"); | |
| lua_pushcfunction(L, mat4_eq, nullptr); | |
| lua_setfield(L, -2, "__eq"); | |
| lua_pushcfunction(L, mat4_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop metatable | |
| uint8_t tag = ScriptedMat4::luaTag; | |
| // m[row][col] is 1-indexed; column-major storage means | |
| // index = (col-1)*4 + (row-1). | |
| lua_registeruserdatadirectfieldget(L, tag, "m11", mat4_direct_m<0>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m21", mat4_direct_m<1>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m31", mat4_direct_m<2>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m41", mat4_direct_m<3>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m12", mat4_direct_m<4>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m22", mat4_direct_m<5>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m32", mat4_direct_m<6>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m42", mat4_direct_m<7>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m13", mat4_direct_m<8>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m23", mat4_direct_m<9>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m33", mat4_direct_m<10>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m43", mat4_direct_m<11>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m14", mat4_direct_m<12>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m24", mat4_direct_m<13>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m34", mat4_direct_m<14>); | |
| lua_registeruserdatadirectfieldget(L, tag, "m44", mat4_direct_m<15>); | |
| return 1; | |
| } | |
| #endif |