lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi John,

You don't necessarily need to create a new base type for this.  I have attached an example of transparently wrapping the "glm" library to provide a set of vector and matrix types in Lua including all of the operator overloads.  It was just a quick proof of concept, so I'm sure there's some room for improvement.  I used sol2 to do the wrapping, so doing it using the basic C API would be a bit more verbose, but equally achievable.

Kind regards,
Roger

> -----Original Message-----
> From: John W <jwdevel@gmail.com>
> Sent: Thursday, July 27, 2023 6:13 PM
> To: Lua mailing list <lua-l@lists.lua.org>
> Subject: Adding custom bulit-in type
> 
> I'm sure someone has done this before, but so far I haven't found any clear
> examples from the mailing archives or online. Maybe my search terms are off.
> 
> Basically, I want to patch the Lua interpreter to support an additional type
> (alongside 'number', 'table', etc).
> 
> From reading the code, it seems that I need to add an entry to the "Value"
> union in lobject.h, and follow the various implications of that.
> 
> Some brief details:
> 
> The type I'm adding is a "vec2" (x,y pair), which conveniently fits into the
> same space as a 64-bit integer in my case, so it does not need to be a
> "reference type". No heap allocation needed. Also, I'm not planning on
> changing the parser, if I can avoid it - not adding any "vec2" keyword or
> anything, just a global "vec2(x,y)" constructor function. However, I *do* want
> to be able to write "foo.x = 3", so there has to be some sort of awareness of
> ".x" syntax - maybe a metatable, similar to how the 'string' type has methods
> attached to it?
> 
> Does anyone have any example of doing something like this? It would save
> me some time tracking down all the details (and probably getting some
> wrong). Maybe a guide/tutorial, or a patch that someone has made
> previously?
> 
> I'm using Lua 5.3, but would still be interested in other versions, as a point of
> reference.
> 
> Thanks
> -John
//
// Created by rleigh on 18/09/2022.
//

#include "LuaGLM.h"

#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>

namespace {
template <typename T, typename U>
auto fetchIndex(T const &c, typename T::length_type idx) -> U const & {
  if (idx < c.length()) {
    return c[idx];
  } else {
    throw std::logic_error("GLM index out of range");
  }
}

template <typename T, typename U>
auto fetchIndex(T &c, typename T::length_type idx) -> U & {
  if (idx < c.length()) {
    return c[idx];
  } else {
    throw std::logic_error("GLM index out of range");
  }
}

template <typename T, typename U>
auto storeIndex(T &c, typename T::length_type idx, U const &v) -> void {
  if (idx < c.length()) {
    c[idx] = v;
  } else {
    throw std::logic_error("GLM index out of range");
  }
}

template <typename T> void createVec(sol::state &state, const char *name) {
  state.new_usertype<T>(
      name,
      sol::constructors<T(), T(float const &),
                        T(float const &, float const &)>(),
      sol::meta_function::multiplication,
      sol::overload(
          sol::resolve<T(T const &, T const &)>(
              &glm::operator*),
          sol::resolve<T(T const &, glm::vec1 const &)>(
              &glm::operator*),
          sol::resolve<T(T const &, float)>(&glm::operator*)),
      sol::meta_function::division,
      sol::overload(
          sol::resolve<T(T const &, T const &)>(
              &glm::operator/),
          sol::resolve<T(T const &, glm::vec1 const &)>(
              &glm::operator/),
          sol::resolve<T(T const &, float)>(&glm::operator/)),
      sol::meta_function::addition,
      sol::overload(
          sol::resolve<T(T const &, T const &)>(
              &glm::operator+),
          sol::resolve<T(T const &, glm::vec1 const &)>(
              &glm::operator+),
          sol::resolve<T(T const &, float)>(&glm::operator+)),
      sol::meta_function::subtraction,
      sol::overload(
          sol::resolve<T(T const &, T const &)>(
              &glm::operator-),
          sol::resolve<T(T const &, glm::vec1 const &)>(
              &glm::operator-),
          sol::resolve<T(T const &, float)>(&glm::operator-)),
      sol::meta_function::index,
      sol::resolve<const float &(T const &, glm::length_t)>(fetchIndex),
      sol::meta_function::new_index,
      sol::resolve<void(T &, glm::length_t, float const &)>(
          storeIndex));
}

} // namespace

