migrated to sol2

decomposed method

added vector2, vector4

refactored tests

added opengl engine to lua

added other engines

added source tests

removed tons of lua files
This commit is contained in:
2026-03-07 22:12:11 +03:00
parent 9752accb14
commit 943472cf64
18 changed files with 1405 additions and 3 deletions

5
.luarc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"diagnostics.globals": [
"omath"
]
}

View File

@@ -31,6 +31,9 @@ option(OMATH_SUPRESS_SAFETY_CHECKS
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
option(OMATH_ENABLE_FORCE_INLINE option(OMATH_ENABLE_FORCE_INLINE
"Will for compiler to make some functions to be force inlined no matter what" ON) "Will for compiler to make some functions to be force inlined no matter what" ON)
option(OMATH_ENABLE_LUA
"omath bindings for lua" OFF)
if(VCPKG_MANIFEST_FEATURES) if(VCPKG_MANIFEST_FEATURES)
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
if(omath_feature STREQUAL "imgui") if(omath_feature STREQUAL "imgui")
@@ -43,6 +46,8 @@ if(VCPKG_MANIFEST_FEATURES)
set(OMATH_BUILD_BENCHMARK ON) set(OMATH_BUILD_BENCHMARK ON)
elseif(omath_feature STREQUAL "examples") elseif(omath_feature STREQUAL "examples")
set(OMATH_BUILD_EXAMPLES ON) set(OMATH_BUILD_EXAMPLES ON)
elseif(omath_feature STREQUAL "lua")
set(OMATH_ENABLE_LUA ON)
endif() endif()
endforeach() endforeach()
@@ -83,6 +88,17 @@ else()
add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS})
endif() endif()
if (OMATH_ENABLE_LUA)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LUA)
find_package(Lua REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${LUA_LIBRARIES})
find_path(SOL2_INCLUDE_DIRS "sol/abort.hpp")
target_include_directories(${PROJECT_NAME} PRIVATE ${SOL2_INCLUDE_DIRS})
endif ()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}")

View File

