diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 2c6b9858..a33fe073 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -107,7 +107,7 @@ jobs: -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ -DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" - name: Build shell: bash @@ -193,7 +193,7 @@ jobs: -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ -DOMATH_ENABLE_COVERAGE=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" - name: Build shell: bash @@ -234,7 +234,7 @@ jobs: -DOMATH_ENABLE_COVERAGE=ON \ -DOMATH_THREAT_WARNING_AS_ERROR=OFF \ -DCMAKE_BUILD_TYPE=Debug \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" cmake --build cmake-build/build/${{ matrix.preset }} --config Debug --target unit_tests omath - name: Run Tests (Generates .profraw) @@ -373,7 +373,7 @@ jobs: -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ -DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" - name: Build shell: bash @@ -450,7 +450,7 @@ jobs: -DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \ -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;tests;lua" - name: Build shell: bash @@ -509,7 +509,7 @@ jobs: cmake --preset ${{ matrix.preset }} \ -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" \ + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;lua" \ -DVCPKG_INSTALL_OPTIONS="--allow-unsupported" cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath ./out/Release/unit_tests @@ -581,7 +581,7 @@ jobs: -DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \ -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;tests;lua" - name: Build shell: bash @@ -650,7 +650,7 @@ jobs: -DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \ -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;tests;lua" - name: Build shell: bash @@ -735,7 +735,7 @@ jobs: -DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \ -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=OFF \ - -DVCPKG_MANIFEST_FEATURES="imgui;tests" + -DVCPKG_MANIFEST_FEATURES="imgui;tests;lua" - name: Build run: | @@ -800,7 +800,7 @@ jobs: -DOMATH_BUILD_TESTS=ON \ -DOMATH_BUILD_BENCHMARK=ON \ -DOMATH_ENABLE_VALGRIND=ON \ - -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests;benchmark" + -DVCPKG_MANIFEST_FEATURES="imgui;avx2;lua;tests;benchmark" - name: Build All Targets shell: bash diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..4ffc1da9 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,5 @@ +{ + "diagnostics.globals": [ + "omath" + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 29a20e7d..ff324be4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ option(OMATH_SUPRESS_SAFETY_CHECKS option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_FORCE_INLINE "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) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) if(omath_feature STREQUAL "imgui") @@ -43,6 +46,8 @@ if(VCPKG_MANIFEST_FEATURES) set(OMATH_BUILD_BENCHMARK ON) elseif(omath_feature STREQUAL "examples") set(OMATH_BUILD_EXAMPLES ON) + elseif(omath_feature STREQUAL "lua") + set(OMATH_ENABLE_LUA ON) endif() endforeach() @@ -72,6 +77,7 @@ if(${PROJECT_IS_TOP_LEVEL}) message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}") message(STATUS "[${PROJECT_NAME}]: Coverage feature status ${OMATH_ENABLE_COVERAGE}") message(STATUS "[${PROJECT_NAME}]: Valgrind feature status ${OMATH_ENABLE_VALGRIND}") + message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}") endif() file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") @@ -83,6 +89,17 @@ else() add_library(${PROJECT_NAME} STATIC ${OMATH_SOURCES} ${OMATH_HEADERS}) 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}) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") @@ -174,6 +191,12 @@ elseif(OMATH_THREAT_WARNING_AS_ERROR) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror) endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(${PROJECT_NAME} PRIVATE /bigobj) +endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_HOST_SYSTEM_NAME EQUAL "Windows") + target_compile_options(${PROJECT_NAME} PRIVATE -mbig-obj) +endif() # Windows SDK redefine min/max via preprocessor and break std::min and std::max if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_definitions(${PROJECT_NAME} INTERFACE NOMINMAX) diff --git a/CMakePresets.json b/CMakePresets.json index 5efed251..8d27ee1d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -145,7 +145,7 @@ "hidden": true, "inherits": ["linux-base", "vcpkg-base"], "cacheVariables": { - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2" + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;lua" } }, { @@ -235,7 +235,7 @@ "hidden": true, "inherits": ["darwin-base", "vcpkg-base"], "cacheVariables": { - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples" + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;lua" } }, { diff --git a/README.md b/README.md index 260e81b5..ec2a968d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![CodeFactor](https://www.codefactor.io/repository/github/orange-cpp/omath/badge)](https://www.codefactor.io/repository/github/orange-cpp/omath) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml) [![Vcpkg package](https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg)](https://repology.org/project/orange-math/versions) +![Conan Center](https://img.shields.io/conan/v/omath) ![GitHub forks](https://img.shields.io/github/forks/orange-cpp/omath) [![discord badge](https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat)](https://discord.gg/eDgdaWbqwZ) [![telegram badge](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white)](https://t.me/orangennotes) @@ -83,6 +84,7 @@ if (auto screen = camera.world_to_screen(world_position)) { - **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**. - **Cross platform**: Supports Windows, MacOS and Linux. - **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps. +- **Scripting**: Supports to make scripts in Lua out of box - **Battle tested**: It's already used by some big players on the market like wraith.su and bluedream.ltd
diff --git a/include/omath/lua/lua.hpp b/include/omath/lua/lua.hpp new file mode 100644 index 00000000..45c0942f --- /dev/null +++ b/include/omath/lua/lua.hpp @@ -0,0 +1,25 @@ +// +// Created by orange on 07.03.2026. +// +#pragma once +#ifdef OMATH_ENABLE_LUA +#include +namespace omath::lua +{ + class LuaInterpreter final + { + public: + static void register_lib(lua_State* lua_state); + + private: + static void register_vec2(sol::table& omath_table); + static void register_vec3(sol::table& omath_table); + static void register_vec4(sol::table& omath_table); + static void register_color(sol::table& omath_table); + static void register_triangle(sol::table& omath_table); + static void register_shared_types(sol::table& omath_table); + static void register_engines(sol::table& omath_table); + static void register_pattern_scan(sol::table& omath_table); + }; +} +#endif \ No newline at end of file diff --git a/source/lua/lua.cpp b/source/lua/lua.cpp new file mode 100644 index 00000000..0d226cea --- /dev/null +++ b/source/lua/lua.cpp @@ -0,0 +1,27 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "lua.hpp" +#include +#include "omath/lua/lua.hpp" + +namespace omath::lua +{ + void LuaInterpreter::register_lib(lua_State* lua_state) + { + sol::state_view lua(lua_state); + + auto omath_table = lua["omath"].get_or_create(); + + register_vec2(omath_table); + register_vec3(omath_table); + register_vec4(omath_table); + register_color(omath_table); + register_triangle(omath_table); + register_shared_types(omath_table); + register_engines(omath_table); + register_pattern_scan(omath_table); + } +} // namespace omath::lua +#endif diff --git a/source/lua/lua_color.cpp b/source/lua/lua_color.cpp new file mode 100644 index 00000000..8e84fdaf --- /dev/null +++ b/source/lua/lua_color.cpp @@ -0,0 +1,46 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include + +namespace omath::lua +{ + void LuaInterpreter::register_color(sol::table& omath_table) + { + omath_table.new_usertype( + "Color", + sol::factories([](float r, float g, float b, float a) { return omath::Color(r, g, b, a); }, + []() { return omath::Color(); }), + + "from_rgba", [](uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { return omath::Color::from_rgba(r, g, b, a); }, "from_hsv", + sol::overload([](float h, float s, float v) { return omath::Color::from_hsv(h, s, v); }, + [](const omath::Hsv& hsv) { return omath::Color::from_hsv(hsv); }), + "red", []() { return omath::Color::red(); }, "green", []() { return omath::Color::green(); }, "blue", + []() { return omath::Color::blue(); }, + + "r", sol::property([](const omath::Color& c) { return c.value().x; }), "g", + sol::property([](const omath::Color& c) { return c.value().y; }), "b", + sol::property([](const omath::Color& c) { return c.value().z; }), "a", + sol::property([](const omath::Color& c) { return c.value().w; }), + + "to_hsv", &omath::Color::to_hsv, "set_hue", &omath::Color::set_hue, "set_saturation", + &omath::Color::set_saturation, "set_value", &omath::Color::set_value, "blend", &omath::Color::blend, + + sol::meta_function::to_string, &omath::Color::to_string); + + omath_table.new_usertype( + "Hsv", sol::constructors(), "hue", + sol::property([](const omath::Hsv& h) { return h.hue; }, [](omath::Hsv& h, float val) { h.hue = val; }), + "saturation", + sol::property([](const omath::Hsv& h) { return h.saturation; }, + [](omath::Hsv& h, float val) { h.saturation = val; }), + "value", + sol::property([](const omath::Hsv& h) { return h.value; }, + [](omath::Hsv& h, float val) { h.value = val; })); + } +} // namespace omath::lua::detail +#endif diff --git a/source/lua/lua_engines.cpp b/source/lua/lua_engines.cpp new file mode 100644 index 00000000..0467446b --- /dev/null +++ b/source/lua/lua_engines.cpp @@ -0,0 +1,227 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + // ---- Canonical shared C++ type aliases ---------------------------------- + // Each unique template instantiation must be registered exactly once. + + using PitchAngle90 = omath::Angle; + using PitchAngle89 = omath::Angle; + using SharedYawRoll = omath::Angle; + using SharedFoV = omath::Angle; + using ViewAngles90 = omath::ViewAngles; + using ViewAngles89 = omath::ViewAngles; + + 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 + void register_angle(sol::table& table, const char* name) + { + table.new_usertype( + 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()); }); + } + + // Set aliases in an engine subtable pointing to the already-registered shared types + template + void set_engine_aliases(sol::table& engine_table, sol::table& types) + { + if constexpr (std::is_same_v) + 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) + engine_table["ViewAngles"] = types["ViewAngles90"]; + else + engine_table["ViewAngles"] = types["ViewAngles89"]; + } + + // Register an engine: alias shared types, register unique Camera + template + 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(); + auto types = omath_table["_types"].get(); + + set_engine_aliases(engine_table, types); + + engine_table.new_usertype( + "Camera", + sol::constructors&, 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& pos) + -> std::tuple>, sol::optional> + { + 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& pos) + -> std::tuple>, sol::optional> + { + 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 LuaInterpreter::register_shared_types(sol::table& omath_table) + { + auto t = omath_table["_types"].get_or_create(); + + register_angle(t, "PitchAngle90"); + register_angle(t, "PitchAngle89"); + register_angle(t, "YawRoll"); + register_angle(t, "FieldOfView"); + + t.new_usertype( + "ViewPort", sol::factories([](float w, float h) { return omath::projection::ViewPort{w, h}; }), "width", + sol::property([](const omath::projection::ViewPort& vp) { return vp.m_width; }, + [](omath::projection::ViewPort& vp, float val) { vp.m_width = val; }), + "height", + sol::property([](const omath::projection::ViewPort& vp) { return vp.m_height; }, + [](omath::projection::ViewPort& vp, float val) { vp.m_height = val; }), + "aspect_ratio", &omath::projection::ViewPort::aspect_ratio); + + t.new_usertype( + "ViewAngles90", + sol::factories([](PitchAngle90 p, SharedYawRoll y, SharedYawRoll r) { return ViewAngles90{p, y, r}; }), + "pitch", + sol::property([](const ViewAngles90& va) { return va.pitch; }, + [](ViewAngles90& va, const PitchAngle90& val) { va.pitch = val; }), + "yaw", + sol::property([](const ViewAngles90& va) { return va.yaw; }, + [](ViewAngles90& va, const SharedYawRoll& val) { va.yaw = val; }), + "roll", + sol::property([](const ViewAngles90& va) { return va.roll; }, + [](ViewAngles90& va, const SharedYawRoll& val) { va.roll = val; })); + + t.new_usertype( + "ViewAngles89", + sol::factories([](PitchAngle89 p, SharedYawRoll y, SharedYawRoll r) { return ViewAngles89{p, y, r}; }), + "pitch", + sol::property([](const ViewAngles89& va) { return va.pitch; }, + [](ViewAngles89& va, const PitchAngle89& val) { va.pitch = val; }), + "yaw", + sol::property([](const ViewAngles89& va) { return va.yaw; }, + [](ViewAngles89& va, const SharedYawRoll& val) { va.yaw = val; }), + "roll", + sol::property([](const ViewAngles89& va) { return va.roll; }, + [](ViewAngles89& va, const SharedYawRoll& val) { va.roll = val; })); + } + + void LuaInterpreter::register_engines(sol::table& omath_table) + { + register_engine(omath_table, "opengl"); + register_engine(omath_table, "frostbite"); + register_engine(omath_table, "iw"); + register_engine(omath_table, "source"); + register_engine(omath_table, "unity"); + register_engine(omath_table, "unreal"); + register_engine(omath_table, "cry"); + } +} // namespace omath::lua::detail +#endif diff --git a/source/lua/lua_pattern_scan.cpp b/source/lua/lua_pattern_scan.cpp new file mode 100644 index 00000000..56345a4f --- /dev/null +++ b/source/lua/lua_pattern_scan.cpp @@ -0,0 +1,104 @@ +// +// Created by orange on 10.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace omath::lua +{ + void LuaInterpreter::register_pattern_scan(sol::table& omath_table) + { + omath_table.new_usertype( + "SectionScanResult", sol::no_constructor, + "virtual_base_addr", + sol::property([](const SectionScanResult& r) { return r.virtual_base_addr; }), + "raw_base_addr", + sol::property([](const SectionScanResult& r) { return r.raw_base_addr; }), + "target_offset", + sol::property([](const SectionScanResult& r) { return r.target_offset; }), + sol::meta_function::to_string, + [](const SectionScanResult& r) + { + return std::format("SectionScanResult(vbase=0x{:X}, raw_base=0x{:X}, offset={})", + r.virtual_base_addr, r.raw_base_addr, r.target_offset); + }); + + // Generic scanner: accepts a Lua string as a byte buffer + auto ps_table = omath_table["PatternScanner"].get_or_create(); + ps_table["scan"] = [](const std::string& data, const std::string& pattern) -> sol::optional + { + const auto* begin = reinterpret_cast(data.data()); + const auto* end = begin + data.size(); + const auto* result = PatternScanner::scan_for_pattern(begin, end, pattern); + if (result == end) + return sol::nullopt; + return std::distance(begin, result); + }; + + auto pe_table = omath_table["PePatternScanner"].get_or_create(); + pe_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = PePatternScanner::scan_for_pattern_in_loaded_module(reinterpret_cast(base_addr), + pattern, section.value_or(".text")); + if (!result) + return sol::nullopt; + return *result; + }; + pe_table["scan_in_file"] = [](const std::string& path, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = PePatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern, + section.value_or(".text")); + if (!result) + return sol::nullopt; + return *result; + }; + auto elf_table = omath_table["ElfPatternScanner"].get_or_create(); + elf_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = ElfPatternScanner::scan_for_pattern_in_loaded_module(reinterpret_cast(base_addr), + pattern, section.value_or(".text")); + if (!result) + return sol::nullopt; + return *result; + }; + elf_table["scan_in_file"] = [](const std::string& path, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = ElfPatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern, + section.value_or(".text")); + if (!result) + return sol::nullopt; + return *result; + }; + auto macho_table = omath_table["MachOPatternScanner"].get_or_create(); + macho_table["scan_in_module"] = [](std::uintptr_t base_addr, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = MachOPatternScanner::scan_for_pattern_in_loaded_module( + reinterpret_cast(base_addr), pattern, section.value_or("__text")); + if (!result) + return sol::nullopt; + return *result; + }; + macho_table["scan_in_file"] = [](const std::string& path, const std::string& pattern, + sol::optional section) -> sol::optional + { + auto result = MachOPatternScanner::scan_for_pattern_in_file(std::filesystem::path(path), pattern, + section.value_or("__text")); + if (!result) + return sol::nullopt; + return *result; + }; + } +} // namespace omath::lua \ No newline at end of file diff --git a/source/lua/lua_triangle.cpp b/source/lua/lua_triangle.cpp new file mode 100644 index 00000000..28ecdcfc --- /dev/null +++ b/source/lua/lua_triangle.cpp @@ -0,0 +1,48 @@ +// +// Created by orange on 10.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include + +namespace omath::lua +{ + void LuaInterpreter::register_triangle(sol::table& omath_table) + { + using Vec3f = omath::Vector3; + using Tri3f = omath::Triangle; + + omath_table.new_usertype( + "Triangle", sol::constructors(), + + "vertex1", + sol::property([](const Tri3f& t) { return t.m_vertex1; }, + [](Tri3f& t, const Vec3f& v) { t.m_vertex1 = v; }), + "vertex2", + sol::property([](const Tri3f& t) { return t.m_vertex2; }, + [](Tri3f& t, const Vec3f& v) { t.m_vertex2 = v; }), + "vertex3", + sol::property([](const Tri3f& t) { return t.m_vertex3; }, + [](Tri3f& t, const Vec3f& v) { t.m_vertex3 = v; }), + + "calculate_normal", &Tri3f::calculate_normal, + "side_a_length", &Tri3f::side_a_length, + "side_b_length", &Tri3f::side_b_length, + "side_a_vector", &Tri3f::side_a_vector, + "side_b_vector", &Tri3f::side_b_vector, + "hypot", &Tri3f::hypot, + "is_rectangular", &Tri3f::is_rectangular, + "mid_point", &Tri3f::mid_point, + + sol::meta_function::to_string, + [](const Tri3f& t) + { + return std::format("Triangle(({}, {}, {}), ({}, {}, {}), ({}, {}, {}))", + t.m_vertex1.x, t.m_vertex1.y, t.m_vertex1.z, + t.m_vertex2.x, t.m_vertex2.y, t.m_vertex2.z, + t.m_vertex3.x, t.m_vertex3.y, t.m_vertex3.z); + }); + } +} // namespace omath::lua +#endif diff --git a/source/lua/lua_vec2.cpp b/source/lua/lua_vec2.cpp new file mode 100644 index 00000000..e4196213 --- /dev/null +++ b/source/lua/lua_vec2.cpp @@ -0,0 +1,54 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include + +namespace omath::lua +{ + void LuaInterpreter::register_vec2(sol::table& omath_table) + { + using Vec2f = omath::Vector2; + + omath_table.new_usertype( + "Vec2", sol::constructors(), + + "x", sol::property([](const Vec2f& v) { return v.x; }, [](Vec2f& v, const float val) { v.x = val; }), + "y", sol::property([](const Vec2f& v) { return v.y; }, [](Vec2f& v, const float val) { v.y = val; }), + + sol::meta_function::addition, sol::resolve(&Vec2f::operator+), + sol::meta_function::subtraction, sol::resolve(&Vec2f::operator-), + sol::meta_function::unary_minus, sol::resolve(&Vec2f::operator-), + sol::meta_function::equal_to, &Vec2f::operator==, + sol::meta_function::less_than, sol::resolve(&Vec2f::operator<), + sol::meta_function::less_than_or_equal_to, sol::resolve(&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::operator*), + [](const float s, const Vec2f& v) { return v * s; }), + + sol::meta_function::division, + sol::resolve(&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; + }); + } +} // namespace omath::lua::detail +#endif diff --git a/source/lua/lua_vec3.cpp b/source/lua/lua_vec3.cpp new file mode 100644 index 00000000..1edb4e84 --- /dev/null +++ b/source/lua/lua_vec3.cpp @@ -0,0 +1,81 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include + +namespace omath::lua +{ + void LuaInterpreter::register_vec3(sol::table& omath_table) + { + using Vec3f = omath::Vector3; + + omath_table.new_usertype( + "Vec3", sol::constructors(), + + "x", sol::property([](const Vec3f& v) { return v.x; }, [](Vec3f& v, float val) { v.x = val; }), + "y", sol::property([](const Vec3f& v) { return v.y; }, [](Vec3f& v, float val) { v.y = val; }), + "z", sol::property([](const Vec3f& v) { return v.z; }, [](Vec3f& v, float val) { v.z = val; }), + + sol::meta_function::addition, sol::resolve(&Vec3f::operator+), + sol::meta_function::subtraction, sol::resolve(&Vec3f::operator-), + sol::meta_function::unary_minus, sol::resolve(&Vec3f::operator-), + sol::meta_function::equal_to, &Vec3f::operator==, sol::meta_function::less_than, + sol::resolve(&Vec3f::operator<), sol::meta_function::less_than_or_equal_to, + sol::resolve(&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::operator*), + sol::resolve(&Vec3f::operator*), + [](const float s, const Vec3f& v) { return v * s; }), + + sol::meta_function::division, + sol::overload(sol::resolve(&Vec3f::operator/), + sol::resolve(&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(&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> + { + auto result = self.angle_between(other); + if (result) + return std::make_tuple(sol::optional(result->as_degrees()), + sol::optional(sol::nullopt)); + return std::make_tuple(sol::optional(sol::nullopt), + sol::optional("impossible angle (zero-length vector)")); + }, + + "is_perpendicular", + [](const Vec3f& self, const Vec3f& other, sol::optional 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; + }); + } +} // namespace omath::lua::detail +#endif diff --git a/source/lua/lua_vec4.cpp b/source/lua/lua_vec4.cpp new file mode 100644 index 00000000..dfa8b978 --- /dev/null +++ b/source/lua/lua_vec4.cpp @@ -0,0 +1,62 @@ +// +// Created by orange on 07.03.2026. +// +#ifdef OMATH_ENABLE_LUA +#include "omath/lua/lua.hpp" +#include +#include + +namespace omath::lua +{ + void LuaInterpreter::register_vec4(sol::table& omath_table) + { + using Vec4f = omath::Vector4; + + omath_table.new_usertype( + "Vec4", sol::constructors(), + + "x", sol::property([](const Vec4f& v) { return v.x; }, [](Vec4f& v, float val) { v.x = val; }), + "y", sol::property([](const Vec4f& v) { return v.y; }, [](Vec4f& v, float val) { v.y = val; }), + "z", sol::property([](const Vec4f& v) { return v.z; }, [](Vec4f& v, float val) { v.z = val; }), + "w", sol::property([](const Vec4f& v) { return v.w; }, [](Vec4f& v, float val) { v.w = val; }), + + sol::meta_function::addition, sol::resolve(&Vec4f::operator+), + sol::meta_function::subtraction, sol::resolve(&Vec4f::operator-), + sol::meta_function::unary_minus, sol::resolve(&Vec4f::operator-), + sol::meta_function::equal_to, &Vec4f::operator==, + sol::meta_function::less_than, sol::resolve(&Vec4f::operator<), + sol::meta_function::less_than_or_equal_to, sol::resolve(&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::operator*), + sol::resolve(&Vec4f::operator*), + [](const float s, const Vec4f& v) { return v * s; }), + + sol::meta_function::division, + sol::overload(sol::resolve(&Vec4f::operator/), + sol::resolve(&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; + }); + } +} // namespace omath::lua::detail +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index de4f83bd..1e1b2718 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,8 +4,8 @@ project(unit_tests) include(GoogleTest) -file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") -add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES}) +file(GLOB_RECURSE UNIT_TESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/general/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/engines/*.cpp") +add_executable(${PROJECT_NAME} ${UNIT_TESTS_SOURCES} main.cpp) set_target_properties( ${PROJECT_NAME} @@ -22,6 +22,16 @@ else() # GTest is being linked as vcpkg package target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest GTest::gtest_main omath::omath) endif() +if (OMATH_ENABLE_LUA) + file(GLOB_RECURSE UNIT_TESTS_SOURCES_LUA CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/lua/*.cpp") + target_compile_definitions(${PROJECT_NAME} PRIVATE LUA_SCRIPTS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/lua") + target_sources(${PROJECT_NAME} PRIVATE ${UNIT_TESTS_SOURCES_LUA}) + if (EMSCRIPTEN) + target_link_options(${PROJECT_NAME} PRIVATE + "SHELL:--embed-file ${CMAKE_CURRENT_SOURCE_DIR}/lua@${CMAKE_CURRENT_SOURCE_DIR}/lua") + endif() +endif() + if(OMATH_ENABLE_COVERAGE) include(${CMAKE_SOURCE_DIR}/cmake/Coverage.cmake) omath_setup_coverage(${PROJECT_NAME}) @@ -36,3 +46,4 @@ endif() if(NOT (ANDROID OR IOS OR EMSCRIPTEN)) gtest_discover_tests(${PROJECT_NAME}) endif() + diff --git a/tests/lua/color_tests.lua b/tests/lua/color_tests.lua new file mode 100644 index 00000000..234e5f9c --- /dev/null +++ b/tests/lua/color_tests.lua @@ -0,0 +1,96 @@ +local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end + +function Color_Constructor_float() + local c = omath.Color.new(1, 0.5, 0.25, 1) + assert(approx(c.r, 1) and approx(c.g, 0.5) and approx(c.b, 0.25) and approx(c.a, 1)) +end + +function Color_Constructor_default() + local c = omath.Color.new() + assert(c ~= nil) +end + +function Color_Constructor_clamping() + local c = omath.Color.new(2, -1, 0.5, 1) + assert(approx(c.r, 1) and approx(c.g, 0) and approx(c.b, 0.5)) +end + +function Color_from_rgba() + local c = omath.Color.from_rgba(255, 128, 0, 255) + assert(approx(c.r, 1) and approx(c.g, 128/255) and approx(c.b, 0) and approx(c.a, 1)) +end + +function Color_from_hsv_components() + local c = omath.Color.from_hsv(0, 1, 1) + assert(approx(c.r, 1) and approx(c.g, 0) and approx(c.b, 0)) +end + +function Color_from_hsv_struct() + local hsv = omath.Hsv.new() + hsv.hue = 0 + hsv.saturation = 1 + hsv.value = 1 + local c = omath.Color.from_hsv(hsv) + assert(approx(c.r, 1) and approx(c.g, 0) and approx(c.b, 0)) +end + +function Color_red() + local c = omath.Color.red() + assert(approx(c.r, 1) and approx(c.g, 0) and approx(c.b, 0) and approx(c.a, 1)) +end + +function Color_green() + local c = omath.Color.green() + assert(approx(c.r, 0) and approx(c.g, 1) and approx(c.b, 0) and approx(c.a, 1)) +end + +function Color_blue() + local c = omath.Color.blue() + assert(approx(c.r, 0) and approx(c.g, 0) and approx(c.b, 1) and approx(c.a, 1)) +end + +function Color_to_hsv() + local hsv = omath.Color.red():to_hsv() + assert(approx(hsv.hue, 0) and approx(hsv.saturation, 1) and approx(hsv.value, 1)) +end + +function Color_set_hue() + local c = omath.Color.red() + c:set_hue(1/3) + assert(approx(c.g, 1, 1e-3)) +end + +function Color_set_saturation() + local c = omath.Color.red() + c:set_saturation(0) + assert(approx(c.r, c.g) and approx(c.g, c.b)) +end + +function Color_set_value() + local c = omath.Color.red() + c:set_value(0) + assert(approx(c.r, 0) and approx(c.g, 0) and approx(c.b, 0)) +end + +function Color_blend() + local c = omath.Color.red():blend(omath.Color.blue(), 0.5) + assert(approx(c.r, 0.5) and approx(c.b, 0.5)) +end + +function Color_blend_clamped_ratio() + local c = omath.Color.red():blend(omath.Color.blue(), 2.0) + assert(approx(c.r, 0) and approx(c.b, 1)) +end + +function Color_to_string() + local s = tostring(omath.Color.red()) + assert(s == "[r:255, g:0, b:0, a:255]") +end + +function Hsv_fields() + local hsv = omath.Hsv.new() + hsv.hue = 0.5 + hsv.saturation = 0.8 + hsv.value = 0.9 + assert(approx(hsv.hue, 0.5) and approx(hsv.saturation, 0.8) and approx(hsv.value, 0.9)) +end diff --git a/tests/lua/source_engine_tests.lua b/tests/lua/source_engine_tests.lua new file mode 100644 index 00000000..26a10af8 --- /dev/null +++ b/tests/lua/source_engine_tests.lua @@ -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 diff --git a/tests/lua/triangle_tests.lua b/tests/lua/triangle_tests.lua new file mode 100644 index 00000000..566b23bc --- /dev/null +++ b/tests/lua/triangle_tests.lua @@ -0,0 +1,82 @@ +local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end + +function Triangle_Constructor_default() + local t = omath.Triangle.new() + assert(t.vertex1.x == 0 and t.vertex1.y == 0 and t.vertex1.z == 0) + assert(t.vertex2.x == 0 and t.vertex2.y == 0 and t.vertex2.z == 0) + assert(t.vertex3.x == 0 and t.vertex3.y == 0 and t.vertex3.z == 0) +end + +function Triangle_Constructor_vertices() + local v1 = omath.Vec3.new(1, 0, 0) + local v2 = omath.Vec3.new(0, 1, 0) + local v3 = omath.Vec3.new(0, 0, 1) + local t = omath.Triangle.new(v1, v2, v3) + assert(t.vertex1.x == 1 and t.vertex1.y == 0 and t.vertex1.z == 0) + assert(t.vertex2.x == 0 and t.vertex2.y == 1 and t.vertex2.z == 0) + assert(t.vertex3.x == 0 and t.vertex3.y == 0 and t.vertex3.z == 1) +end + +function Triangle_Vertex_mutation() + local t = omath.Triangle.new() + t.vertex1 = omath.Vec3.new(5, 6, 7) + assert(t.vertex1.x == 5 and t.vertex1.y == 6 and t.vertex1.z == 7) +end + +-- Right triangle: v1=(0,3,0), v2=(0,0,0), v3=(4,0,0) — sides 3, 4, hypot 5 +function Triangle_SideALength() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + assert(approx(t:side_a_length(), 3.0)) +end + +function Triangle_SideBLength() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + assert(approx(t:side_b_length(), 4.0)) +end + +function Triangle_Hypot() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + assert(approx(t:hypot(), 5.0)) +end + +function Triangle_SideAVector() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + local a = t:side_a_vector() + assert(approx(a.x, 0) and approx(a.y, 3) and approx(a.z, 0)) +end + +function Triangle_SideBVector() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + local b = t:side_b_vector() + assert(approx(b.x, 4) and approx(b.y, 0) and approx(b.z, 0)) +end + +function Triangle_IsRectangular_true() + local t = omath.Triangle.new(omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(4, 0, 0)) + assert(t:is_rectangular() == true) +end + +function Triangle_IsRectangular_false() + -- equilateral-ish triangle, not rectangular + local t = omath.Triangle.new(omath.Vec3.new(0, 1, 0), omath.Vec3.new(-1, 0, 0), omath.Vec3.new(1, 0, 0)) + assert(t:is_rectangular() == false) +end + +function Triangle_MidPoint() + local t = omath.Triangle.new(omath.Vec3.new(3, 0, 0), omath.Vec3.new(0, 3, 0), omath.Vec3.new(0, 0, 3)) + local m = t:mid_point() + assert(approx(m.x, 1.0) and approx(m.y, 1.0) and approx(m.z, 1.0)) +end + +function Triangle_CalculateNormal() + -- flat triangle in XY plane — normal should be (0, 0, 1) + local t = omath.Triangle.new(omath.Vec3.new(0, 1, 0), omath.Vec3.new(0, 0, 0), omath.Vec3.new(1, 0, 0)) + local n = t:calculate_normal() + assert(approx(n.x, 0) and approx(n.y, 0) and approx(n.z, 1)) +end + +function Triangle_ToString() + local t = omath.Triangle.new(omath.Vec3.new(1, 0, 0), omath.Vec3.new(0, 1, 0), omath.Vec3.new(0, 0, 1)) + local s = tostring(t) + assert(s == "Triangle((1, 0, 0), (0, 1, 0), (0, 0, 1))") +end diff --git a/tests/lua/unit_test_lua_color.cpp b/tests/lua/unit_test_lua_color.cpp new file mode 100644 index 00000000..1397b5e7 --- /dev/null +++ b/tests/lua/unit_test_lua_color.cpp @@ -0,0 +1,51 @@ +// +// Created by orange on 08.03.2026. +// +#include +#include +#include + +class LuaColor : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::register_lib(L); + if (luaL_dofile(L, LUA_SCRIPTS_DIR "/color_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(LuaColor, Constructor_float) { check("Color_Constructor_float"); } +TEST_F(LuaColor, Constructor_default) { check("Color_Constructor_default"); } +TEST_F(LuaColor, Constructor_clamping) { check("Color_Constructor_clamping"); } +TEST_F(LuaColor, from_rgba) { check("Color_from_rgba"); } +TEST_F(LuaColor, from_hsv_components) { check("Color_from_hsv_components"); } +TEST_F(LuaColor, from_hsv_struct) { check("Color_from_hsv_struct"); } +TEST_F(LuaColor, red) { check("Color_red"); } +TEST_F(LuaColor, green) { check("Color_green"); } +TEST_F(LuaColor, blue) { check("Color_blue"); } +TEST_F(LuaColor, to_hsv) { check("Color_to_hsv"); } +TEST_F(LuaColor, set_hue) { check("Color_set_hue"); } +TEST_F(LuaColor, set_saturation) { check("Color_set_saturation"); } +TEST_F(LuaColor, set_value) { check("Color_set_value"); } +TEST_F(LuaColor, blend) { check("Color_blend"); } +TEST_F(LuaColor, blend_clamped_ratio) { check("Color_blend_clamped_ratio"); } +TEST_F(LuaColor, to_string) { check("Color_to_string"); } +TEST_F(LuaColor, Hsv_fields) { check("Hsv_fields"); } diff --git a/tests/lua/unit_test_lua_source_engine.cpp b/tests/lua/unit_test_lua_source_engine.cpp new file mode 100644 index 00000000..102644c3 --- /dev/null +++ b/tests/lua/unit_test_lua_source_engine.cpp @@ -0,0 +1,79 @@ +// +// Created by orange on 07.03.2026. +// +#include +#include +#include + +class LuaSourceEngine : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::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"); } diff --git a/tests/lua/unit_test_lua_triangle.cpp b/tests/lua/unit_test_lua_triangle.cpp new file mode 100644 index 00000000..096017b0 --- /dev/null +++ b/tests/lua/unit_test_lua_triangle.cpp @@ -0,0 +1,47 @@ +// +// Created by orange on 10.03.2026. +// +#include +#include +#include + +class LuaTriangle : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::register_lib(L); + if (luaL_dofile(L, LUA_SCRIPTS_DIR "/triangle_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(LuaTriangle, Constructor_default) { check("Triangle_Constructor_default"); } +TEST_F(LuaTriangle, Constructor_vertices) { check("Triangle_Constructor_vertices"); } +TEST_F(LuaTriangle, Vertex_mutation) { check("Triangle_Vertex_mutation"); } +TEST_F(LuaTriangle, SideALength) { check("Triangle_SideALength"); } +TEST_F(LuaTriangle, SideBLength) { check("Triangle_SideBLength"); } +TEST_F(LuaTriangle, Hypot) { check("Triangle_Hypot"); } +TEST_F(LuaTriangle, SideAVector) { check("Triangle_SideAVector"); } +TEST_F(LuaTriangle, SideBVector) { check("Triangle_SideBVector"); } +TEST_F(LuaTriangle, IsRectangular_true) { check("Triangle_IsRectangular_true"); } +TEST_F(LuaTriangle, IsRectangular_false) { check("Triangle_IsRectangular_false"); } +TEST_F(LuaTriangle, MidPoint) { check("Triangle_MidPoint"); } +TEST_F(LuaTriangle, CalculateNormal) { check("Triangle_CalculateNormal"); } +TEST_F(LuaTriangle, ToString) { check("Triangle_ToString"); } diff --git a/tests/lua/unit_test_lua_vector2.cpp b/tests/lua/unit_test_lua_vector2.cpp new file mode 100644 index 00000000..929cb20d --- /dev/null +++ b/tests/lua/unit_test_lua_vector2.cpp @@ -0,0 +1,56 @@ +// +// Created by orange on 07.03.2026. +// +#include +#include +#include + +class LuaVec2 : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::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"); } diff --git a/tests/lua/unit_test_lua_vector3.cpp b/tests/lua/unit_test_lua_vector3.cpp new file mode 100644 index 00000000..a2148658 --- /dev/null +++ b/tests/lua/unit_test_lua_vector3.cpp @@ -0,0 +1,69 @@ +// +// Created by orange on 07.03.2026. +// +#include +#include +#include + +class LuaVec3 : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::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"); } diff --git a/tests/lua/unit_test_lua_vector4.cpp b/tests/lua/unit_test_lua_vector4.cpp new file mode 100644 index 00000000..5d073595 --- /dev/null +++ b/tests/lua/unit_test_lua_vector4.cpp @@ -0,0 +1,57 @@ +// +// Created by orange on 07.03.2026. +// +#include +#include +#include + +class LuaVec4 : public ::testing::Test +{ +protected: + lua_State* L = nullptr; + + void SetUp() override + { + L = luaL_newstate(); + luaL_openlibs(L); + omath::lua::LuaInterpreter::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"); } diff --git a/tests/lua/vec2_tests.lua b/tests/lua/vec2_tests.lua new file mode 100644 index 00000000..e51417e0 --- /dev/null +++ b/tests/lua/vec2_tests.lua @@ -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 diff --git a/tests/lua/vec3_tests.lua b/tests/lua/vec3_tests.lua new file mode 100644 index 00000000..8ac7d93c --- /dev/null +++ b/tests/lua/vec3_tests.lua @@ -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 diff --git a/tests/lua/vec4_tests.lua b/tests/lua/vec4_tests.lua new file mode 100644 index 00000000..ba584c3f --- /dev/null +++ b/tests/lua/vec4_tests.lua @@ -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 diff --git a/tests/lua/vector2_test.lua b/tests/lua/vector2_test.lua new file mode 100644 index 00000000..d20dd2cf --- /dev/null +++ b/tests/lua/vector2_test.lua @@ -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())) diff --git a/tests/lua/vector3_test.lua b/tests/lua/vector3_test.lua new file mode 100644 index 00000000..88835cc3 --- /dev/null +++ b/tests/lua/vector3_test.lua @@ -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) diff --git a/tests/lua/vector4_test.lua b/tests/lua/vector4_test.lua new file mode 100644 index 00000000..4983a6f2 --- /dev/null +++ b/tests/lua/vector4_test.lua @@ -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) diff --git a/vcpkg.json b/vcpkg.json index 7a99ddfb..52c18565 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -17,7 +17,7 @@ ], "features": { "avx2": { - "description": "Omath will use AVX2 to boost performance", + "description": "omath will use AVX2 to boost performance", "supports": "!arm" }, "benchmark": { @@ -35,7 +35,7 @@ ] }, "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": [ "imgui" ] @@ -45,6 +45,13 @@ "dependencies": [ "gtest" ] + }, + "lua": { + "description": "lua support for omath", + "dependencies": [ + "lua", + "sol2" + ] } } }