void luaGlmInit(sol::state &state) {
  createVec<glm::vec1>(state, "vec1");
  createVec<glm::vec2>(state, "vec2");
  createVec<glm::vec3>(state, "vec3");
  createVec<glm::vec4>(state, "vec4");

  state.new_usertype<glm::mat2>(
      "mat2",
      sol::constructors<glm::mat2(), glm::mat2(float const &),
                        glm::mat2(float const &, float const &, float const &,
                                  float const &)>(),
      sol::meta_function::multiplication,
      sol::overload(
          sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
              &glm::operator*),
          sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator*)),
      sol::meta_function::division,
      sol::overload(
          sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
              &glm::operator/),
          sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator/)),
      sol::meta_function::addition,
      sol::overload(
          sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
              &glm::operator+),
          sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator+)),
      sol::meta_function::subtraction,
      sol::overload(
          sol::resolve<glm::mat2(glm::mat2 const &, glm::mat2 const &)>(
              &glm::operator-),
          sol::resolve<glm::mat2(glm::mat2 const &, float)>(&glm::operator-)),
      sol::meta_function::index,
      sol::resolve<glm::vec2 &(glm::mat2 &, glm::length_t)>(fetchIndex),
      sol::meta_function::new_index,
      sol::resolve<void(glm::mat2 &, glm::length_t, glm::vec2 const &)>(
          storeIndex));

  state.new_usertype<glm::mat3>(
      "mat3",
      sol::constructors<glm::mat3(), glm::mat3(float const &),
                        glm::mat3(float const &, float const &, float const &,
                                  float const &, float const &, float const &,
                                  float const &, float const &, float const &)>(),
      sol::meta_function::multiplication,
      sol::overload(
          sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
              &glm::operator*),
          sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator*)),
      sol::meta_function::division,
      sol::overload(
          sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
              &glm::operator/),
          sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator/)),
      sol::meta_function::addition,
      sol::overload(
          sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
              &glm::operator+),
          sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator+)),
      sol::meta_function::subtraction,
      sol::overload(
          sol::resolve<glm::mat3(glm::mat3 const &, glm::mat3 const &)>(
              &glm::operator-),
          sol::resolve<glm::mat3(glm::mat3 const &, float)>(&glm::operator-)),
      sol::meta_function::index,
      sol::resolve<glm::vec3 &(glm::mat3 &, glm::length_t)>(fetchIndex),
      sol::meta_function::new_index,
      sol::resolve<void(glm::mat3 &, glm::length_t, glm::vec3 const &)>(
          storeIndex));

  state.new_usertype<glm::mat4>(
      "mat4",
      sol::constructors<glm::mat4(), glm::mat4(float const &),
                        glm::mat4(float const &, float const &, float const &, float const &,
                                  float const &, float const &, float const &, float const &,
                                  float const &, float const &, float const &, float const &,
                                  float const &, float const &, float const &, float const &)>(),
      sol::meta_function::multiplication,
      sol::overload(
          sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
              &glm::operator*),
          sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator*)),
      sol::meta_function::division,
      sol::overload(
          sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
              &glm::operator/),
          sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator/)),
      sol::meta_function::addition,
      sol::overload(
          sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
              &glm::operator+),
          sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator+)),
      sol::meta_function::subtraction,
      sol::overload(
          sol::resolve<glm::mat4(glm::mat4 const &, glm::mat4 const &)>(
              &glm::operator-),
          sol::resolve<glm::mat4(glm::mat4 const &, float const&)>(&glm::operator-)),
      sol::meta_function::index,
      sol::resolve<glm::vec4 &(glm::mat4 &, glm::length_t)>(fetchIndex),
      sol::meta_function::new_index,
      sol::resolve<void(glm::mat4 &, glm::length_t, glm::vec4 const &)>(
          storeIndex));
}
//
// Created by rleigh on 18/09/2022.
//

#ifndef VLK_LUAGLM_H
#define VLK_LUAGLM_H

#include <sol/sol.hpp>

void luaGlmInit(sol::state& state);

#endif // VLK_LUAGLM_H
//
// Created by rleigh on 18/09/2022.
//

#include <string>

#include <gtest/gtest.h>

#include "LuaGLM.h"

#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>

TEST(LuaGLM, Init) {
  sol::state lua;
  lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::math,
                     sol::lib::debug, sol::lib::string, sol::lib::table);

  luaGlmInit(lua);
  lua.safe_script("print(\"Lua loaded GLM libraries\")");
}