@@ -145,7 +145,7 @@
"hidden": true, "hidden": true,
"inherits": ["linux-base", "vcpkg-base"], "inherits": ["linux-base", "vcpkg-base"],
"cacheVariables": { "cacheVariables": {
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2" "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;lua"
} }
}, },
{ {

13
include/omath/lua/lua.hpp Normal file
View File

@@ -0,0 +1,13 @@
//
// Created by orange on 07.03.2026.
//
#pragma once
#ifdef OMATH_ENABLE_LUA
// ReSharper disable once CppInconsistentNaming
struct lua_State;
namespace omath::lua
{
void register_lib(lua_State* lua_state);
}
#endif

411
source/lua/lua.cpp Normal file
View File

@@ -0,0 +1,411 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "lua.hpp"
#include "omath/lua/lua.hpp"
#include <omath/engines/cry_engine/camera.hpp>
#include <omath/engines/cry_engine/constants.hpp>
#include <omath/engines/frostbite_engine/camera.hpp>
#include <omath/engines/frostbite_engine/constants.hpp>
#include <omath/engines/iw_engine/camera.hpp>
#include <omath/engines/iw_engine/constants.hpp>
#include <omath/engines/opengl_engine/camera.hpp>
#include <omath/engines/opengl_engine/constants.hpp>
#include <omath/engines/opengl_engine/formulas.hpp>
#include <omath/engines/source_engine/camera.hpp>
#include <omath/engines/source_engine/constants.hpp>
#include <omath/engines/unity_engine/camera.hpp>
#include <omath/engines/unity_engine/constants.hpp>
#include <omath/engines/unreal_engine/camera.hpp>
#include <omath/engines/unreal_engine/constants.hpp>
#include <omath/linear_algebra/vector2.hpp>
#include <omath/linear_algebra/vector3.hpp>
#include <omath/linear_algebra/vector4.hpp>
#include <omath/projection/camera.hpp>
#include <omath/projection/error_codes.hpp>
#include <sol/sol.hpp>
#include <string_view>
namespace
{
void register_vec2(sol::table& omath_table)
{
using Vec2f = omath::Vector2<float>;
omath_table.new_usertype<Vec2f>(
"Vec2", sol::constructors<Vec2f(), Vec2f(float, float)>(),
"x", &Vec2f::x, "y", &Vec2f::y,
sol::meta_function::addition, sol::resolve<Vec2f(const Vec2f&) const>(&Vec2f::operator+),
sol::meta_function::subtraction, sol::resolve<Vec2f(const Vec2f&) const>(&Vec2f::operator-),
sol::meta_function::unary_minus, sol::resolve<Vec2f() const>(&Vec2f::operator-),
sol::meta_function::equal_to, &Vec2f::operator==,
sol::meta_function::less_than, sol::resolve<bool(const Vec2f&) const>(&Vec2f::operator<),
sol::meta_function::less_than_or_equal_to, sol::resolve<bool(const Vec2f&) const>(&Vec2f::operator<=),
sol::meta_function::to_string,
[](const Vec2f& v) { return std::format("Vec2({}, {})", v.x, v.y); },
sol::meta_function::multiplication,
sol::overload(sol::resolve<Vec2f(const float&) const>(&Vec2f::operator*),
[](const float s, const Vec2f& v) { return v * s; }),
sol::meta_function::division,
sol::resolve<Vec2f(const float&) const>(&Vec2f::operator/),
"length", &Vec2f::length,
"length_sqr", &Vec2f::length_sqr,
"normalized", &Vec2f::normalized,
"dot", &Vec2f::dot,
"distance_to", &Vec2f::distance_to,
"distance_to_sqr", &Vec2f::distance_to_sqr,
"sum", &Vec2f::sum,
"abs",
[](const Vec2f& v)
{
Vec2f copy = v;
copy.abs();
return copy;
});
}
void register_vec4(sol::table& omath_table)
{
using Vec4f = omath::Vector4<float>;
omath_table.new_usertype<Vec4f>(
"Vec4", sol::constructors<Vec4f(), Vec4f(float, float, float, float)>(),
"x", &Vec4f::x, "y", &Vec4f::y, "z", &Vec4f::z, "w", &Vec4f::w,
sol::meta_function::addition, sol::resolve<Vec4f(const Vec4f&) const>(&Vec4f::operator+),
sol::meta_function::subtraction, sol::resolve<Vec4f(const Vec4f&) const>(&Vec4f::operator-),
sol::meta_function::unary_minus, sol::resolve<Vec4f() const>(&Vec4f::operator-),
sol::meta_function::equal_to, &Vec4f::operator==,
sol::meta_function::less_than, sol::resolve<bool(const Vec4f&) const>(&Vec4f::operator<),
sol::meta_function::less_than_or_equal_to, sol::resolve<bool(const Vec4f&) const>(&Vec4f::operator<=),
sol::meta_function::to_string,
[](const Vec4f& v) { return std::format("Vec4({}, {}, {}, {})", v.x, v.y, v.z, v.w); },
sol::meta_function::multiplication,
sol::overload(sol::resolve<Vec4f(const float&) const>(&Vec4f::operator*),
sol::resolve<Vec4f(const Vec4f&) const>(&Vec4f::operator*),
[](const float s, const Vec4f& v) { return v * s; }),
sol::meta_function::division,
sol::overload(sol::resolve<Vec4f(const float&) const>(&Vec4f::operator/),
sol::resolve<Vec4f(const Vec4f&) const>(&Vec4f::operator/)),
"length", &Vec4f::length,
"length_sqr", &Vec4f::length_sqr,
"dot", &Vec4f::dot,
"sum", &Vec4f::sum,
"abs",
[](const Vec4f& v)
{
Vec4f copy = v;
copy.abs();
return copy;
},
"clamp",
[](Vec4f& v, float mn, float mx)
{
v.clamp(mn, mx);
return v;
});
}
void register_vec3(sol::table& omath_table)
{
using Vec3f = omath::Vector3<float>;
omath_table.new_usertype<Vec3f>(
"Vec3", sol::constructors<Vec3f(), Vec3f(float, float, float)>(),
"x", &Vec3f::x, "y", &Vec3f::y, "z", &Vec3f::z,
sol::meta_function::addition, sol::resolve<Vec3f(const Vec3f&) const>(&Vec3f::operator+),
sol::meta_function::subtraction, sol::resolve<Vec3f(const Vec3f&) const>(&Vec3f::operator-),
sol::meta_function::unary_minus, sol::resolve<Vec3f() const>(&Vec3f::operator-),
sol::meta_function::equal_to, &Vec3f::operator==, sol::meta_function::less_than,
sol::resolve<bool(const Vec3f&) const>(&Vec3f::operator<), sol::meta_function::less_than_or_equal_to,
sol::resolve<bool(const Vec3f&) const>(&Vec3f::operator<=), sol::meta_function::to_string,
[](const Vec3f& v) { return std::format("Vec3({}, {}, {})", v.x, v.y, v.z); },
sol::meta_function::multiplication,
sol::overload(sol::resolve<Vec3f(const float&) const>(&Vec3f::operator*),
sol::resolve<Vec3f(const Vec3f&) const>(&Vec3f::operator*),
[](const float s, const Vec3f& v) { return v * s; }),
sol::meta_function::division,
sol::overload(sol::resolve<Vec3f(const float&) const>(&Vec3f::operator/),
sol::resolve<Vec3f(const Vec3f&) const>(&Vec3f::operator/)),
"length", &Vec3f::length, "length_2d", &Vec3f::length_2d, "length_sqr", &Vec3f::length_sqr,
"normalized", &Vec3f::normalized, "dot", &Vec3f::dot, "cross", &Vec3f::cross, "distance_to",
&Vec3f::distance_to, "distance_to_sqr", &Vec3f::distance_to_sqr, "sum",
sol::resolve<float() const>(&Vec3f::sum), "sum_2d", &Vec3f::sum_2d, "point_to_same_direction",
&Vec3f::point_to_same_direction, "as_array", &Vec3f::as_array,
"abs",
[](const Vec3f& v)
{
Vec3f copy = v;
copy.abs();
return copy;
},
"angle_between",
[](const Vec3f& self,
const Vec3f& other) -> std::tuple<sol::optional<float>, sol::optional<std::string>>
{
auto result = self.angle_between(other);
if (result)
return std::make_tuple(sol::optional<float>(result->as_degrees()),
sol::optional<std::string>(sol::nullopt));
return std::make_tuple(sol::optional<float>(sol::nullopt),
sol::optional<std::string>("impossible angle (zero-length vector)"));
},
"is_perpendicular",
[](const Vec3f& self, const Vec3f& other, sol::optional<float> eps)
{ return self.is_perpendicular(other, eps.value_or(0.0001f)); },
"as_table",
[](const Vec3f& v, sol::this_state s) -> sol::table
{
sol::state_view lua(s);
sol::table t = lua.create_table();
t["x"] = v.x;
t["y"] = v.y;
t["z"] = v.z;
return t;
});
}
static std::string projection_error_to_string(omath::projection::Error e)
{
switch (e)
{
case omath::projection::Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS:
return "world position is out of screen bounds";
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
return "inverse view-projection matrix determinant is zero";
}
return "unknown error";
}
template<class AngleType>
void register_angle(sol::table& table, const char* name)
{
table.new_usertype<AngleType>(
name, sol::no_constructor,
"from_degrees", &AngleType::from_degrees,
"from_radians", &AngleType::from_radians,
"as_degrees", &AngleType::as_degrees,
"as_radians", &AngleType::as_radians,
"sin", &AngleType::sin,
"cos", &AngleType::cos,
"tan", &AngleType::tan,
"cot", &AngleType::cot,
sol::meta_function::addition,
[](const AngleType& a, const AngleType& b)
{ return AngleType::from_degrees(a.as_degrees() + b.as_degrees()); },
sol::meta_function::subtraction,
[](const AngleType& a, const AngleType& b)
{ return AngleType::from_degrees(a.as_degrees() - b.as_degrees()); },
sol::meta_function::unary_minus,
[](const AngleType& a) { return AngleType::from_degrees(-a.as_degrees()); },
sol::meta_function::equal_to,
[](const AngleType& a, const AngleType& b) { return a == b; },
sol::meta_function::to_string,
[](const AngleType& a) { return std::format("{}deg", a.as_degrees()); });
}
// ---- Canonical shared C++ type aliases ----------------------------------
// Each unique template instantiation must be registered exactly once.
using PitchAngle90 = omath::Angle<float, -90.f, 90.f, omath::AngleFlags::Clamped>;
using PitchAngle89 = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
using SharedYawRoll = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
using SharedFoV = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>;
using ViewAngles90 = omath::ViewAngles<PitchAngle90, SharedYawRoll, SharedYawRoll>;
using ViewAngles89 = omath::ViewAngles<PitchAngle89, SharedYawRoll, SharedYawRoll>;
// Register every shared C++ type exactly once under omath._types
void register_shared_types(sol::table& omath_table)
{
auto t = omath_table["_types"].get_or_create<sol::table>();
register_angle<PitchAngle90>(t, "PitchAngle90");
register_angle<PitchAngle89>(t, "PitchAngle89");
register_angle<SharedYawRoll>(t, "YawRoll");
register_angle<SharedFoV>(t, "FieldOfView");
t.new_usertype<omath::projection::ViewPort>(
"ViewPort",
sol::factories([](float w, float h) { return omath::projection::ViewPort{w, h}; }),
"width", &omath::projection::ViewPort::m_width,
"height", &omath::projection::ViewPort::m_height,
"aspect_ratio", &omath::projection::ViewPort::aspect_ratio);
t.new_usertype<ViewAngles90>(
"ViewAngles90",
sol::factories([](PitchAngle90 p, SharedYawRoll y, SharedYawRoll r)
{ return ViewAngles90{p, y, r}; }),
"pitch", &ViewAngles90::pitch,
"yaw", &ViewAngles90::yaw,
"roll", &ViewAngles90::roll);
t.new_usertype<ViewAngles89>(
"ViewAngles89",
sol::factories([](PitchAngle89 p, SharedYawRoll y, SharedYawRoll r)
{ return ViewAngles89{p, y, r}; }),
"pitch", &ViewAngles89::pitch,
"yaw", &ViewAngles89::yaw,
"roll", &ViewAngles89::roll);
}
// Set aliases in an engine subtable pointing to the already-registered shared types
template<class PitchAngleType, class ViewAnglesType>
void set_engine_aliases(sol::table& engine_table, sol::table& types)
{
if constexpr (std::is_same_v<PitchAngleType, PitchAngle90>)
engine_table["PitchAngle"] = types["PitchAngle90"];
else
engine_table["PitchAngle"] = types["PitchAngle89"];
engine_table["YawAngle"] = types["YawRoll"];
engine_table["RollAngle"] = types["YawRoll"];
engine_table["FieldOfView"] = types["FieldOfView"];
engine_table["ViewPort"] = types["ViewPort"];
if constexpr (std::is_same_v<ViewAnglesType, ViewAngles90>)
engine_table["ViewAngles"] = types["ViewAngles90"];
else
engine_table["ViewAngles"] = types["ViewAngles89"];
}
// Register an engine: alias shared types, register unique Camera
template<class EngineTraits>
void register_engine(sol::table& omath_table, const char* subtable_name)
{
using PitchAngle = typename EngineTraits::PitchAngle;
using ViewAngles = typename EngineTraits::ViewAngles;
using Camera = typename EngineTraits::Camera;
auto engine_table = omath_table[subtable_name].get_or_create<sol::table>();
auto types = omath_table["_types"].get<sol::table>();
set_engine_aliases<PitchAngle, ViewAngles>(engine_table, types);
engine_table.new_usertype<Camera>(
"Camera",
sol::constructors<Camera(const omath::Vector3<float>&, const ViewAngles&,
const omath::projection::ViewPort&,
const omath::projection::FieldOfView&, float, float)>(),
"look_at", &Camera::look_at,
"get_forward", &Camera::get_forward,
"get_right", &Camera::get_right,
"get_up", &Camera::get_up,
"get_origin", &Camera::get_origin,
"get_view_angles", &Camera::get_view_angles,
"get_near_plane", &Camera::get_near_plane,
"get_far_plane", &Camera::get_far_plane,
"get_field_of_view", &Camera::get_field_of_view,
"set_origin", &Camera::set_origin,
"set_view_angles", &Camera::set_view_angles,
"set_view_port", &Camera::set_view_port,
"set_field_of_view", &Camera::set_field_of_view,
"set_near_plane", &Camera::set_near_plane,
"set_far_plane", &Camera::set_far_plane,
"world_to_screen",
[](const Camera& cam, const omath::Vector3<float>& pos)
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
{
auto result = cam.world_to_screen(pos);
if (result) return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())};
},
"screen_to_world",
[](const Camera& cam, const omath::Vector3<float>& pos)
-> std::tuple<sol::optional<omath::Vector3<float>>, sol::optional<std::string>>
{
auto result = cam.screen_to_world(pos);
if (result) return {*result, sol::nullopt};
return {sol::nullopt, projection_error_to_string(result.error())};
});
}
// ---- Engine trait structs -----------------------------------------------
struct OpenGLEngineTraits
{
using PitchAngle = omath::opengl_engine::PitchAngle;
using ViewAngles = omath::opengl_engine::ViewAngles;
using Camera = omath::opengl_engine::Camera;
};
struct FrostbiteEngineTraits
{
using PitchAngle = omath::frostbite_engine::PitchAngle;
using ViewAngles = omath::frostbite_engine::ViewAngles;
using Camera = omath::frostbite_engine::Camera;
};
struct IWEngineTraits
{
using PitchAngle = omath::iw_engine::PitchAngle;
using ViewAngles = omath::iw_engine::ViewAngles;
using Camera = omath::iw_engine::Camera;
};
struct SourceEngineTraits
{
using PitchAngle = omath::source_engine::PitchAngle;
using ViewAngles = omath::source_engine::ViewAngles;
using Camera = omath::source_engine::Camera;
};
struct UnityEngineTraits
{
using PitchAngle = omath::unity_engine::PitchAngle;
using ViewAngles = omath::unity_engine::ViewAngles;
using Camera = omath::unity_engine::Camera;
};
struct UnrealEngineTraits
{
using PitchAngle = omath::unreal_engine::PitchAngle;
using ViewAngles = omath::unreal_engine::ViewAngles;
using Camera = omath::unreal_engine::Camera;
};
struct CryEngineTraits
{
using PitchAngle = omath::cry_engine::PitchAngle;
using ViewAngles = omath::cry_engine::ViewAngles;
using Camera = omath::cry_engine::Camera;
};
} // namespace
namespace omath::lua
{
void register_lib(lua_State* lua_state)
{
sol::state_view lua(lua_state);
auto omath_table = lua["omath"].get_or_create<sol::table>();
register_vec2(omath_table);
register_vec3(omath_table);
register_vec4(omath_table);
register_shared_types(omath_table);
register_engine<OpenGLEngineTraits>(omath_table, "opengl");
register_engine<FrostbiteEngineTraits>(omath_table, "frostbite");
register_engine<IWEngineTraits>(omath_table, "iw");
register_engine<SourceEngineTraits>(omath_table, "source");
register_engine<UnityEngineTraits>(omath_table, "unity");
register_engine<UnrealEngineTraits>(omath_table, "unreal");
register_engine<CryEngineTraits>(omath_table, "cry");
}
} // namespace omath::lua
#endif

