1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 15:29:55 +00:00

Lua bindings for Colours

This commit is contained in:
uramer 2022-01-17 22:35:06 +00:00 committed by Petr Mikheev
parent 4e93716584
commit d1d8f058ac
9 changed files with 390 additions and 39 deletions

View file

@ -5,11 +5,13 @@
#include <osg/Quat>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <components/lua/serialization.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/endianness.hpp>
#include <components/misc/color.hpp>
#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<osg::Vec3f>());
EXPECT_EQ(value.as<osg::Vec3f>(), 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<osg::Vec4f>());
EXPECT_EQ(value.as<osg::Vec4f>(), 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<Misc::Color>());
EXPECT_EQ(value.as<Misc::Color>(), color);
}
}
TEST(LuaSerializationTest, Transform) {

View file

@ -70,6 +70,52 @@ namespace
EXPECT_FLOAT_EQ(get<float>(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<float>(lua, "v.x"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "v.y"), 12);
EXPECT_FLOAT_EQ(get<float>(lua, "v.z"), 13);
EXPECT_FLOAT_EQ(get<float>(lua, "v.w"), 15);
EXPECT_EQ(get<std::string>(lua, "tostring(v)"), "(5, 12, 13, 15)");
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector4(4, 0, 0, 3):length()"), 5);
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector4(4, 0, 0, 3):length2()"), 25);
EXPECT_FALSE(get<bool>(lua, "util.vector4(1, 2, 3, 4) == util.vector4(1, 3, 2, 4)"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4) + util.vector4(2, 5, 1, 2) == util.vector4(3, 7, 4, 6)"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4) - util.vector4(2, 5, 1, 7) == -util.vector4(1, 3, -2, 3)"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4) == util.vector4(2, 4, 6, 8) / 2"));
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4) * 2 == util.vector4(2, 4, 6, 8)"));
EXPECT_FLOAT_EQ(get<float>(lua, "util.vector4(3, 2, 1, 4) * v"), 5 * 3 + 12 * 2 + 13 * 1 + 15 * 4);
EXPECT_FLOAT_EQ(get<float>(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<float>(lua, "len"), 5);
EXPECT_TRUE(get<bool>(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<float>(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<std::string>(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)");
lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)");
EXPECT_EQ(get<std::string>(lua, "tostring(blue)"), "(0, 1, 0, 1)");
lua.safe_script("red = util.color.hex('ff0000')");
EXPECT_EQ(get<std::string>(lua, "tostring(red)"), "(1, 0, 0, 1)");
lua.safe_script("green = util.color.hex('00FF00')");
EXPECT_EQ(get<std::string>(lua, "tostring(green)"), "(0, 1, 0, 1)");
lua.safe_script("darkRed = util.color.hex('a01112')");
EXPECT_EQ(get<std::string>(lua, "darkRed:asHex()"), "a01112");
EXPECT_TRUE(get<bool>(lua, "green:asRgba() == util.vector4(0, 1, 0, 1)"));
EXPECT_TRUE(get<bool>(lua, "red:asRgb() == util.vector3(1, 0, 0)"));
}
TEST(LuaUtilPackageTest, Transform)
{
sol::state lua;

View file

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

View file

@ -6,6 +6,7 @@
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <components/misc/color.hpp>
#include <components/misc/endianness.hpp>
#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<double>(out, quat[i]);
return;
}
if (data.is<osg::Vec4f>())
{
appendType(out, SerializedType::VEC4);
osg::Vec4f v = data.as<osg::Vec4f>();
appendValue<double>(out, v.x());
appendValue<double>(out, v.y());
appendValue<double>(out, v.z());
appendValue<double>(out, v.w());
return;
}
if (data.is<Misc::Color>())
{
appendType(out, SerializedType::COLOR);
Misc::Color v = data.as<Misc::Color> ();
appendValue<float>(out, v.r());
appendValue<float>(out, v.g());
appendValue<float>(out, v.b());
appendValue<float>(out, v.a());
return;
}
if (customSerializer && customSerializer->serialize(out, data))
return;
else
@ -271,6 +294,24 @@ namespace LuaUtil
sol::stack::push<TransformQ>(lua, asTransform(q));
return;
}
case SerializedType::VEC4:
{
float x = getValue<double>(binaryData);
float y = getValue<double>(binaryData);
float z = getValue<double>(binaryData);
float w = getValue<double>(binaryData);
sol::stack::push<osg::Vec4f>(lua, osg::Vec4f(x, y, z, w));
return;
}
case SerializedType::COLOR:
{
float r = getValue<float>(binaryData);
float g = getValue<float>(binaryData);
float b = getValue<float>(binaryData);
float a = getValue<float>(binaryData);
sol::stack::push<Misc::Color>(lua, Misc::Color(r, g, b, a));
return;
}
}
throw std::runtime_error("Unknown type in serialized data: " + std::to_string(type));
}

View file

@ -2,8 +2,10 @@
#include <algorithm>
#include <sstream>
#include <array>
#include <components/misc/mathutil.hpp>
#include <components/misc/color.hpp>
#include "luastate.hpp"
@ -15,6 +17,12 @@ namespace sol
template <>
struct is_automagical<LuaUtil::Vec3> : std::false_type {};
template <>
struct is_automagical<LuaUtil::Vec4> : std::false_type {};
template <>
struct is_automagical<Misc::Color> : std::false_type {};
template <>
struct is_automagical<LuaUtil::TransformM> : std::false_type {};
@ -24,6 +32,31 @@ namespace sol
namespace LuaUtil
{
namespace {
template<typename T>
void addVectorMethods(sol::usertype<T>& 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<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) {
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<Vec2>(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<Vec3>(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<Vec4> vec4Type = lua.new_usertype<Vec4>("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<Vec4>(vec4Type);
// Lua bindings for Color
sol::usertype<Misc::Color> colorType = lua.new_usertype<Misc::Color>("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<TransformM> transMType = lua.new_usertype<TransformM>("TransformM");

View file

@ -3,6 +3,7 @@
#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/Matrix>
#include <sol/sol.hpp>
@ -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.

59
components/misc/color.cpp Normal file
View file

@ -0,0 +1,59 @@
#include "color.hpp"
#include <charconv>
#include <array>
#include <sstream>
#include <algorithm>
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<float, 3> 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<float, 3> rgb = { mR, mG, mB };
for (size_t i = 0; i < rgb.size(); i++)
{
int b = static_cast<int>(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;
}
}

34
components/misc/color.hpp Normal file
View file

@ -0,0 +1,34 @@
#ifndef COMPONENTS_MISC_COLOR
#define COMPONENTS_MISC_COLOR
#include <string>
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

View file

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