template <typename T>
class LuaGLM : public testing::Test {
private:
  void SetUp() override {
    lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::math,
                        sol::lib::debug, sol::lib::string, sol::lib::table);

    luaGlmInit(lua);
  }
  
public:
  sol::state lua;

  static const T initVal;
  static const std::string initStr;
  static const std::string initLuaStr;
};

template <typename T>
class LuaGLMVec : public LuaGLM<T>
{};

template <typename T>
class LuaGLMMat : public LuaGLM<T>
{};

TYPED_TEST_CASE_P(LuaGLMVec);
TYPED_TEST_CASE_P(LuaGLMMat);

template<>
const glm::vec1 LuaGLM<glm::vec1>::initVal{2.4f};
template<>
const std::string LuaGLM<glm::vec1>::initStr{"vec1.new(2.4)"};
template<>
const std::string LuaGLM<glm::vec1>::initLuaStr{"vec1(2.400000)"};

template<>
const glm::vec2 LuaGLM<glm::vec2>::initVal{2.4f, 4.6f};
template<>
const std::string LuaGLM<glm::vec2>::initStr{"vec2.new(2.4, 4.6)"};
template<>
const std::string LuaGLM<glm::vec2>::initLuaStr{"vec2(2.400000, 4.600000)"};

template<>
const glm::vec3 LuaGLM<glm::vec3>::initVal{2.4f, 4.6f, 1.5f};
template<>
const std::string LuaGLM<glm::vec3>::initStr{"vec3.new(2.4, 4.6, 1.5)"};
template<>
const std::string LuaGLM<glm::vec3>::initLuaStr{"vec3(2.400000, 4.600000, 1.500000)"};

template<>
const glm::vec4 LuaGLM<glm::vec4>::initVal{2.4f, 4.6f, 1.5f, 9.3f};
template<>
const std::string LuaGLM<glm::vec4>::initStr{"vec4.new(2.4, 4.6, 1.5, 9.3)"};
template<>
const std::string LuaGLM<glm::vec4>::initLuaStr{"vec4(2.400000, 4.600000, 1.500000, 9.300000)"};

template<>
const glm::mat2 LuaGLM<glm::mat2>::initVal{2.4f, 4.6f, 3.9f, 1.6f};
template<>
const std::string LuaGLM<glm::mat2>::initStr{"mat2.new(2.4, 4.6, 3.9, 1.6)"};
template<>
const std::string LuaGLM<glm::mat2>::initLuaStr{"mat2x2((2.400000, 4.600000), (3.900000, 1.600000))"};

template<>
const glm::mat3 LuaGLM<glm::mat3>::initVal{2.4f, 4.6f, 3.9f, 1.6f, 3.5f, 8.1f, 7.4f, 0.7f, 5.4f};
template<>
const std::string LuaGLM<glm::mat3>::initStr{"mat3.new(2.4, 4.6, 3.9, 1.6, 3.5, 8.1, 7.4, 0.7, 5.4)"};
template<>
const std::string LuaGLM<glm::mat3>::initLuaStr{"mat3x3((2.400000, 4.600000, 3.900000), (1.600000, 3.500000, 8.100000), (7.400000, 0.700000, 5.400000))"};

template<>
const glm::mat4 LuaGLM<glm::mat4>::initVal{2.4f, 4.6f, 3.9f, 1.6f, 3.5f, 8.1f, 7.4f, 0.7f, 5.4f, 14.5f, 11.4f, 10.5f, 13.6f, 13.7f, 16.4f, 15.1f};
template<>
const std::string LuaGLM<glm::mat4>::initStr{"mat4.new(2.4, 4.6, 3.9, 1.6, 3.5, 8.1, 7.4, 0.7, 5.4, 14.5, 11.4, 10.5, 13.6, 13.7, 16.4, 15.1)"};
template<>
const std::string LuaGLM<glm::mat4>::initLuaStr{"mat4x4((2.400000, 4.600000, 3.900000, 1.600000), (3.500000, 8.100000, 7.400000, 0.700000), (5.400000, 14.500000, 11.400000, 10.500000), (13.600000, 13.700000, 16.400000, 15.100000))"};

#define ASSERT_GLM_VEC_EQ(Vec1, Vec2) \
  for (typename TypeParam::length_type i = 0; i < TypeParam::length(); ++i) { \
    ASSERT_FLOAT_EQ(Vec1[i], Vec2[i]); \
  }