View File

@@ -22,6 +22,8 @@ else() # GTest is being linked as vcpkg package
target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main omath::omath) target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main omath::omath)
endif() endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE LUA_SCRIPTS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/lua")
if(OMATH_ENABLE_COVERAGE) if(OMATH_ENABLE_COVERAGE)
include(${CMAKE_SOURCE_DIR}/cmake/Coverage.cmake) include(${CMAKE_SOURCE_DIR}/cmake/Coverage.cmake)
omath_setup_coverage(${PROJECT_NAME}) omath_setup_coverage(${PROJECT_NAME})

View File

@@ -0,0 +1,79 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaSourceEngine : public ::testing::Test
{
protected:
lua_State* L = nullptr;
void SetUp() override
{
L = luaL_newstate();
luaL_openlibs(L);
omath::lua::register_lib(L);
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/source_engine_tests.lua") != LUA_OK)
FAIL() << lua_tostring(L, -1);
}
void TearDown() override { lua_close(L); }
void check(const char* func_name)
{
lua_getglobal(L, func_name);
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
{
FAIL() << lua_tostring(L, -1);
lua_pop(L, 1);
}
}
};
// PitchAngle
TEST_F(LuaSourceEngine, PitchAngle_from_degrees) { check("Source_PitchAngle_from_degrees"); }
TEST_F(LuaSourceEngine, PitchAngle_clamping_max) { check("Source_PitchAngle_clamping_max"); }
TEST_F(LuaSourceEngine, PitchAngle_clamping_min) { check("Source_PitchAngle_clamping_min"); }
TEST_F(LuaSourceEngine, PitchAngle_from_radians) { check("Source_PitchAngle_from_radians"); }
TEST_F(LuaSourceEngine, PitchAngle_as_radians) { check("Source_PitchAngle_as_radians"); }
TEST_F(LuaSourceEngine, PitchAngle_sin) { check("Source_PitchAngle_sin"); }
TEST_F(LuaSourceEngine, PitchAngle_cos) { check("Source_PitchAngle_cos"); }
TEST_F(LuaSourceEngine, PitchAngle_tan) { check("Source_PitchAngle_tan"); }
TEST_F(LuaSourceEngine, PitchAngle_addition) { check("Source_PitchAngle_addition"); }
TEST_F(LuaSourceEngine, PitchAngle_addition_clamped) { check("Source_PitchAngle_addition_clamped"); }
TEST_F(LuaSourceEngine, PitchAngle_subtraction) { check("Source_PitchAngle_subtraction"); }
TEST_F(LuaSourceEngine, PitchAngle_unary_minus) { check("Source_PitchAngle_unary_minus"); }
TEST_F(LuaSourceEngine, PitchAngle_equal_to) { check("Source_PitchAngle_equal_to"); }
TEST_F(LuaSourceEngine, PitchAngle_to_string) { check("Source_PitchAngle_to_string"); }
// YawAngle
TEST_F(LuaSourceEngine, YawAngle_from_degrees) { check("Source_YawAngle_from_degrees"); }
TEST_F(LuaSourceEngine, YawAngle_normalization) { check("Source_YawAngle_normalization"); }
// RollAngle
TEST_F(LuaSourceEngine, RollAngle_from_degrees) { check("Source_RollAngle_from_degrees"); }
// FieldOfView
TEST_F(LuaSourceEngine, FieldOfView_from_degrees) { check("Source_FieldOfView_from_degrees"); }
TEST_F(LuaSourceEngine, FieldOfView_clamping) { check("Source_FieldOfView_clamping"); }
// ViewAngles
TEST_F(LuaSourceEngine, ViewAngles_new) { check("Source_ViewAngles_new"); }
TEST_F(LuaSourceEngine, ViewAngles_field_mutation) { check("Source_ViewAngles_field_mutation"); }
// Camera
TEST_F(LuaSourceEngine, Camera_constructor) { check("Source_Camera_constructor"); }
TEST_F(LuaSourceEngine, Camera_get_set_origin) { check("Source_Camera_get_set_origin"); }
TEST_F(LuaSourceEngine, Camera_get_set_near_plane) { check("Source_Camera_get_set_near_plane"); }
TEST_F(LuaSourceEngine, Camera_get_set_far_plane) { check("Source_Camera_get_set_far_plane"); }
TEST_F(LuaSourceEngine, Camera_get_set_fov) { check("Source_Camera_get_set_fov"); }
TEST_F(LuaSourceEngine, Camera_get_set_view_angles) { check("Source_Camera_get_set_view_angles"); }
TEST_F(LuaSourceEngine, Camera_look_at) { check("Source_Camera_look_at"); }
TEST_F(LuaSourceEngine, Camera_get_forward) { check("Source_Camera_get_forward"); }
TEST_F(LuaSourceEngine, Camera_get_right) { check("Source_Camera_get_right"); }
TEST_F(LuaSourceEngine, Camera_get_up) { check("Source_Camera_get_up"); }
TEST_F(LuaSourceEngine, Camera_world_to_screen_success) { check("Source_Camera_world_to_screen_success"); }
TEST_F(LuaSourceEngine, Camera_world_to_screen_error) { check("Source_Camera_world_to_screen_error"); }
TEST_F(LuaSourceEngine, Camera_screen_to_world) { check("Source_Camera_screen_to_world"); }

