From d1d8f058acf1e8494e114829a9b5e398a0a92efb Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 17 Jan 2022 22:35:06 +0000 Subject: [PATCH] Lua bindings for Colours --- .../lua/test_serialization.cpp | 24 ++++ .../lua/test_utilpackage.cpp | 46 +++++++ components/CMakeLists.txt | 2 +- components/lua/serialization.cpp | 41 +++++++ components/lua/utilpackage.cpp | 108 +++++++++++------ components/lua/utilpackage.hpp | 2 + components/misc/color.cpp | 59 +++++++++ components/misc/color.hpp | 34 ++++++ files/lua_api/openmw/util.lua | 113 +++++++++++++++++- 9 files changed, 390 insertions(+), 39 deletions(-) create mode 100644 components/misc/color.cpp create mode 100644 components/misc/color.hpp diff --git a/apps/openmw_test_suite/lua/test_serialization.cpp b/apps/openmw_test_suite/lua/test_serialization.cpp index 1d664b06a4..5a6d5b7e51 100644 --- a/apps/openmw_test_suite/lua/test_serialization.cpp +++ b/apps/openmw_test_suite/lua/test_serialization.cpp @@ -5,11 +5,13 @@ #include #include #include +#include #include #include #include +#include #include "testing_util.hpp" @@ -90,6 +92,7 @@ namespace sol::state lua; osg::Vec2f vec2(1, 2); osg::Vec3f vec3(1, 2, 3); + osg::Vec4f vec4(1, 2, 3, 4); { std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec2)); @@ -105,6 +108,27 @@ namespace ASSERT_TRUE(value.is()); EXPECT_EQ(value.as(), vec3); } + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, vec4)); + EXPECT_EQ(serialized.size(), 34); // version, type, 4x double + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), vec4); + } + } + + TEST(LuaSerializationTest, Color) + { + sol::state lua; + Misc::Color color(1, 1, 1, 1); + + { + std::string serialized = LuaUtil::serialize(sol::make_object(lua, color)); + EXPECT_EQ(serialized.size(), 18); // version, type, 4x float + sol::object value = LuaUtil::deserialize(lua, serialized); + ASSERT_TRUE(value.is()); + EXPECT_EQ(value.as(), color); + } } TEST(LuaSerializationTest, Transform) { diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index 953d5f50d3..ba19cca383 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -70,6 +70,52 @@ namespace EXPECT_FLOAT_EQ(get(lua, "len"), 0); } + TEST(LuaUtilPackageTest, Vector4) + { + 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.vector4(5, 12, 13, 15)"); + 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_FLOAT_EQ(get(lua, "v.w"), 15); + EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13, 15)"); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(4, 0, 0, 3):length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(1, 3, 2, 4)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) + util.vector4(2, 5, 1, 2) == util.vector4(3, 7, 4, 6)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) - util.vector4(2, 5, 1, 7) == -util.vector4(1, 3, -2, 3)")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) == util.vector4(2, 4, 6, 8) / 2")); + EXPECT_TRUE(get(lua, "util.vector4(1, 2, 3, 4) * 2 == util.vector4(2, 4, 6, 8)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4) * v"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); + EXPECT_FLOAT_EQ(get(lua, "util.vector4(3, 2, 1, 4):dot(v)"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4); + lua.safe_script("v2, len = util.vector4(3, 0, 0, 4):normalize()"); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)")); + lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()"); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); + } + + TEST(LuaUtilPackageTest, Color) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)"); + EXPECT_EQ(get(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)"); + lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)"); + EXPECT_EQ(get(lua, "tostring(blue)"), "(0, 1, 0, 1)"); + lua.safe_script("red = util.color.hex('ff0000')"); + EXPECT_EQ(get(lua, "tostring(red)"), "(1, 0, 0, 1)"); + lua.safe_script("green = util.color.hex('00FF00')"); + EXPECT_EQ(get(lua, "tostring(green)"), "(0, 1, 0, 1)"); + lua.safe_script("darkRed = util.color.hex('a01112')"); + EXPECT_EQ(get(lua, "darkRed:asHex()"), "a01112"); + EXPECT_TRUE(get(lua, "green:asRgba() == util.vector4(0, 1, 0, 1)")); + EXPECT_TRUE(get(lua, "red:asRgb() == util.vector3(1, 0, 0)")); + } + TEST(LuaUtilPackageTest, Transform) { sol::state lua; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f7382db1c7..59f1e331e4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,7 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression osguservalues errorMarker + compression osguservalues errorMarker color ) add_component_dir (debug diff --git a/components/lua/serialization.cpp b/components/lua/serialization.cpp index e8f66c698c..d0bbbc9dc5 100644 --- a/components/lua/serialization.cpp +++ b/components/lua/serialization.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "luastate.hpp" @@ -28,6 +29,8 @@ namespace LuaUtil VEC3 = 0x11, TRANSFORM_M = 0x12, TRANSFORM_Q = 0x13, + VEC4 = 0x14, + COLOR = 0x15, // All values should be lesser than 0x20 (SHORT_STRING_FLAG). }; @@ -129,6 +132,26 @@ namespace LuaUtil appendValue(out, quat[i]); return; } + if (data.is()) + { + appendType(out, SerializedType::VEC4); + osg::Vec4f v = data.as(); + appendValue(out, v.x()); + appendValue(out, v.y()); + appendValue(out, v.z()); + appendValue(out, v.w()); + return; + } + if (data.is()) + { + appendType(out, SerializedType::COLOR); + Misc::Color v = data.as (); + appendValue(out, v.r()); + appendValue(out, v.g()); + appendValue(out, v.b()); + appendValue(out, v.a()); + return; + } if (customSerializer && customSerializer->serialize(out, data)) return; else @@ -271,6 +294,24 @@ namespace LuaUtil sol::stack::push(lua, asTransform(q)); return; } + case SerializedType::VEC4: + { + float x = getValue(binaryData); + float y = getValue(binaryData); + float z = getValue(binaryData); + float w = getValue(binaryData); + sol::stack::push(lua, osg::Vec4f(x, y, z, w)); + return; + } + case SerializedType::COLOR: + { + float r = getValue(binaryData); + float g = getValue(binaryData); + float b = getValue(binaryData); + float a = getValue(binaryData); + sol::stack::push(lua, Misc::Color(r, g, b, a)); + return; + } } throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type)); } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index abb680a6bc..c51b00a7d5 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -2,8 +2,10 @@ #include #include +#include #include +#include #include "luastate.hpp" @@ -15,6 +17,12 @@ namespace sol template <> struct is_automagical : std::false_type {}; + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + template <> struct is_automagical : std::false_type {}; @@ -24,6 +32,31 @@ namespace sol namespace LuaUtil { + namespace { + template + void addVectorMethods(sol::usertype& vectorType) + { + vectorType[sol::meta_function::unary_minus] = [](const T& a) { return -a; }; + vectorType[sol::meta_function::addition] = [](const T& a, const T& b) { return a + b; }; + vectorType[sol::meta_function::subtraction] = [](const T& a, const T& b) { return a - b; }; + vectorType[sol::meta_function::equal_to] = [](const T& a, const T& b) { return a == b; }; + vectorType[sol::meta_function::multiplication] = sol::overload( + [](const T& a, float c) { return a * c; }, + [](const T& a, const T& b) { return a * b; }); + vectorType[sol::meta_function::division] = [](const T& a, float c) { return a / c; }; + vectorType["dot"] = [](const T& a, const T b) { return a * b; }; + vectorType["length"] = &T::length; + vectorType["length2"] = &T::length2; + vectorType["normalize"] = [](const T& v) + { + float len = v.length(); + if (len == 0) + return std::make_tuple(T(), 0.f); + else + return std::make_tuple(v * (1.f / len), len); + }; + } + } sol::table initUtilPackage(sol::state& lua) { @@ -34,29 +67,13 @@ namespace LuaUtil 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) { + 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 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 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(Vec2(), 0.f); - else - return std::make_tuple(v * (1.f / len), len); - }; + addVectorMethods(vec2Type); vec2Type["rotate"] = &Misc::rotateVec2f; // Lua bindings for Vec3 @@ -65,31 +82,48 @@ namespace LuaUtil 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) { + 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 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 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; }; + addVectorMethods(vec3Type); 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(Vec3(), 0.f); - else - return std::make_tuple(v * (1.f / len), len); + + // Lua bindings for Vec4 + util["vector4"] = [](float x, float y, float z, float w) + { return Vec4(x, y, z, w); }; + sol::usertype vec4Type = lua.new_usertype("Vec4"); + vec4Type["x"] = sol::readonly_property([](const Vec4& v) -> float { return v.x(); }); + vec4Type["y"] = sol::readonly_property([](const Vec4& v) -> float { return v.y(); }); + vec4Type["z"] = sol::readonly_property([](const Vec4& v) -> float { return v.z(); }); + vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); + vec4Type[sol::meta_function::to_string] = [](const Vec4& v) + { + std::stringstream ss; + ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ")"; + return ss.str(); }; + addVectorMethods(vec4Type); + + // Lua bindings for Color + sol::usertype colorType = lua.new_usertype("Color"); + colorType["r"] = [](const Misc::Color& c) { return c.r(); }; + colorType["g"] = [](const Misc::Color& c) { return c.g(); }; + colorType["b"] = [](const Misc::Color& c) { return c.b(); }; + colorType["a"] = [](const Misc::Color& c) { return c.a(); }; + colorType[sol::meta_function::to_string] = [](const Misc::Color& c) { return c.toString(); }; + colorType["asRgba"] = [](const Misc::Color& c) { return Vec4(c.r(), c.g(), c.b(), c.a()); }; + colorType["asRgb"] = [](const Misc::Color& c) { return Vec3(c.r(), c.g(), c.b()); }; + colorType["asHex"] = [](const Misc::Color& c) { return c.toHex(); }; + + sol::table color(lua, sol::create); + color["rgba"] = [](float r, float g, float b, float a) { return Misc::Color(r, g, b, a); }; + color["rgb"] = [](float r, float g, float b) { return Misc::Color(r, g, b, 1); }; + color["hex"] = [](std::string_view hex) { return Misc::Color::fromHex(hex); }; + util["color"] = LuaUtil::makeReadOnly(color); // Lua bindings for Transform sol::usertype transMType = lua.new_usertype("TransformM"); diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp index d26bfdb027..a647b682af 100644 --- a/components/lua/utilpackage.hpp +++ b/components/lua/utilpackage.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -11,6 +12,7 @@ namespace LuaUtil { using Vec2 = osg::Vec2f; using Vec3 = osg::Vec3f; + using Vec4 = osg::Vec4f; // For performance reasons "Transform" is implemented as 2 types with the same interface. // Transform supports only composition, inversion, and applying to a 3d vector. diff --git a/components/misc/color.cpp b/components/misc/color.cpp new file mode 100644 index 0000000000..edf3435428 --- /dev/null +++ b/components/misc/color.cpp @@ -0,0 +1,59 @@ +#include "color.hpp" + +#include +#include +#include +#include + +namespace Misc +{ + Color::Color(float r, float g, float b, float a) + : mR(std::clamp(r, 0.f, 1.f)) + , mG(std::clamp(g, 0.f, 1.f)) + , mB(std::clamp(b, 0.f, 1.f)) + , mA(std::clamp(a, 0.f, 1.f)) + {} + + std::string Color::toString() const + { + std::ostringstream ss; + ss << "(" << r() << ", " << g() << ", " << b() << ", " << a() << ')'; + return ss.str(); + } + + Color Color::fromHex(std::string_view hex) + { + if (hex.size() != 6) + throw std::logic_error(std::string("Invalid hex color: ") += hex); + std::array rgb; + for (size_t i = 0; i < rgb.size(); i++) + { + auto sub = hex.substr(i * 2, 2); + int v; + auto [_, ec] = std::from_chars(sub.data(), sub.data() + sub.size(), v, 16); + if (ec != std::errc()) + throw std::logic_error(std::string("Invalid hex color: ") += hex); + rgb[i] = v / 255.0f; + } + return Color(rgb[0], rgb[1], rgb[2], 1); + } + + std::string Color::toHex() const + { + std::string result(6, '0'); + std::array rgb = { mR, mG, mB }; + for (size_t i = 0; i < rgb.size(); i++) + { + int b = static_cast(rgb[i] * 255.0f); + auto [_, ec] = std::to_chars(result.data() + i * 2, result.data() + (i + 1) * 2, b, 16); + if (ec != std::errc()) + throw std::logic_error("Error when converting number to base 16"); + } + return result; + } + + bool operator==(const Color& l, const Color& r) + { + return l.mR == r.mR && l.mG == r.mG && l.mB == r.mB && l.mA == r.mA; + } +} diff --git a/components/misc/color.hpp b/components/misc/color.hpp new file mode 100644 index 0000000000..932d261fad --- /dev/null +++ b/components/misc/color.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTS_MISC_COLOR +#define COMPONENTS_MISC_COLOR + +#include + +namespace Misc +{ + class Color + { + public: + Color(float r, float g, float b, float a); + + float r() const { return mR; } + float g() const { return mG; } + float b() const { return mB; } + float a() const { return mA; } + + std::string toString() const; + + static Color fromHex(std::string_view hex); + + std::string toHex() const; + + friend bool operator==(const Color& l, const Color& r); + + private: + float mR; + float mG; + float mB; + float mA; + }; +} + +#endif // !COMPONENTS_MISC_COLOR diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 22986f09a6..ea7d037daa 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -133,7 +133,7 @@ ------------------------------------------------------------------------------- -- Normalizes vector. -- Returns two values: normalized vector and the length of the original vector. --- It doesn't change the original vector. +-- It doesn't change the original vector. -- @function [parent=#Vector3] normalize -- @param self -- @return #Vector3, #number @@ -152,6 +152,117 @@ -- @param #Vector3 v -- @return #Vector3 + +------------------------------------------------------------------------------- +-- Immutable 4D vector. +-- @type Vector4 +-- @field #number x +-- @field #number y +-- @field #number z +-- @field #number w +-- @usage +-- v = util.vector4(3, 4, 5, 6) +-- v.x, v.y, v.z, v.w -- 3.0, 4.0, 5.0, 6.0 +-- str(v) -- "(3.0, 4.0, 5.0, 6.0)" +-- v:length() -- length +-- v:length2() -- square of the length +-- v:normalize() -- normalized vector +-- v1:dot(v2) -- dot product (returns a number) +-- v1 * v2 -- dot product (returns a number) +-- v1 + v2 -- vector addition +-- v1 - v2 -- vector subtraction +-- v1 * x -- multiplication by a number +-- v1 / x -- division by a number + +------------------------------------------------------------------------------- +-- Creates a new 4D vector. Vectors are immutable and can not be changed after creation. +-- @function [parent=#util] vector4 +-- @param #number x. +-- @param #number y. +-- @param #number z. +-- @param #number w. +-- @return #Vector4. + +------------------------------------------------------------------------------- +-- Length of the vector +-- @function [parent=#Vector4] length +-- @param self +-- @return #number + +------------------------------------------------------------------------------- +-- Square of the length of the vector +-- @function [parent=#Vector4] length2 +-- @param self +-- @return #number + +------------------------------------------------------------------------------- +-- Normalizes vector. +-- Returns two values: normalized vector and the length of the original vector. +-- It doesn't change the original vector. +-- @function [parent=#Vector4] normalize +-- @param self +-- @return #Vector4, #number + +------------------------------------------------------------------------------- +-- Dot product. +-- @function [parent=#Vector4] dot +-- @param self +-- @param #Vector4 v +-- @return #number + +------------------------------------------------------------------------------- +-- Color in RGBA format. All of the component values are in the range [0, 1]. +-- @type Color +-- @field #number r Red component +-- @field #number g Green component +-- @field #number b Blue component +-- @field #number a Alpha (transparency) component + +------------------------------------------------------------------------------- +-- Returns a Vector4 with RGBA components of the Color. +-- @function [parent=#Color] asRgba +-- @param self +-- @return #Vector4 + +------------------------------------------------------------------------------- +-- Returns a Vector3 with RGB components of the Color. +-- @function [parent=#Color] asRgb +-- @param self +-- @return #Vector3 + +------------------------------------------------------------------------------- +-- Converts the color into a HEX string. +-- @function [parent=#Color] asHex +-- @param self +-- @return #string + +------------------------------------------------------------------------------- +-- @type COLOR +-- @field [parent=#util] #COLOR color Methods for creating #Color values from different formats. + +------------------------------------------------------------------------------- +-- Creates a Color from RGBA format +-- @function [parent=#COLOR] rgba +-- @param #number r +-- @param #number g +-- @param #number b +-- @param #number a +-- @return #Color + +------------------------------------------------------------------------------- +-- Creates a Color from RGB format. Equivalent to calling util.rgba with a = 1. +-- @function [parent=#COLOR] rgb +-- @param #number r +-- @param #number g +-- @param #number b +-- @return #Color + +------------------------------------------------------------------------------- +-- Parses a hex color string into a Color. +-- @function [parent=#COLOR] hex +-- @param #string hex A hex color string in RRGGBB format (e. g. "ff0000"). +-- @return #Color + ------------------------------------------------------------------------------- -- @type Transform