#define ASSERT_GLM_MAT_EQ(Mat1, Mat2) \
  for (typename TypeParam::length_type i = 0; i < TypeParam::length(); ++i) { \
    for (typename TypeParam::col_type::length_type j = 0; j < TypeParam::col_type::length(); ++j) { \
      ASSERT_FLOAT_EQ(Mat1[i][j], Mat2[i][j]); \
    } \
  }

TYPED_TEST_P(LuaGLMVec, ConstructExt) {
  sol::state& lua = this->lua;

  lua["vec"] = TypeParam{this->initVal};
  lua.safe_script("print(vec)");
  lua.safe_script("str = tostring(vec)");

  TypeParam ret = lua["vec"];
  std::string str = lua["str"];

  ASSERT_GLM_VEC_EQ(ret, this->initVal);
  ASSERT_EQ(str, this->initLuaStr);
}

TYPED_TEST_P(LuaGLMMat, ConstructExt) {
  sol::state &lua = this->lua;

  lua["mat"] = TypeParam{this->initVal};

  lua.safe_script("print(mat)");
  lua.safe_script("str = tostring(mat)");

  TypeParam ret = lua["mat"];
  std::string str = lua["str"];

  ASSERT_GLM_MAT_EQ(ret, this->initVal);
  ASSERT_EQ(str, this->initLuaStr);
}

TYPED_TEST_P(LuaGLMVec, ConstructInt) {
  sol::state& lua = this->lua;
  
  std::string script{"vec = "};
  script += this->initStr;
  lua.safe_script(script);
  lua.safe_script("print(vec)");
  lua.safe_script("str = tostring(vec)");

  TypeParam ret = lua["vec"];
  std::string str = lua["str"];

  ASSERT_GLM_VEC_EQ(ret, this->initVal);
  ASSERT_EQ(str, this->initLuaStr);
}

TYPED_TEST_P(LuaGLMMat, ConstructInt) {
  sol::state &lua = this->lua;

  std::string script{"mat = "};
  script += this->initStr;
  lua.safe_script(script);
  lua.safe_script("print(mat)");
  lua.safe_script("str = tostring(mat)");

  TypeParam ret = lua["mat"];
  std::string str = lua["str"];

  ASSERT_GLM_MAT_EQ(ret, this->initVal);
  ASSERT_EQ(str, this->initLuaStr);
}

TYPED_TEST_P(LuaGLMVec, MultiplyScalar) {
  sol::state& lua = this->lua;

  lua["vec"] = TypeParam{this->initVal};
  lua.safe_script("print(vec)");
  lua.safe_script("vecm = vec * 2.0");
  lua.safe_script("print(vecm)");

  TypeParam ret = lua["vecm"];

  ASSERT_GLM_VEC_EQ(ret, (this->initVal * 2.0f));
}

TYPED_TEST_P(LuaGLMMat, MultiplyScalar) {
  sol::state &lua = this->lua;

  lua["mat"] = TypeParam{this->initVal};
  lua.safe_script("print(mat)");
  lua.safe_script("matm = mat * 2.0");
  lua.safe_script("print(matm)");

  TypeParam ret = lua["matm"];

  ASSERT_GLM_MAT_EQ(ret, (this->initVal * 2.0f));
}

TYPED_TEST_P(LuaGLMVec, DivideScalar) {
  sol::state& lua = this->lua;

  lua["vec"] = TypeParam{this->initVal};
  lua.safe_script("print(vec)");
  lua.safe_script("vecm = vec / 1.5");
  lua.safe_script("print(vecm)");

  TypeParam ret = lua["vecm"];

  ASSERT_GLM_VEC_EQ(ret, (this->initVal / 1.5f));
}

TYPED_TEST_P(LuaGLMMat, DivideScalar) {
  sol::state &lua = this->lua;

  lua["mat"] = TypeParam{this->initVal};
  lua.safe_script("print(mat)");
  lua.safe_script("matm = mat / 1.5");
  lua.safe_script("print(matm)");

  TypeParam ret = lua["matm"];

  ASSERT_GLM_MAT_EQ(ret, (this->initVal / 1.5f));
}