View File

@@ -0,0 +1,56 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaVec2 : public ::testing::Test
{
protected:
lua_State* L = nullptr;
void SetUp() override
{
L = luaL_newstate();
luaL_openlibs(L);
omath::lua::register_lib(L);
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/vec2_tests.lua") != LUA_OK)
FAIL() << lua_tostring(L, -1);
}
void TearDown() override { lua_close(L); }
void check(const char* func_name)
{
lua_getglobal(L, func_name);
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
{
FAIL() << lua_tostring(L, -1);
lua_pop(L, 1);
}
}
};
TEST_F(LuaVec2, Constructor_default) { check("Vec2_Constructor_default"); }
TEST_F(LuaVec2, Constructor_xy) { check("Vec2_Constructor_xy"); }
TEST_F(LuaVec2, Field_mutation) { check("Vec2_Field_mutation"); }
TEST_F(LuaVec2, Addition) { check("Vec2_Addition"); }
TEST_F(LuaVec2, Subtraction) { check("Vec2_Subtraction"); }
TEST_F(LuaVec2, UnaryMinus) { check("Vec2_UnaryMinus"); }
TEST_F(LuaVec2, Multiplication_scalar) { check("Vec2_Multiplication_scalar"); }
TEST_F(LuaVec2, Multiplication_scalar_reversed) { check("Vec2_Multiplication_scalar_reversed"); }
TEST_F(LuaVec2, Division_scalar) { check("Vec2_Division_scalar"); }
TEST_F(LuaVec2, EqualTo_true) { check("Vec2_EqualTo_true"); }
TEST_F(LuaVec2, EqualTo_false) { check("Vec2_EqualTo_false"); }
TEST_F(LuaVec2, LessThan) { check("Vec2_LessThan"); }
TEST_F(LuaVec2, LessThanOrEqual) { check("Vec2_LessThanOrEqual"); }
TEST_F(LuaVec2, ToString) { check("Vec2_ToString"); }
TEST_F(LuaVec2, Length) { check("Vec2_Length"); }
TEST_F(LuaVec2, LengthSqr) { check("Vec2_LengthSqr"); }
TEST_F(LuaVec2, Normalized) { check("Vec2_Normalized"); }
TEST_F(LuaVec2, Dot) { check("Vec2_Dot"); }
TEST_F(LuaVec2, DistanceTo) { check("Vec2_DistanceTo"); }
TEST_F(LuaVec2, DistanceToSqr) { check("Vec2_DistanceToSqr"); }
TEST_F(LuaVec2, Sum) { check("Vec2_Sum"); }
TEST_F(LuaVec2, Abs) { check("Vec2_Abs"); }

View File

@@ -0,0 +1,69 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaVec3 : public ::testing::Test
{
protected:
lua_State* L = nullptr;
void SetUp() override
{
L = luaL_newstate();
luaL_openlibs(L);
omath::lua::register_lib(L);
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/vec3_tests.lua") != LUA_OK)
FAIL() << lua_tostring(L, -1);
}
void TearDown() override { lua_close(L); }
void check(const char* func_name)
{
lua_getglobal(L, func_name);
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
{
FAIL() << lua_tostring(L, -1);
lua_pop(L, 1);
}
}
};
TEST_F(LuaVec3, Constructor_default) { check("Vec3_Constructor_default"); }
TEST_F(LuaVec3, Constructor_xyz) { check("Vec3_Constructor_xyz"); }
TEST_F(LuaVec3, Field_mutation) { check("Vec3_Field_mutation"); }
TEST_F(LuaVec3, Addition) { check("Vec3_Addition"); }
TEST_F(LuaVec3, Subtraction) { check("Vec3_Subtraction"); }
TEST_F(LuaVec3, UnaryMinus) { check("Vec3_UnaryMinus"); }
TEST_F(LuaVec3, Multiplication_scalar) { check("Vec3_Multiplication_scalar"); }
TEST_F(LuaVec3, Multiplication_scalar_reversed) { check("Vec3_Multiplication_scalar_reversed"); }
TEST_F(LuaVec3, Multiplication_vec) { check("Vec3_Multiplication_vec"); }
TEST_F(LuaVec3, Division_scalar) { check("Vec3_Division_scalar"); }
TEST_F(LuaVec3, Division_vec) { check("Vec3_Division_vec"); }
TEST_F(LuaVec3, EqualTo_true) { check("Vec3_EqualTo_true"); }
TEST_F(LuaVec3, EqualTo_false) { check("Vec3_EqualTo_false"); }
TEST_F(LuaVec3, LessThan) { check("Vec3_LessThan"); }
TEST_F(LuaVec3, LessThanOrEqual) { check("Vec3_LessThanOrEqual"); }
TEST_F(LuaVec3, ToString) { check("Vec3_ToString"); }
TEST_F(LuaVec3, Length) { check("Vec3_Length"); }
TEST_F(LuaVec3, Length2d) { check("Vec3_Length2d"); }
TEST_F(LuaVec3, LengthSqr) { check("Vec3_LengthSqr"); }
TEST_F(LuaVec3, Normalized) { check("Vec3_Normalized"); }
TEST_F(LuaVec3, Dot_perpendicular) { check("Vec3_Dot_perpendicular"); }
TEST_F(LuaVec3, Dot_parallel) { check("Vec3_Dot_parallel"); }
TEST_F(LuaVec3, Cross) { check("Vec3_Cross"); }
TEST_F(LuaVec3, DistanceTo) { check("Vec3_DistanceTo"); }
TEST_F(LuaVec3, DistanceToSqr) { check("Vec3_DistanceToSqr"); }
TEST_F(LuaVec3, Sum) { check("Vec3_Sum"); }
TEST_F(LuaVec3, Sum2d) { check("Vec3_Sum2d"); }
TEST_F(LuaVec3, Abs) { check("Vec3_Abs"); }
TEST_F(LuaVec3, PointToSameDirection_true) { check("Vec3_PointToSameDirection_true"); }
TEST_F(LuaVec3, PointToSameDirection_false) { check("Vec3_PointToSameDirection_false"); }
TEST_F(LuaVec3, IsPerpendicular_true) { check("Vec3_IsPerpendicular_true"); }
TEST_F(LuaVec3, IsPerpendicular_false) { check("Vec3_IsPerpendicular_false"); }
TEST_F(LuaVec3, AngleBetween_90deg) { check("Vec3_AngleBetween_90deg"); }
TEST_F(LuaVec3, AngleBetween_zero_vector_error) { check("Vec3_AngleBetween_zero_vector_error"); }
TEST_F(LuaVec3, AsTable) { check("Vec3_AsTable"); }

