From 48538d5ceff0ca4d809e4606d2cecc94903a1dcf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 14:43:15 +0200 Subject: [PATCH] 3D transforms in Lua --- apps/openmw/mwlua/luabindings.cpp | 2 +- .../lua/test_utilpackage.cpp | 121 ++++++++---- components/lua/utilpackage.cpp | 174 +++++++++++++----- components/lua/utilpackage.hpp | 15 +- files/lua_api/openmw/util.lua | 87 ++++++++- 5 files changed, 309 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 6526a18367..aceffc24db 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 6; + api["API_REVISION"] = 7; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index fb8e48e461..934cbf761a 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -10,30 +10,41 @@ namespace { using namespace testing; + template + T get(sol::state& lua, std::string luaCode) + { + return lua.safe_script("return " + luaCode).get(); + } + + std::string getAsString(sol::state& lua, std::string luaCode) + { + return LuaUtil::toString(lua.safe_script("return " + luaCode)); + } + TEST(LuaUtilPackageTest, Vector2) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 3); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 4); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get(), 17); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get(), 17); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 3); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 4); + EXPECT_EQ(get(lua, "tostring(v)"), "(3, 4)"); + EXPECT_FLOAT_EQ(get(lua, "v:length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "v:length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector2(1, 2) == util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) == util.vector2(2, 4) / 2")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) * 2 == util.vector2(2, 4)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2) * v"), 17); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2):dot(v)"), 17); EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault lua.safe_script("v2, len = v:normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); } TEST(LuaUtilPackageTest, Vector3) @@ -42,27 +53,59 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector3(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); - EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); - EXPECT_EQ(LuaUtil::toString(lua.safe_script("return v")), "(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get(), 5*3 + 12*2 + 13*1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get(), 5*3 + 12*2 + 13*1); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)").get()); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); + EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); + EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13)"); + EXPECT_EQ(getAsString(lua, "v"), "(5, 12, 13)"); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector3(1, 2, 3) == util.vector3(1, 3, 2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1) * v"), 5*3 + 12*2 + 13*1); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1):dot(v)"), 5*3 + 12*2 + 13*1); + EXPECT_TRUE(get(lua, "util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)")); EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata"); lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); + } + + TEST(LuaUtilPackageTest, Transform) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua["T"] = lua["util"]["transform"]; + lua["v"] = lua["util"]["vector3"]; + EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index"); + EXPECT_EQ(getAsString(lua, "T.identity * v(3, 4, 5)"), "(3, 4, 5)"); + EXPECT_EQ(getAsString(lua, "T.move(1, 2, 3) * v(3, 4, 5)"), "(4, 6, 8)"); + EXPECT_EQ(getAsString(lua, "T.scale(1, -2, 3) * v(3, 4, 5)"), "(3, -8, 15)"); + EXPECT_EQ(getAsString(lua, "T.scale(v(1, 2, 3)) * v(3, 4, 5)"), "(3, 8, 15)"); + lua.safe_script("moveAndScale = T.move(v(1, 2, 3)) * T.scale(0.5, 1, 0.5) * T.move(10, 20, 30)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(0, 0, 0)"), "(6, 22, 18)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); + EXPECT_EQ(getAsString(lua, "moveAndScale"), "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) }"); + EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); + lua.safe_script("rx = T.rotateX(math.pi / 2)"); + lua.safe_script("ry = T.rotateY(math.pi / 2)"); + lua.safe_script("rz = T.rotateZ(math.pi / 2)"); + EXPECT_LT(get(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz * v(1, 2, 3) - v(-2, 1, 3)):length()"), 1e-6); + lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(-math.pi / 4)"); + EXPECT_THAT(getAsString(lua, "rot"), HasSubstr("TransformQ")); + EXPECT_LT(get(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rot * rot:inverse() * v(1, 0, 0) - v(1, 0, 0)):length()"), 1e-6); + lua.safe_script("rz_move_rx = rz * T.move(0, 3, 0) * rx"); + EXPECT_LT(get(lua, "(rz_move_rx * v(1, 2, 3) - v(0, 1, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6); } TEST(LuaUtilPackageTest, UtilityFunctions) @@ -71,12 +114,12 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), -0.5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 0.86602539); - EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get(), 0); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get(), 1.5); + EXPECT_FLOAT_EQ(get(lua, "v.x"), -0.5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 0.86602539); + EXPECT_FLOAT_EQ(get(lua, "util.normalizeAngle(math.pi * 10 + 0.1)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5); } } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index abcc6d424e..17cb64461a 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -3,17 +3,23 @@ #include #include -#include - #include +#include "luastate.hpp" + namespace sol { template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; } namespace LuaUtil @@ -23,70 +29,148 @@ namespace LuaUtil { sol::table util(lua, sol::create); - // TODO: Add bindings for osg::Matrix - - // Lua bindings for osg::Vec2f - util["vector2"] = [](float x, float y) { return osg::Vec2f(x, y); }; - sol::usertype vec2Type = lua.new_usertype("Vec2"); - vec2Type["x"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.x(); } ); - vec2Type["y"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const osg::Vec2f& v) { + // Lua bindings for Vec2 + util["vector2"] = [](float x, float y) { return Vec2(x, y); }; + sol::usertype vec2Type = lua.new_usertype("Vec2"); + vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); + vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); + vec2Type[sol::meta_function::to_string] = [](const Vec2& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ")"; return ss.str(); }; - vec2Type[sol::meta_function::unary_minus] = [](const osg::Vec2f& a) { return -a; }; - vec2Type[sol::meta_function::addition] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a + b; }; - vec2Type[sol::meta_function::subtraction] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a - b; }; - vec2Type[sol::meta_function::equal_to] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a == b; }; + vec2Type[sol::meta_function::unary_minus] = [](const Vec2& a) { return -a; }; + vec2Type[sol::meta_function::addition] = [](const Vec2& a, const Vec2& b) { return a + b; }; + vec2Type[sol::meta_function::subtraction] = [](const Vec2& a, const Vec2& b) { return a - b; }; + vec2Type[sol::meta_function::equal_to] = [](const Vec2& a, const Vec2& b) { return a == b; }; vec2Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec2f& a, float c) { return a * c; }, - [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }); - vec2Type[sol::meta_function::division] = [](const osg::Vec2f& a, float c) { return a / c; }; - vec2Type["dot"] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }; - vec2Type["length"] = &osg::Vec2f::length; - vec2Type["length2"] = &osg::Vec2f::length2; - vec2Type["normalize"] = [](const osg::Vec2f& v) { + [](const Vec2& a, float c) { return a * c; }, + [](const Vec2& a, const Vec2& b) { return a * b; }); + vec2Type[sol::meta_function::division] = [](const Vec2& a, float c) { return a / c; }; + vec2Type["dot"] = [](const Vec2& a, const Vec2& b) { return a * b; }; + vec2Type["length"] = &Vec2::length; + vec2Type["length2"] = &Vec2::length2; + vec2Type["normalize"] = [](const Vec2& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec2f(), 0.f); + return std::make_tuple(Vec2(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; vec2Type["rotate"] = &Misc::rotateVec2f; - // Lua bindings for osg::Vec3f - util["vector3"] = [](float x, float y, float z) { return osg::Vec3f(x, y, z); }; - sol::usertype vec3Type = lua.new_usertype("Vec3"); - vec3Type["x"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.x(); } ); - vec3Type["y"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.y(); } ); - vec3Type["z"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const osg::Vec3f& v) { + // Lua bindings for Vec3 + util["vector3"] = [](float x, float y, float z) { return Vec3(x, y, z); }; + sol::usertype vec3Type = lua.new_usertype("Vec3"); + vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); + vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); + vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); + vec3Type[sol::meta_function::to_string] = [](const Vec3& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; return ss.str(); }; - vec3Type[sol::meta_function::unary_minus] = [](const osg::Vec3f& a) { return -a; }; - vec3Type[sol::meta_function::addition] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a + b; }; - vec3Type[sol::meta_function::subtraction] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a - b; }; - vec3Type[sol::meta_function::equal_to] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a == b; }; + vec3Type[sol::meta_function::unary_minus] = [](const Vec3& a) { return -a; }; + vec3Type[sol::meta_function::addition] = [](const Vec3& a, const Vec3& b) { return a + b; }; + vec3Type[sol::meta_function::subtraction] = [](const Vec3& a, const Vec3& b) { return a - b; }; + vec3Type[sol::meta_function::equal_to] = [](const Vec3& a, const Vec3& b) { return a == b; }; vec3Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec3f& a, float c) { return a * c; }, - [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }); - vec3Type[sol::meta_function::division] = [](const osg::Vec3f& a, float c) { return a / c; }; - vec3Type[sol::meta_function::involution] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["dot"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }; - vec3Type["cross"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["length"] = &osg::Vec3f::length; - vec3Type["length2"] = &osg::Vec3f::length2; - vec3Type["normalize"] = [](const osg::Vec3f& v) { + [](const Vec3& a, float c) { return a * c; }, + [](const Vec3& a, const Vec3& b) { return a * b; }); + vec3Type[sol::meta_function::division] = [](const Vec3& a, float c) { return a / c; }; + vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["dot"] = [](const Vec3& a, const Vec3& b) { return a * b; }; + vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["length"] = &Vec3::length; + vec3Type["length2"] = &Vec3::length2; + vec3Type["normalize"] = [](const Vec3& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec3f(), 0.f); + return std::make_tuple(Vec3(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; + // Lua bindings for Transform + sol::usertype transMType = lua.new_usertype("TransformM"); + sol::usertype transQType = lua.new_usertype("TransformQ"); + sol::table transforms(lua, sol::create); + util["transform"] = LuaUtil::makeReadOnly(transforms); + + transforms["identity"] = sol::make_object(lua, TransformM{osg::Matrixf::identity()}); + transforms["move"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::translate(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::translate(x, y, z)}; }); + transforms["scale"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::scale(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::scale(x, y, z)}; }); + transforms["rotate"] = [](float angle, const Vec3& axis) { return TransformQ{osg::Quat(angle, axis)}; }; + transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(1, 0, 0))}; }; + transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 1, 0))}; }; + transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, 1))}; }; + + transMType[sol::meta_function::multiplication] = sol::overload( + [](const TransformM& a, const Vec3& b) { return a.mM.preMult(b); }, + [](const TransformM& a, const TransformM& b) { return TransformM{b.mM * a.mM}; }, + [](const TransformM& a, const TransformQ& b) + { + TransformM res{a.mM}; + res.mM.preMultRotate(b.mQ); + return res; + }); + transMType[sol::meta_function::to_string] = [](const TransformM& m) + { + osg::Vec3f trans, scale; + osg::Quat rotation, so; + m.mM.decompose(trans, rotation, scale, so); + osg::Quat::value_type rot_angle, so_angle; + osg::Vec3f rot_axis, so_axis; + rotation.getRotate(rot_angle, rot_axis); + so.getRotate(so_angle, so_axis); + std::stringstream ss; + ss << "TransformM{ "; + if (trans.length2() > 0) + ss << "move(" << trans.x() << ", " << trans.y() << ", " << trans.z() << ") "; + if (rot_angle != 0) + ss << "rotation(angle=" << rot_angle << ", axis=(" + << rot_axis.x() << ", " << rot_axis.y() << ", " << rot_axis.z() << ")) "; + if (scale.x() != 1 || scale.y() != 1 || scale.z() != 1) + ss << "scale(" << scale.x() << ", " << scale.y() << ", " << scale.z() << ") "; + if (so_angle != 0) + ss << "rotation(angle=" << so_angle << ", axis=(" + << so_axis.x() << ", " << so_axis.y() << ", " << so_axis.z() << ")) "; + ss << "}"; + return ss.str(); + }; + transMType["inverse"] = [](const TransformM& m) + { + TransformM res; + if (!res.mM.invert_4x3(m.mM)) + throw std::runtime_error("This Transform is not invertible"); + return res; + }; + + transQType[sol::meta_function::multiplication] = sol::overload( + [](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, + [](const TransformQ& a, const TransformQ& b) { return TransformQ{b.mQ * a.mQ}; }, + [](const TransformQ& a, const TransformM& b) + { + TransformM res{b}; + res.mM.postMultRotate(a.mQ); + return res; + }); + transQType[sol::meta_function::to_string] = [](const TransformQ& q) + { + osg::Quat::value_type angle; + osg::Vec3f axis; + q.mQ.getRotate(angle, axis); + std::stringstream ss; + ss << "TransformQ{ rotation(angle=" << angle << ", axis=(" + << axis.x() << ", " << axis.y() << ", " << axis.z() << ")) }"; + return ss.str(); + }; + transQType["inverse"] = [](const TransformQ& q) { return TransformQ{q.mQ.inverse()}; }; + // Utility functions util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp index 9e73723561..d26bfdb027 100644 --- a/components/lua/utilpackage.hpp +++ b/components/lua/utilpackage.hpp @@ -1,11 +1,24 @@ #ifndef COMPONENTS_LUA_UTILPACKAGE_H #define COMPONENTS_LUA_UTILPACKAGE_H -#include // missing from sol/sol.hpp +#include +#include +#include + #include namespace LuaUtil { + using Vec2 = osg::Vec2f; + using Vec3 = osg::Vec3f; + + // For performance reasons "Transform" is implemented as 2 types with the same interface. + // Transform supports only composition, inversion, and applying to a 3d vector. + struct TransformM { osg::Matrixf mM; }; + struct TransformQ { osg::Quat mQ; }; + + inline TransformM asTransform(const osg::Matrixf& m) { return {m}; } + inline TransformQ asTransform(const osg::Quat& q) { return {q}; } sol::table initUtilPackage(sol::state&); diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 7d31ced408..84e640ff40 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -6,7 +6,7 @@ ------------------------------------------------------------------------------- --- Limits given value to the interval [`from`, `to`] +-- Limits given value to the interval [`from`, `to`]. -- @function [parent=#util] clamp -- @param #number value -- @param #number from @@ -14,7 +14,7 @@ -- @return #number min(max(value, from), to) ------------------------------------------------------------------------------- --- Adds `2pi*k` and puts the angle in range `[-pi, pi]` +-- Adds `2pi*k` and puts the angle in range `[-pi, pi]`. -- @function [parent=#util] normalizeAngle -- @param #number angle Angle in radians -- @return #number Angle in range `[-pi, pi]` @@ -48,13 +48,13 @@ -- @return #Vector2. ------------------------------------------------------------------------------- --- Length of the vector +-- Length of the vector. -- @function [parent=#Vector2] length -- @param self -- @return #number ------------------------------------------------------------------------------- --- Square of the length of the vector +-- Square of the length of the vector. -- @function [parent=#Vector2] length2 -- @param self -- @return #number @@ -146,5 +146,84 @@ -- @param #Vector3 v -- @return #Vector3 +------------------------------------------------------------------------------- +-- @type Transform + +------------------------------------------------------------------------------- +-- Returns the inverse transform. +-- @function [parent=#Transform] inverse +-- @param self +-- @return #Transform. + +------------------------------------------------------------------------------- +-- @type TRANSFORM +-- @field [parent=#TRANSFORM] #Transform identity Empty transform. + +------------------------------------------------------------------------------- +-- Movement by given vector. +-- @function [parent=#TRANSFORM] move +-- @param #Vector3 offset. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.move(x, y, z) +-- util.transform.move(util.vector3(x, y, z)) + +------------------------------------------------------------------------------- +-- Scale transform. +-- @function [parent=#TRANSFORM] scale +-- @param #number scaleX. +-- @param #number scaleY. +-- @param #number scaleZ. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.scale(x, y, z) +-- util.transform.scale(util.vector3(x, y, z)) + + +------------------------------------------------------------------------------- +-- Rotation (any axis). +-- @function [parent=#TRANSFORM] rotate +-- @param #number angle +-- @param #Vector3 axis. +-- @return #Transform. + +------------------------------------------------------------------------------- +-- X-axis rotation. +-- @function [parent=#TRANSFORM] rotateX +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Y-axis rotation. +-- @function [parent=#TRANSFORM] rotateY +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Z-axis rotation. +-- @function [parent=#TRANSFORM] rotateZ +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- 3D transforms (scale/move/rotate) that can be applied to 3D vectors. +-- Several transforms can be combined and applied to a vector using multiplication. +-- Combined transforms apply in reverse order (from right to left). +-- @field [parent=#util] #TRANSFORM transform +-- @usage +-- local util = require('openmw.util') +-- local trans = util.transform +-- local fromActorSpace = trans.move(actor.position) * trans.rotateZ(actor.rotation.z) +-- +-- -- rotation is applied first, movement is second +-- local posBehindActor = fromActorSpace * util.vector3(0, -100, 0) +-- +-- -- equivalent to trans.rotateZ(-actor.rotation.z) * trans.move(-actor.position) +-- local toActorSpace = fromActorSpace:inverse() +-- local relativeTargetPos = toActorSpace * target.position +-- local deltaAngle = math.atan2(relativeTargetPos.y, relativeTargetPos.x) + return nil