TYPED_TEST_P(LuaGLMVec, AddScalar) {
  sol::state& lua = this->lua;

  lua["vec"] = TypeParam{this->initVal};
  lua.safe_script("print(vec)");
  lua.safe_script("vecm = vec + 3.2");
  lua.safe_script("print(vecm)");

  TypeParam ret = lua["vecm"];

  ASSERT_GLM_VEC_EQ(ret, (this->initVal + 3.2f));
}

TYPED_TEST_P(LuaGLMMat, AddScalar) {
  sol::state &lua = this->lua;

  lua["mat"] = TypeParam{this->initVal};
  lua.safe_script("print(mat)");
  lua.safe_script("matm = mat + 3.2");
  lua.safe_script("print(matm)");

  TypeParam ret = lua["matm"];

  ASSERT_GLM_MAT_EQ(ret, (this->initVal + 3.2f));
}

TYPED_TEST_P(LuaGLMVec, SubtractScalar) {
  sol::state& lua = this->lua;

  lua["vec"] = TypeParam{this->initVal};
  lua.safe_script("print(vec)");
  lua.safe_script("vecm = vec - 1.43");
  lua.safe_script("print(vecm)");

  TypeParam ret = lua["vecm"];

  ASSERT_GLM_VEC_EQ(ret, (this->initVal - 1.43f));
}

TYPED_TEST_P(LuaGLMMat, SubtractScalar) {
  sol::state &lua = this->lua;

  lua["mat"] = TypeParam{this->initVal};
  lua.safe_script("print(mat)");
  lua.safe_script("matm = mat - 1.43");
  lua.safe_script("print(matm)");

  TypeParam ret = lua["matm"];

  ASSERT_GLM_MAT_EQ(ret, (this->initVal - 1.43f));
}

#if 0


TEST_F(LuaGLM, Vec1MultiplyVec1) {
  lua.safe_script("vec = vec1.new(2.4)\n"
              "vec = vec * vec\n"
              "print(vec)");

  glm::vec1 expected(5.76f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}


TEST_F(LuaGLM, Vec1DivideVec1) {
  lua.safe_script("vec = vec1.new(2.4)\n"
              "vec = vec / vec\n"
              "print(vec)");

  glm::vec1 expected(1.0f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}

TEST_F(LuaGLM, Vec1AddScalar) {
  lua.safe_script("vec = vec1.new(2.4)\n"
              "vec = vec + 2.0\n"
              "print(vec)");

  glm::vec1 expected(4.4f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}

TEST_F(LuaGLM, Vec1AddVec1) {
  lua.safe_script("vec = vec1.new(2.4)\n"
              "vec = vec + vec\n"
              "print(vec)");

  glm::vec1 expected(4.8f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}

TEST_F(LuaGLM, Vec1SubtractScalar) {
  lua.safe_script("vec = vec1.new(2.4)\n"
              "vec = vec - 2.0\n"
              "print(vec)");

  glm::vec1 expected(0.4f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}

TEST_F(LuaGLM, Vec1SubtractVec1) {
  lua.safe_script(R"(vec = vec1.new(2.4)
                  vec = vec - vec
                  print(vec))");

  glm::vec1 expected(0.0f);
  glm::vec1 observed = lua["vec"];
  ASSERT_FLOAT_EQ(observed[0], expected[0]);
}




#endif

REGISTER_TYPED_TEST_CASE_P(LuaGLMVec,
                           ConstructExt,
                           ConstructInt,
                           MultiplyScalar,
                           DivideScalar,
                           AddScalar,
                           SubtractScalar);
REGISTER_TYPED_TEST_CASE_P(LuaGLMMat,
                           ConstructExt,
                           ConstructInt,
                           MultiplyScalar,
                           DivideScalar,
                           AddScalar,
                           SubtractScalar);

INSTANTIATE_TYPED_TEST_CASE_P(Vec1, LuaGLMVec, ::testing::Types<glm::vec1>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec2, LuaGLMVec, ::testing::Types<glm::vec2>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec3, LuaGLMVec, ::testing::Types<glm::vec3>);
INSTANTIATE_TYPED_TEST_CASE_P(Vec4, LuaGLMVec, ::testing::Types<glm::vec4>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat2, LuaGLMMat, ::testing::Types<glm::mat2>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat3, LuaGLMMat, ::testing::Types<glm::mat3>);
INSTANTIATE_TYPED_TEST_CASE_P(Mat4, LuaGLMMat, ::testing::Types<glm::mat4>);