View File

@@ -0,0 +1,57 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaVec4 : public ::testing::Test
{
protected:
lua_State* L = nullptr;
void SetUp() override
{
L = luaL_newstate();
luaL_openlibs(L);
omath::lua::register_lib(L);
if (luaL_dofile(L, LUA_SCRIPTS_DIR "/vec4_tests.lua") != LUA_OK)
FAIL() << lua_tostring(L, -1);
}
void TearDown() override { lua_close(L); }
void check(const char* func_name)
{
lua_getglobal(L, func_name);
if (lua_pcall(L, 0, 0, 0) != LUA_OK)
{
FAIL() << lua_tostring(L, -1);
lua_pop(L, 1);
}
}
};
TEST_F(LuaVec4, Constructor_default) { check("Vec4_Constructor_default"); }
TEST_F(LuaVec4, Constructor_xyzw) { check("Vec4_Constructor_xyzw"); }
TEST_F(LuaVec4, Field_mutation) { check("Vec4_Field_mutation"); }
TEST_F(LuaVec4, Addition) { check("Vec4_Addition"); }
TEST_F(LuaVec4, Subtraction) { check("Vec4_Subtraction"); }
TEST_F(LuaVec4, UnaryMinus) { check("Vec4_UnaryMinus"); }
TEST_F(LuaVec4, Multiplication_scalar) { check("Vec4_Multiplication_scalar"); }
TEST_F(LuaVec4, Multiplication_scalar_reversed) { check("Vec4_Multiplication_scalar_reversed"); }
TEST_F(LuaVec4, Multiplication_vec) { check("Vec4_Multiplication_vec"); }
TEST_F(LuaVec4, Division_scalar) { check("Vec4_Division_scalar"); }
TEST_F(LuaVec4, Division_vec) { check("Vec4_Division_vec"); }
TEST_F(LuaVec4, EqualTo_true) { check("Vec4_EqualTo_true"); }
TEST_F(LuaVec4, EqualTo_false) { check("Vec4_EqualTo_false"); }
TEST_F(LuaVec4, LessThan) { check("Vec4_LessThan"); }
TEST_F(LuaVec4, LessThanOrEqual) { check("Vec4_LessThanOrEqual"); }
TEST_F(LuaVec4, ToString) { check("Vec4_ToString"); }
TEST_F(LuaVec4, Length) { check("Vec4_Length"); }
TEST_F(LuaVec4, LengthSqr) { check("Vec4_LengthSqr"); }
TEST_F(LuaVec4, Dot) { check("Vec4_Dot"); }
TEST_F(LuaVec4, Dot_perpendicular) { check("Vec4_Dot_perpendicular"); }
TEST_F(LuaVec4, Sum) { check("Vec4_Sum"); }
TEST_F(LuaVec4, Abs) { check("Vec4_Abs"); }
TEST_F(LuaVec4, Clamp) { check("Vec4_Clamp"); }

View File

@@ -0,0 +1,197 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end
local function make_camera()
local pos = omath.Vec3.new(0, 0, 0)
local pitch = omath.source.PitchAngle.from_degrees(0)
local yaw = omath.source.YawAngle.from_degrees(0)
local roll = omath.source.RollAngle.from_degrees(0)
local angles = omath.source.ViewAngles.new(pitch, yaw, roll)
local vp = omath.opengl.ViewPort.new(1920, 1080)
local fov = omath.source.FieldOfView.from_degrees(90)
return omath.source.Camera.new(pos, angles, vp, fov, 0.1, 1000)
end
-- PitchAngle
function Source_PitchAngle_from_degrees()
assert(omath.source.PitchAngle.from_degrees(45):as_degrees() == 45)
end
function Source_PitchAngle_clamping_max()
assert(omath.source.PitchAngle.from_degrees(100):as_degrees() == 89)
end
function Source_PitchAngle_clamping_min()
assert(omath.source.PitchAngle.from_degrees(-100):as_degrees() == -89)
end
function Source_PitchAngle_from_radians()
assert(approx(omath.source.PitchAngle.from_radians(math.pi / 4):as_degrees(), 45))
end
function Source_PitchAngle_as_radians()
assert(approx(omath.source.PitchAngle.from_degrees(0):as_radians(), 0))
end
function Source_PitchAngle_sin()
assert(approx(omath.source.PitchAngle.from_degrees(30):sin(), 0.5))
end
function Source_PitchAngle_cos()
assert(approx(omath.source.PitchAngle.from_degrees(60):cos(), 0.5))
end
function Source_PitchAngle_tan()
assert(approx(omath.source.PitchAngle.from_degrees(45):tan(), 1.0))
end
function Source_PitchAngle_addition()
local c = omath.source.PitchAngle.from_degrees(20) + omath.source.PitchAngle.from_degrees(15)
assert(c:as_degrees() == 35)
end
function Source_PitchAngle_addition_clamped()
local c = omath.source.PitchAngle.from_degrees(80) + omath.source.PitchAngle.from_degrees(20)
assert(c:as_degrees() == 89)
end
function Source_PitchAngle_subtraction()
local c = omath.source.PitchAngle.from_degrees(50) - omath.source.PitchAngle.from_degrees(20)
assert(c:as_degrees() == 30)
end
function Source_PitchAngle_unary_minus()
assert((-omath.source.PitchAngle.from_degrees(45)):as_degrees() == -45)
end
function Source_PitchAngle_equal_to()
local a = omath.source.PitchAngle.from_degrees(45)
assert(a == omath.source.PitchAngle.from_degrees(45))
assert(not (a == omath.source.PitchAngle.from_degrees(30)))
end
function Source_PitchAngle_to_string()
assert(tostring(omath.source.PitchAngle.from_degrees(45)) == "45deg")
end
-- YawAngle
function Source_YawAngle_from_degrees()
assert(omath.source.YawAngle.from_degrees(90):as_degrees() == 90)
end
function Source_YawAngle_normalization()
assert(approx(omath.source.YawAngle.from_degrees(200):as_degrees(), -160))
end
-- RollAngle
function Source_RollAngle_from_degrees()
assert(omath.source.RollAngle.from_degrees(45):as_degrees() == 45)
end
-- FieldOfView
function Source_FieldOfView_from_degrees()
assert(omath.source.FieldOfView.from_degrees(90):as_degrees() == 90)
end
function Source_FieldOfView_clamping()
assert(omath.source.FieldOfView.from_degrees(200):as_degrees() == 180)
end
-- ViewAngles
function Source_ViewAngles_new()
local angles = omath.source.ViewAngles.new(
omath.source.PitchAngle.from_degrees(30),
omath.source.YawAngle.from_degrees(90),
omath.source.RollAngle.from_degrees(0))
assert(angles.pitch:as_degrees() == 30)
assert(angles.yaw:as_degrees() == 90)
assert(angles.roll:as_degrees() == 0)
end
function Source_ViewAngles_field_mutation()
local angles = omath.source.ViewAngles.new(
omath.source.PitchAngle.from_degrees(0),
omath.source.YawAngle.from_degrees(0),
omath.source.RollAngle.from_degrees(0))
angles.pitch = omath.source.PitchAngle.from_degrees(45)
assert(angles.pitch:as_degrees() == 45)
end
-- Camera
function Source_Camera_constructor()
assert(make_camera() ~= nil)
end
function Source_Camera_get_set_origin()
local cam = make_camera()
cam:set_origin(omath.Vec3.new(1, 2, 3))
local o = cam:get_origin()
assert(approx(o.x, 1) and approx(o.y, 2) and approx(o.z, 3))
end
function Source_Camera_get_set_near_plane()
local cam = make_camera()
cam:set_near_plane(0.5)
assert(approx(cam:get_near_plane(), 0.5))
end
function Source_Camera_get_set_far_plane()
local cam = make_camera()
cam:set_far_plane(500)
assert(approx(cam:get_far_plane(), 500))
end
function Source_Camera_get_set_fov()
local cam = make_camera()
cam:set_field_of_view(omath.source.FieldOfView.from_degrees(60))
assert(approx(cam:get_field_of_view():as_degrees(), 60))
end
function Source_Camera_get_set_view_angles()
local cam = make_camera()
cam:set_view_angles(omath.source.ViewAngles.new(
omath.source.PitchAngle.from_degrees(30),
omath.source.YawAngle.from_degrees(90),
omath.source.RollAngle.from_degrees(0)))
assert(approx(cam:get_view_angles().pitch:as_degrees(), 30))
assert(approx(cam:get_view_angles().yaw:as_degrees(), 90))
end
function Source_Camera_look_at()
local cam = make_camera()
cam:look_at(omath.Vec3.new(10, 0, 0))
assert(cam:get_view_angles() ~= nil)
end
function Source_Camera_get_forward()
local fwd = make_camera():get_forward()
assert(approx(fwd:length(), 1.0))
end
function Source_Camera_get_right()
assert(approx(make_camera():get_right():length(), 1.0))
end
function Source_Camera_get_up()
assert(approx(make_camera():get_up():length(), 1.0))
end
function Source_Camera_world_to_screen_success()
local cam = make_camera()
cam:look_at(omath.Vec3.new(1, 0, 0))
local screen, err = cam:world_to_screen(omath.Vec3.new(5, 0, 0))
assert(screen ~= nil, "expected screen pos, got: " .. tostring(err))
end
function Source_Camera_world_to_screen_error()
local cam = make_camera()
cam:look_at(omath.Vec3.new(1, 0, 0))
local screen, err = cam:world_to_screen(omath.Vec3.new(-100, 0, 0))
assert(screen == nil and err ~= nil)
end
function Source_Camera_screen_to_world()
local cam = make_camera()
cam:look_at(omath.Vec3.new(1, 0, 0))
local world, err = cam:screen_to_world(omath.Vec3.new(960, 540, 1))
assert(world ~= nil, "expected world pos, got: " .. tostring(err))
end

