Merge branch 'lua_transform' into 'master'

3D transforms in Lua

See merge request OpenMW/openmw!1235
pull/3153/head
psi29a 3 years ago
commit c3f5e8f968

@ -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<std::string>();

@ -10,30 +10,41 @@ namespace
{
using namespace testing;
template <typename T>
T get(sol::state& lua, std::string luaCode)
{
return lua.safe_script("return " + luaCode).get<T>();
}
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<float>(), 3);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 4);
EXPECT_EQ(lua.safe_script("return tostring(v)").get<std::string>(), "(3, 4)");
EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get<float>(), 25);
EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get<bool>());
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get<float>(), 17);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get<float>(), 17);
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 3);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 4);
EXPECT_EQ(get<std::string>(lua, "tostring(v)"), "(3, 4)");
EXPECT_FLOAT_EQ(get<float>(lua, "v:length()"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v:length2()"), 25);
EXPECT_FALSE(get<bool>(lua, "util.vector2(1, 2) == util.vector2(1, 3)"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2) == util.vector2(2, 4) / 2"));
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2) * 2 == util.vector2(2, 4)"));
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector2(3, 2) * v"), 17);
EXPECT_FLOAT_EQ(get<float>(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<float>(), 5);
EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get<bool>());
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 5);
EXPECT_TRUE(get<bool>(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<float>(), 0);
EXPECT_FLOAT_EQ(get<float>(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<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 12);
EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get<float>(), 13);
EXPECT_EQ(lua.safe_script("return tostring(v)").get<std::string>(), "(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<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get<float>(), 25);
EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get<bool>());
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get<float>(), 5*3 + 12*2 + 13*1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get<float>(), 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<bool>());
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
EXPECT_FLOAT_EQ(get<float>(lua, "v.z"), 13);
EXPECT_EQ(get<std::string>(lua, "tostring(v)"), "(5, 12, 13)");
EXPECT_EQ(getAsString(lua, "v"), "(5, 12, 13)");
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector3(4, 0, 3):length()"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector3(4, 0, 3):length2()"), 25);
EXPECT_FALSE(get<bool>(lua, "util.vector3(1, 2, 3) == util.vector3(1, 3, 2)"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2"));
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)"));
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector3(3, 2, 1) * v"), 5*3 + 12*2 + 13*1);
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector3(3, 2, 1):dot(v)"), 5*3 + 12*2 + 13*1);
EXPECT_TRUE(get<bool>(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<float>(), 5);
EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get<bool>());
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 5);
EXPECT_TRUE(get<bool>(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<float>(), 0);
EXPECT_FLOAT_EQ(get<float>(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<float>(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6);
EXPECT_LT(get<float>(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6);
EXPECT_LT(get<float>(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<float>(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6);
EXPECT_LT(get<float>(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<float>(lua, "(rz_move_rx * v(1, 2, 3) - v(0, 1, 2)):length()"), 1e-6);
EXPECT_LT(get<float>(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<float>(), -0.5);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 0.86602539);
EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get<float>(), 0.1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get<float>(), 0.1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get<float>(), 0);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get<float>(), 1.5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.x"), -0.5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 0.86602539);
EXPECT_FLOAT_EQ(get<float>(lua, "util.normalizeAngle(math.pi * 10 + 0.1)"), 0.1);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(0.1, 0, 1.5)"), 0.1);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(-0.1, 0, 1.5)"), 0);
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(2.1, 0, 1.5)"), 1.5);
}
}

@ -3,17 +3,23 @@
#include <algorithm>
#include <sstream>
#include <osg/Vec3f>
#include <components/misc/mathutil.hpp>
#include "luastate.hpp"
namespace sol
{
template <>
struct is_automagical<osg::Vec2f> : std::false_type {};
struct is_automagical<LuaUtil::Vec2> : std::false_type {};
template <>
struct is_automagical<LuaUtil::Vec3> : std::false_type {};
template <>
struct is_automagical<LuaUtil::TransformM> : std::false_type {};
template <>
struct is_automagical<osg::Vec3f> : std::false_type {};
struct is_automagical<LuaUtil::TransformQ> : 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<osg::Vec2f> vec2Type = lua.new_usertype<osg::Vec2f>("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<Vec2> vec2Type = lua.new_usertype<Vec2>("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<osg::Vec3f> vec3Type = lua.new_usertype<osg::Vec3f>("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<Vec3> vec3Type = lua.new_usertype<Vec3>("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<TransformM> transMType = lua.new_usertype<TransformM>("TransformM");
sol::usertype<TransformQ> transQType = lua.new_usertype<TransformQ>("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<float>` causes error 'AddressSanitizer: stack-use-after-scope'

@ -1,11 +1,24 @@
#ifndef COMPONENTS_LUA_UTILPACKAGE_H
#define COMPONENTS_LUA_UTILPACKAGE_H
#include <limits> // missing from sol/sol.hpp
#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Matrix>
#include <sol/sol.hpp>
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&);

@ -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

Loading…
Cancel
Save