102
tests/lua/vec2_tests.lua Normal file
View File

@@ -0,0 +1,102 @@
local function approx(a, b) return math.abs(a - b) < 1e-5 end
function Vec2_Constructor_default()
local v = omath.Vec2.new()
assert(v.x == 0 and v.y == 0)
end
function Vec2_Constructor_xy()
local v = omath.Vec2.new(3, 4)
assert(v.x == 3 and v.y == 4)
end
function Vec2_Field_mutation()
local v = omath.Vec2.new(1, 2)
v.x = 9; v.y = 8
assert(v.x == 9 and v.y == 8)
end
function Vec2_Addition()
local c = omath.Vec2.new(1, 2) + omath.Vec2.new(3, 4)
assert(c.x == 4 and c.y == 6)
end
function Vec2_Subtraction()
local c = omath.Vec2.new(5, 7) - omath.Vec2.new(2, 3)
assert(c.x == 3 and c.y == 4)
end
function Vec2_UnaryMinus()
local b = -omath.Vec2.new(1, 2)
assert(b.x == -1 and b.y == -2)
end
function Vec2_Multiplication_scalar()
local b = omath.Vec2.new(2, 3) * 2.0
assert(b.x == 4 and b.y == 6)
end
function Vec2_Multiplication_scalar_reversed()
local b = 2.0 * omath.Vec2.new(2, 3)
assert(b.x == 4 and b.y == 6)
end
function Vec2_Division_scalar()
local b = omath.Vec2.new(4, 6) / 2.0
assert(b.x == 2 and b.y == 3)
end
function Vec2_EqualTo_true()
assert(omath.Vec2.new(1, 2) == omath.Vec2.new(1, 2))
end
function Vec2_EqualTo_false()
assert(not (omath.Vec2.new(1, 2) == omath.Vec2.new(9, 9)))
end
function Vec2_LessThan()
assert(omath.Vec2.new(1, 0) < omath.Vec2.new(3, 4))
end
function Vec2_LessThanOrEqual()
-- (3,4) and (4,3) both have length 5
assert(omath.Vec2.new(3, 4) <= omath.Vec2.new(4, 3))
end
function Vec2_ToString()
assert(tostring(omath.Vec2.new(1, 2)) == "Vec2(1, 2)")
end
function Vec2_Length()
assert(approx(omath.Vec2.new(3, 4):length(), 5.0))
end
function Vec2_LengthSqr()
assert(omath.Vec2.new(3, 4):length_sqr() == 25.0)
end
function Vec2_Normalized()
local n = omath.Vec2.new(3, 4):normalized()
assert(approx(n.x, 0.6) and approx(n.y, 0.8))
end
function Vec2_Dot()
assert(omath.Vec2.new(1, 2):dot(omath.Vec2.new(3, 4)) == 11.0)
end
function Vec2_DistanceTo()
assert(approx(omath.Vec2.new(0, 0):distance_to(omath.Vec2.new(3, 4)), 5.0))
end
function Vec2_DistanceToSqr()
assert(omath.Vec2.new(0, 0):distance_to_sqr(omath.Vec2.new(3, 4)) == 25.0)
end
function Vec2_Sum()
assert(omath.Vec2.new(3, 4):sum() == 7.0)
end
function Vec2_Abs()
local a = omath.Vec2.new(-3, -4):abs()
assert(a.x == 3 and a.y == 4)
end

163
tests/lua/vec3_tests.lua Normal file
View File

@@ -0,0 +1,163 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end
function Vec3_Constructor_default()
local v = omath.Vec3.new()
assert(v.x == 0 and v.y == 0 and v.z == 0)
end
function Vec3_Constructor_xyz()
local v = omath.Vec3.new(1, 2, 3)
assert(v.x == 1 and v.y == 2 and v.z == 3)
end
function Vec3_Field_mutation()
local v = omath.Vec3.new(1, 2, 3)
v.x = 9; v.y = 8; v.z = 7
assert(v.x == 9 and v.y == 8 and v.z == 7)
end
function Vec3_Addition()
local c = omath.Vec3.new(1, 2, 3) + omath.Vec3.new(4, 5, 6)
assert(c.x == 5 and c.y == 7 and c.z == 9)
end
function Vec3_Subtraction()
local c = omath.Vec3.new(4, 5, 6) - omath.Vec3.new(1, 2, 3)
assert(c.x == 3 and c.y == 3 and c.z == 3)
end
function Vec3_UnaryMinus()
local b = -omath.Vec3.new(1, 2, 3)
assert(b.x == -1 and b.y == -2 and b.z == -3)
end
function Vec3_Multiplication_scalar()
local b = omath.Vec3.new(1, 2, 3) * 2.0
assert(b.x == 2 and b.y == 4 and b.z == 6)
end
function Vec3_Multiplication_scalar_reversed()
local b = 2.0 * omath.Vec3.new(1, 2, 3)
assert(b.x == 2 and b.y == 4 and b.z == 6)
end
function Vec3_Multiplication_vec()
local c = omath.Vec3.new(2, 3, 4) * omath.Vec3.new(2, 2, 2)
assert(c.x == 4 and c.y == 6 and c.z == 8)
end
function Vec3_Division_scalar()
local b = omath.Vec3.new(2, 4, 6) / 2.0
assert(b.x == 1 and b.y == 2 and b.z == 3)
end
function Vec3_Division_vec()
local c = omath.Vec3.new(4, 6, 8) / omath.Vec3.new(2, 2, 2)
assert(c.x == 2 and c.y == 3 and c.z == 4)
end
function Vec3_EqualTo_true()
assert(omath.Vec3.new(1, 2, 3) == omath.Vec3.new(1, 2, 3))
end
function Vec3_EqualTo_false()
assert(not (omath.Vec3.new(1, 2, 3) == omath.Vec3.new(9, 9, 9)))
end
function Vec3_LessThan()
assert(omath.Vec3.new(1, 0, 0) < omath.Vec3.new(3, 4, 0))
end
function Vec3_LessThanOrEqual()
-- (0,3,4) and (0,4,3) both have length 5
assert(omath.Vec3.new(0, 3, 4) <= omath.Vec3.new(0, 4, 3))
end
function Vec3_ToString()
assert(tostring(omath.Vec3.new(1, 2, 3)) == "Vec3(1, 2, 3)")
end
function Vec3_Length()
assert(approx(omath.Vec3.new(1, 2, 2):length(), 3.0))
end
function Vec3_Length2d()
assert(approx(omath.Vec3.new(3, 4, 99):length_2d(), 5.0))
end
function Vec3_LengthSqr()
assert(omath.Vec3.new(1, 2, 2):length_sqr() == 9.0)
end
function Vec3_Normalized()
local n = omath.Vec3.new(3, 0, 0):normalized()
assert(approx(n.x, 1.0) and approx(n.y, 0.0) and approx(n.z, 0.0))
end
function Vec3_Dot_perpendicular()
assert(omath.Vec3.new(1, 0, 0):dot(omath.Vec3.new(0, 1, 0)) == 0.0)
end
function Vec3_Dot_parallel()
local a = omath.Vec3.new(1, 2, 3)
assert(a:dot(a) == 14.0)
end
function Vec3_Cross()
local c = omath.Vec3.new(1, 0, 0):cross(omath.Vec3.new(0, 1, 0))
assert(approx(c.x, 0) and approx(c.y, 0) and approx(c.z, 1))
end
function Vec3_DistanceTo()
assert(approx(omath.Vec3.new(0, 0, 0):distance_to(omath.Vec3.new(1, 2, 2)), 3.0))
end
function Vec3_DistanceToSqr()
assert(omath.Vec3.new(0, 0, 0):distance_to_sqr(omath.Vec3.new(1, 2, 2)) == 9.0)
end
function Vec3_Sum()
assert(omath.Vec3.new(1, 2, 3):sum() == 6.0)
end
function Vec3_Sum2d()
assert(omath.Vec3.new(1, 2, 3):sum_2d() == 3.0)
end
function Vec3_Abs()
local a = omath.Vec3.new(-1, -2, -3):abs()
assert(a.x == 1 and a.y == 2 and a.z == 3)
end
function Vec3_PointToSameDirection_true()
assert(omath.Vec3.new(1, 1, 0):point_to_same_direction(omath.Vec3.new(2, 2, 0)) == true)
end
function Vec3_PointToSameDirection_false()
assert(omath.Vec3.new(1, 0, 0):point_to_same_direction(omath.Vec3.new(-1, 0, 0)) == false)
end
function Vec3_IsPerpendicular_true()
assert(omath.Vec3.new(1, 0, 0):is_perpendicular(omath.Vec3.new(0, 1, 0)) == true)
end
function Vec3_IsPerpendicular_false()
local a = omath.Vec3.new(1, 0, 0)
assert(a:is_perpendicular(a) == false)
end
function Vec3_AngleBetween_90deg()
local angle, err = omath.Vec3.new(1, 0, 0):angle_between(omath.Vec3.new(0, 1, 0))
assert(angle ~= nil, err)
assert(math.abs(angle - 90.0) < 1e-3)
end
function Vec3_AngleBetween_zero_vector_error()
local angle, err = omath.Vec3.new(0, 0, 0):angle_between(omath.Vec3.new(1, 0, 0))
assert(angle == nil and err ~= nil)
end
function Vec3_AsTable()
local t = omath.Vec3.new(1, 2, 3):as_table()
assert(t.x == 1 and t.y == 2 and t.z == 3)
end

110
tests/lua/vec4_tests.lua Normal file
View File

@@ -0,0 +1,110 @@
local function approx(a, b) return math.abs(a - b) < 1e-5 end
function Vec4_Constructor_default()
local v = omath.Vec4.new()
assert(v.x == 0 and v.y == 0 and v.z == 0 and v.w == 0)
end
function Vec4_Constructor_xyzw()
local v = omath.Vec4.new(1, 2, 3, 4)
assert(v.x == 1 and v.y == 2 and v.z == 3 and v.w == 4)
end
function Vec4_Field_mutation()
local v = omath.Vec4.new(1, 2, 3, 4)
v.w = 99
assert(v.w == 99)
end
function Vec4_Addition()
local c = omath.Vec4.new(1, 2, 3, 4) + omath.Vec4.new(4, 3, 2, 1)
assert(c.x == 5 and c.y == 5 and c.z == 5 and c.w == 5)
end
function Vec4_Subtraction()
local c = omath.Vec4.new(5, 5, 5, 5) - omath.Vec4.new(1, 2, 3, 4)
assert(c.x == 4 and c.y == 3 and c.z == 2 and c.w == 1)
end
function Vec4_UnaryMinus()
local b = -omath.Vec4.new(1, 2, 3, 4)
assert(b.x == -1 and b.y == -2 and b.z == -3 and b.w == -4)
end
function Vec4_Multiplication_scalar()
local b = omath.Vec4.new(1, 2, 3, 4) * 2.0
assert(b.x == 2 and b.y == 4 and b.z == 6 and b.w == 8)
end
function Vec4_Multiplication_scalar_reversed()
local b = 2.0 * omath.Vec4.new(1, 2, 3, 4)
assert(b.x == 2 and b.y == 4 and b.z == 6 and b.w == 8)
end
function Vec4_Multiplication_vec()
local c = omath.Vec4.new(2, 3, 4, 5) * omath.Vec4.new(2, 2, 2, 2)
assert(c.x == 4 and c.y == 6 and c.z == 8 and c.w == 10)
end
function Vec4_Division_scalar()
local b = omath.Vec4.new(2, 4, 6, 8) / 2.0
assert(b.x == 1 and b.y == 2 and b.z == 3 and b.w == 4)
end
function Vec4_Division_vec()
local c = omath.Vec4.new(4, 6, 8, 10) / omath.Vec4.new(2, 2, 2, 2)
assert(c.x == 2 and c.y == 3 and c.z == 4 and c.w == 5)
end
function Vec4_EqualTo_true()
assert(omath.Vec4.new(1, 2, 3, 4) == omath.Vec4.new(1, 2, 3, 4))
end
function Vec4_EqualTo_false()
assert(not (omath.Vec4.new(1, 2, 3, 4) == omath.Vec4.new(9, 9, 9, 9)))
end
function Vec4_LessThan()
assert(omath.Vec4.new(1, 0, 0, 0) < omath.Vec4.new(0, 0, 3, 4))
end
function Vec4_LessThanOrEqual()
-- (0,0,3,4) and (0,0,4,3) both have length 5
assert(omath.Vec4.new(0, 0, 3, 4) <= omath.Vec4.new(0, 0, 4, 3))
end
function Vec4_ToString()
assert(tostring(omath.Vec4.new(1, 2, 3, 4)) == "Vec4(1, 2, 3, 4)")
end
function Vec4_Length()
assert(approx(omath.Vec4.new(0, 0, 3, 4):length(), 5.0))
end
function Vec4_LengthSqr()
assert(omath.Vec4.new(0, 0, 3, 4):length_sqr() == 25.0)
end
function Vec4_Dot()
local a = omath.Vec4.new(1, 2, 3, 4)
assert(a:dot(a) == 30.0)
end
function Vec4_Dot_perpendicular()
assert(omath.Vec4.new(1, 0, 0, 0):dot(omath.Vec4.new(0, 1, 0, 0)) == 0.0)
end
function Vec4_Sum()
assert(omath.Vec4.new(1, 2, 3, 4):sum() == 10.0)
end
function Vec4_Abs()
local a = omath.Vec4.new(-1, -2, -3, -4):abs()
assert(a.x == 1 and a.y == 2 and a.z == 3 and a.w == 4)
end
function Vec4_Clamp()
local v = omath.Vec4.new(5, -3, 10, 99)
v:clamp(0, 7)
assert(v.x == 5 and v.y == 0 and v.z == 7)
end

View File

@@ -0,0 +1,29 @@
local a = omath.Vec2.new(1, 2)
local b = omath.Vec2.new(10, 20)
-- Operators
local c = a + b
local d = a - b
local e = a * 2.0
local f = -a
print("a + b = " .. tostring(c))
print("a - b = " .. tostring(d))
print("a * 2 = " .. tostring(e))
print("-a = " .. tostring(f))
print("a == Vec2(1,2): " .. tostring(a == omath.Vec2.new(1, 2)))
print("a < b: " .. tostring(a < b))
-- Field access + mutation
print("c.x = " .. c.x .. ", c.y = " .. c.y)
c.x = 99
print("c.x after mutation = " .. c.x)
-- Methods
print("a:length() = " .. a:length())
print("a:length_sqr() = " .. a:length_sqr())
print("a:normalized() = " .. tostring(a:normalized()))
print("a:dot(b) = " .. a:dot(b))
print("a:distance_to(b) = " .. a:distance_to(b))
print("a:distance_to_sqr(b) = " .. a:distance_to_sqr(b))
print("a:sum() = " .. a:sum())
print("a:abs() = " .. tostring(a:abs()))

View File

@@ -0,0 +1,55 @@
local a = omath.Vec3.new(1, 0, 0)
local b = omath.Vec3.new(0, 1, 0)
-- Operators
local c = a + b
local d = a - b
local e = a * 2.0
local f = -a
print("a + b = " .. tostring(c))
print("a - b = " .. tostring(d))
print("a * 2 = " .. tostring(e))
print("-a = " .. tostring(f))
print("a == Vec3(1,2,3): " .. tostring(a == omath.Vec3.new(1, 2, 3)))
print("a < b: " .. tostring(a < b))
-- Field access + mutation
print("c.x = " .. c.x .. ", c.y = " .. c.y .. ", c.z = " .. c.z)
c.x = 99
print("c.x after mutation = " .. c.x)
-- Methods
print("a:length() = " .. a:length())
print("a:length_2d() = " .. a:length_2d())
print("a:length_sqr() = " .. a:length_sqr())
print("a:normalized() = " .. tostring(a:normalized()))
print("a:dot(b) = " .. a:dot(b))
print("a:cross(b) = " .. tostring(a:cross(b)))
print("a:distance_to(b) = " .. a:distance_to(b))
print("a:distance_to_sqr(b) = " .. a:distance_to_sqr(b))
print("a:abs() = " .. tostring(a:abs()))
print("a:sum() = " .. a:sum())
print("a:sum_2d() = " .. a:sum_2d())
print("a:point_to_same_direction(b) = " .. tostring(a:point_to_same_direction(b)))
print("a:is_perpendicular(b) = " .. tostring(a:is_perpendicular(b)))
-- angle_between
local angle, err = a:angle_between(b)
if angle then
print("angle_between = " .. angle .. " degrees")
else
print("angle_between error: " .. err)
end
-- Zero vector edge case
local zero = omath.Vec3.new(0, 0, 0)
local ang2, err2 = zero:angle_between(a)
if ang2 then
print("zero angle = " .. ang2)
else
print("zero angle error: " .. err2)
end
-- as_table
local t = a:as_table()
print("as_table: x=" .. t.x .. " y=" .. t.y .. " z=" .. t.z)

View File

@@ -0,0 +1,31 @@
local a = omath.Vec4.new(1, 2, 3, 4)
local b = omath.Vec4.new(10, 20, 30, 40)
-- Operators
local c = a + b
local d = a - b
local e = a * 2.0
local f = -a
print("a + b = " .. tostring(c))
print("a - b = " .. tostring(d))
print("a * 2 = " .. tostring(e))
print("-a = " .. tostring(f))
print("a == Vec4(1,2,3,4): " .. tostring(a == omath.Vec4.new(1, 2, 3, 4)))
print("a < b: " .. tostring(a < b))
-- Field access + mutation
print("c.x=" .. c.x .. " c.y=" .. c.y .. " c.z=" .. c.z .. " c.w=" .. c.w)
c.w = 99
print("c.w after mutation = " .. c.w)
-- Methods
print("a:length() = " .. a:length())
print("a:length_sqr() = " .. a:length_sqr())
print("a:dot(b) = " .. a:dot(b))
print("a:sum() = " .. a:sum())
print("a:abs() = " .. tostring(a:abs()))
-- clamp
local clamped = omath.Vec4.new(5, -3, 10, 1)
clamped:clamp(0, 7)
print("clamp([5,-3,10,1], 0, 7).x=" .. clamped.x .. " .y=" .. clamped.y .. " .z=" .. clamped.z)

View File

@@ -17,7 +17,7 @@
], ],
"features": { "features": {
"avx2": { "avx2": {
"description": "Omath will use AVX2 to boost performance", "description": "omath will use AVX2 to boost performance",
"supports": "!arm" "supports": "!arm"
}, },
"benchmark": { "benchmark": {
@@ -35,7 +35,7 @@
] ]
}, },
"imgui": { "imgui": {
"description": "Omath will define method to convert omath types to imgui types", "description": "omath will define method to convert omath types to imgui types",
"dependencies": [ "dependencies": [
"imgui" "imgui"
] ]
@@ -45,6 +45,13 @@
"dependencies": [ "dependencies": [
"gtest" "gtest"
] ]
},
"lua": {
"description": "lua support for omath",
"dependencies": [
"lua",
"sol2"
]
} }
} }
} }