Merge pull request #192 from orange-cpp/feature/lua

added more lua stuff
This commit is contained in:
2026-05-18 11:11:13 +03:00
committed by GitHub
19 changed files with 2272 additions and 30 deletions
+1 -1
View File
@@ -124,7 +124,7 @@ namespace omath
}
[[nodiscard]]
static consteval MatSize size() noexcept
static constexpr MatSize size() noexcept
{
return {Rows, Columns};
}
+5
View File
@@ -15,8 +15,13 @@ namespace omath::lua
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_matrices(sol::table& omath_table);
static void register_quaternion(sol::table& omath_table);
static void register_color(sol::table& omath_table);
static void register_hud(sol::table& omath_table);
static void register_triangle(sol::table& omath_table);
static void register_3d_primitives(sol::table& omath_table);
static void register_collision(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);
+2 -1
View File
@@ -107,7 +107,8 @@ namespace omath::projection
// m[1,1] == 1 / tan(fov/2) => fov = 2 * atan(1 / m[1,1])
const auto f = proj_matrix.at(1, 1);
// m[0,0] == m[1,1] / aspect_ratio => aspect = m[1,1] / m[0,0]
return {FieldOfView::from_radians(NumericType{2} * std::atan(NumericType{1} / f)),
const auto fov_radians = NumericType{2} * std::atan(NumericType{1} / f);
return {FieldOfView::from_radians(static_cast<typename FieldOfView::ArithmeticType>(fov_radians)),
f / proj_matrix.at(0, 0)};
}
+5
View File
@@ -17,8 +17,13 @@ namespace omath::lua
register_vec2(omath_table);
register_vec3(omath_table);
register_vec4(omath_table);
register_matrices(omath_table);
register_quaternion(omath_table);
register_color(omath_table);
register_hud(omath_table);
register_triangle(omath_table);
register_3d_primitives(omath_table);
register_collision(omath_table);
register_shared_types(omath_table);
register_engines(omath_table);
register_pattern_scan(omath_table);
+304
View File
@@ -0,0 +1,304 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/3d_primitives/aabb.hpp>
#include <omath/3d_primitives/obb.hpp>
#include <omath/collision/collider_interface.hpp>
#include <omath/collision/epa_algorithm.hpp>
#include <omath/collision/gjk_algorithm.hpp>
#include <omath/collision/line_tracer.hpp>
#include <omath/linear_algebra/triangle.hpp>
#include <omath/linear_algebra/vector3.hpp>
#include <sol/sol.hpp>
#include <algorithm>
#include <stdexcept>
#include <vector>
namespace
{
using Vec3f = omath::Vector3<float>;
using Triangle3f = omath::Triangle<Vec3f>;
using Ray3f = omath::collision::Ray<Vec3f>;
using LineTracer3f = omath::collision::LineTracer<Ray3f>;
using Aabbf = omath::primitives::Aabb<float>;
using Obbf = omath::primitives::Obb<float>;
template<class Object, class Value>
auto lua_field(Value Object::* member)
{
return sol::property(
[member](const Object& object) -> const Value&
{
return object.*member;
},
[member](Object& object, const Value& value)
{
object.*member = value;
});
}
class LuaConvexCollider final : public omath::collision::ColliderInterface<Vec3f>
{
public:
using VectorType = Vec3f;
explicit LuaConvexCollider(std::vector<Vec3f> vertices, const Vec3f& origin = {})
: m_vertices(std::move(vertices)), m_origin(origin)
{
if (m_vertices.empty())
throw std::invalid_argument("convex collider must contain at least one vertex");
}
[[nodiscard]]
Vec3f find_abs_furthest_vertex_position(const Vec3f& direction) const override
{
const auto furthest = std::ranges::max_element(
m_vertices,
[&direction](const Vec3f& first, const Vec3f& second)
{
return first.dot(direction) < second.dot(direction);
});
return m_origin + *furthest;
}
[[nodiscard]]
const Vec3f& get_origin() const override
{
return m_origin;
}
void set_origin(const Vec3f& new_origin) override
{
m_origin = new_origin;
}
[[nodiscard]]
std::size_t vertex_count() const noexcept
{
return m_vertices.size();
}
[[nodiscard]]
const std::vector<Vec3f>& vertices() const noexcept
{
return m_vertices;
}
private:
std::vector<Vec3f> m_vertices;
Vec3f m_origin;
};
std::vector<Vec3f> vec3_table_to_vector(const sol::table& points)
{
std::vector<Vec3f> result;
for (std::size_t i = 1;; ++i)
{
const auto point = points[i].get<sol::optional<Vec3f>>();
if (!point)
break;
result.push_back(*point);
}
return result;
}
sol::table vec3_array_to_table(const auto& points, sol::this_state state)
{
sol::state_view lua(state);
sol::table result = lua.create_table(static_cast<int>(points.size()), 0);
for (std::size_t i = 0; i < points.size(); ++i)
result[i + 1] = points[i];
return result;
}
Vec3f aabb_top(const Aabbf& aabb, const omath::primitives::UpAxis axis)
{
switch (axis)
{
case omath::primitives::UpAxis::X:
return aabb.top<omath::primitives::UpAxis::X>();
case omath::primitives::UpAxis::Y:
return aabb.top<omath::primitives::UpAxis::Y>();
case omath::primitives::UpAxis::Z:
return aabb.top<omath::primitives::UpAxis::Z>();
}
std::unreachable();
}
Vec3f aabb_bottom(const Aabbf& aabb, const omath::primitives::UpAxis axis)
{
switch (axis)
{
case omath::primitives::UpAxis::X:
return aabb.bottom<omath::primitives::UpAxis::X>();
case omath::primitives::UpAxis::Y:
return aabb.bottom<omath::primitives::UpAxis::Y>();
case omath::primitives::UpAxis::Z:
return aabb.bottom<omath::primitives::UpAxis::Z>();
}
std::unreachable();
}
bool ray_hits_triangle(const Ray3f& ray, const Triangle3f& triangle)
{
return !(LineTracer3f::get_ray_hit_point(ray, triangle) == ray.end);
}
bool ray_hits_aabb(const Ray3f& ray, const Aabbf& aabb)
{
return !(LineTracer3f::get_ray_hit_point(ray, aabb) == ray.end);
}
bool ray_hits_obb(const Ray3f& ray, const Obbf& obb)
{
return !(LineTracer3f::get_ray_hit_point(ray, obb) == ray.end);
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_3d_primitives(sol::table& omath_table)
{
auto primitives_table = omath_table["primitives"].get_or_create<sol::table>();
primitives_table.new_enum("UpAxis", "X", omath::primitives::UpAxis::X, "Y", omath::primitives::UpAxis::Y, "Z",
omath::primitives::UpAxis::Z);
primitives_table.new_usertype<Aabbf>(
"Aabb",
sol::factories([]() { return Aabbf{}; },
[](const Vec3f& min, const Vec3f& max) { return Aabbf{min, max}; }),
"min", lua_field(&Aabbf::min), "max", lua_field(&Aabbf::max), "center", &Aabbf::center, "extents",
&Aabbf::extents,
"top",
[](const Aabbf& aabb, sol::optional<omath::primitives::UpAxis> axis)
{
return aabb_top(aabb, axis.value_or(omath::primitives::UpAxis::Y));
},
"bottom",
[](const Aabbf& aabb, sol::optional<omath::primitives::UpAxis> axis)
{
return aabb_bottom(aabb, axis.value_or(omath::primitives::UpAxis::Y));
},
"is_collide", &Aabbf::is_collide,
"as_table",
[](const Aabbf& aabb, sol::this_state state) -> sol::table
{
sol::state_view lua(state);
sol::table result = lua.create_table();
result["min"] = aabb.min;
result["max"] = aabb.max;
return result;
});
primitives_table.new_usertype<Obbf>(
"Obb",
sol::factories(
[]()
{
return Obbf{};
},
[](const Vec3f& center, const Vec3f& axis_x, const Vec3f& axis_y, const Vec3f& axis_z,
const Vec3f& half_extents)
{
return Obbf{center, axis_x, axis_y, axis_z, half_extents};
}),
"center", lua_field(&Obbf::center), "axis_x", lua_field(&Obbf::axis_x), "axis_y",
lua_field(&Obbf::axis_y), "axis_z", lua_field(&Obbf::axis_z), "half_extents",
lua_field(&Obbf::half_extents),
"vertices",
[](const Obbf& obb, sol::this_state state)
{
return vec3_array_to_table(obb.vertices(), state);
});
}
void LuaInterpreter::register_collision(sol::table& omath_table)
{
auto collision_table = omath_table["collision"].get_or_create<sol::table>();
collision_table.new_usertype<Ray3f>(
"Ray",
sol::factories([]() { return Ray3f{}; },
[](const Vec3f& start, const Vec3f& end) { return Ray3f{start, end}; },
[](const Vec3f& start, const Vec3f& end, const bool infinite_length)
{ return Ray3f{start, end, infinite_length}; }),
"start", lua_field(&Ray3f::start), "end", lua_field(&Ray3f::end), "infinite_length",
lua_field(&Ray3f::infinite_length),
"direction_vector", &Ray3f::direction_vector, "direction_vector_normalized",
&Ray3f::direction_vector_normalized);
collision_table.new_usertype<LuaConvexCollider>(
"ConvexCollider",
sol::factories([](const sol::table& vertices) { return LuaConvexCollider(vec3_table_to_vector(vertices)); },
[](const sol::table& vertices, const Vec3f& origin)
{ return LuaConvexCollider(vec3_table_to_vector(vertices), origin); }),
"find_abs_furthest_vertex_position", &LuaConvexCollider::find_abs_furthest_vertex_position,
"get_origin", &LuaConvexCollider::get_origin, "set_origin", &LuaConvexCollider::set_origin,
"vertex_count", &LuaConvexCollider::vertex_count,
"vertices",
[](const LuaConvexCollider& collider, sol::this_state state)
{
return vec3_array_to_table(collider.vertices(), state);
});
collision_table.new_usertype<LineTracer3f>(
"LineTracer", sol::no_constructor, "can_trace_line", &LineTracer3f::can_trace_line,
"get_ray_hit_point",
sol::overload(
[](const Ray3f& ray, const Triangle3f& triangle)
{
return LineTracer3f::get_ray_hit_point(ray, triangle);
},
[](const Ray3f& ray, const Aabbf& aabb)
{
return LineTracer3f::get_ray_hit_point(ray, aabb);
},
[](const Ray3f& ray, const Obbf& obb)
{
return LineTracer3f::get_ray_hit_point(ray, obb);
}),
"ray_hits_triangle", &ray_hits_triangle, "ray_hits_aabb", &ray_hits_aabb, "ray_hits_obb",
&ray_hits_obb);
collision_table["gjk_support_vertex"] =
[](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b, const Vec3f& direction)
{ return omath::collision::GjkAlgorithm<LuaConvexCollider>::find_support_vertex(collider_a, collider_b, direction); };
collision_table["gjk_collide"] = [](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b)
{ return omath::collision::GjkAlgorithm<LuaConvexCollider>::is_collide(collider_a, collider_b); };
collision_table["epa_solve"] = [](const LuaConvexCollider& collider_a, const LuaConvexCollider& collider_b,
sol::this_state state) -> sol::object
{
using Gjk = omath::collision::GjkAlgorithm<LuaConvexCollider>;
using Epa = omath::collision::Epa<LuaConvexCollider>;
sol::state_view lua(state);
const auto gjk = Gjk::is_collide_with_simplex_info(collider_a, collider_b);
if (!gjk.hit)
return sol::nil;
const auto epa = Epa::solve(collider_a, collider_b, gjk.simplex);
if (!epa)
return sol::nil;
sol::table result = lua.create_table();
result["normal"] = epa->normal;
result["penetration_vector"] = epa->penetration_vector;
result["depth"] = epa->depth;
result["iterations"] = epa->iterations;
result["num_vertices"] = epa->num_vertices;
result["num_faces"] = epa->num_faces;
return sol::make_object(lua, result);
};
}
} // namespace omath::lua
#endif
+45 -15
View File
@@ -14,6 +14,8 @@
#include <omath/engines/unreal_engine/camera.hpp>
#include <sol/sol.hpp>
#include <string_view>
#include <type_traits>
#include <utility>
namespace
{
@@ -85,44 +87,72 @@ namespace
using PitchAngle = typename EngineTraits::PitchAngle;
using ViewAngles = typename EngineTraits::ViewAngles;
using Camera = typename EngineTraits::Camera;
using Mat4X4 = std::remove_cvref_t<decltype(std::declval<const Camera&>().get_view_matrix())>;
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>(
auto camera_type = engine_table.new_usertype<Camera>(
"Camera",
sol::constructors<Camera(const omath::Vector3<ArithmeticType>&, const ViewAngles&,
const omath::projection::ViewPort&, const omath::projection::FieldOfView&,
ArithmeticType, ArithmeticType)>(),
"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,
ArithmeticType, ArithmeticType)>());
"world_to_screen",
[](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
camera_type["look_at"] = &Camera::look_at;
camera_type["get_forward"] = &Camera::get_forward;
camera_type["get_right"] = &Camera::get_right;
camera_type["get_up"] = &Camera::get_up;
camera_type["get_origin"] = &Camera::get_origin;
camera_type["get_view_angles"] = &Camera::get_view_angles;
camera_type["get_near_plane"] = &Camera::get_near_plane;
camera_type["get_far_plane"] = &Camera::get_far_plane;
camera_type["get_field_of_view"] = &Camera::get_field_of_view;
camera_type["set_origin"] = &Camera::set_origin;
camera_type["set_view_angles"] = &Camera::set_view_angles;
camera_type["set_view_port"] = &Camera::set_view_port;
camera_type["set_field_of_view"] = &Camera::set_field_of_view;
camera_type["set_near_plane"] = &Camera::set_near_plane;
camera_type["set_far_plane"] = &Camera::set_far_plane;
camera_type["get_view_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_view_matrix();
};
camera_type["get_projection_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_projection_matrix();
};
camera_type["get_view_projection_matrix"] = [](const Camera& cam) -> Mat4X4
{
return cam.get_view_projection_matrix();
};
camera_type["extract_projection_params"] = [](const Mat4X4& projection_matrix)
{
const auto params = Camera::extract_projection_params(projection_matrix);
return std::make_tuple(params.fov, params.aspect_ratio);
};
camera_type["calc_view_angles_from_view_matrix"] = &Camera::calc_view_angles_from_view_matrix;
camera_type["calc_origin_from_view_matrix"] = &Camera::calc_origin_from_view_matrix;
camera_type["world_to_screen"] = [](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, 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<ArithmeticType>& pos)
camera_type["screen_to_world"] = [](const Camera& cam, const omath::Vector3<ArithmeticType>& pos)
-> std::tuple<sol::optional<omath::Vector3<ArithmeticType>>, 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 -----------------------------------------------
+516
View File
@@ -0,0 +1,516 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/hud/canvas_box.hpp>
#include <omath/hud/entity_overlay.hpp>
#include <sol/sol.hpp>
#include <any>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
namespace
{
[[noreturn]]
void throw_lua_error(sol::protected_function_result& result)
{
sol::error err = result;
throw err;
}
class LuaHudRenderer final : public omath::hud::HudRendererInterface
{
public:
explicit LuaHudRenderer(sol::table callbacks): m_callbacks(std::move(callbacks))
{
}
void add_line(const omath::Vector2<float>& line_start, const omath::Vector2<float>& line_end,
const omath::Color& color, const float thickness) override
{
call_optional("add_line", line_start, line_end, color, thickness);
}
void add_polyline(const std::span<const omath::Vector2<float>>& vertexes, const omath::Color& color,
const float thickness) override
{
call_optional("add_polyline", make_points_table(vertexes), color, thickness);
}
void add_filled_polyline(const std::span<const omath::Vector2<float>>& vertexes,
const omath::Color& color) override
{
call_optional("add_filled_polyline", make_points_table(vertexes), color);
}
void add_rectangle(const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& color) override
{
call_optional("add_rectangle", min, max, color);
}
void add_filled_rectangle(const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& color) override
{
call_optional("add_filled_rectangle", min, max, color);
}
void add_circle(const omath::Vector2<float>& center, const float radius, const omath::Color& color,
const float thickness, const int segments) override
{
call_optional("add_circle", center, radius, color, thickness, segments);
}
void add_filled_circle(const omath::Vector2<float>& center, const float radius, const omath::Color& color,
const int segments) override
{
call_optional("add_filled_circle", center, radius, color, segments);
}
void add_arc(const omath::Vector2<float>& center, const float radius, const float a_min, const float a_max,
const omath::Color& color, const float thickness, const int segments) override
{
call_optional("add_arc", center, radius, a_min, a_max, color, thickness, segments);
}
void add_image(const std::any& texture_id, const omath::Vector2<float>& min, const omath::Vector2<float>& max,
const omath::Color& tint) override
{
const auto callback = callback_for("add_image");
if (!callback)
return;
sol::object texture = sol::nil;
if (const auto lua_object = std::any_cast<sol::object>(&texture_id))
texture = *lua_object;
auto result = (*callback)(texture, min, max, tint);
if (!result.valid())
throw_lua_error(result);
}
void add_text(const omath::Vector2<float>& position, const omath::Color& color,
const std::string_view& text) override
{
call_optional("add_text", position, color, std::string{text});
}
[[nodiscard]]
omath::Vector2<float> calc_text_size(const std::string_view& text) override
{
const auto callback = callback_for("calc_text_size");
if (!callback)
return {};
auto result = (*callback)(std::string{text});
if (!result.valid())
throw_lua_error(result);
return result.get<omath::Vector2<float>>();
}
private:
sol::main_table m_callbacks;
[[nodiscard]]
sol::optional<sol::protected_function> callback_for(const char* name) const
{
const sol::object callback = m_callbacks[name];
if (!callback.valid() || callback == sol::nil)
return sol::nullopt;
return callback.as<sol::protected_function>();
}
template<class... Args>
void call_optional(const char* name, Args&&... args) const
{
const auto callback = callback_for(name);
if (!callback)
return;
auto result = (*callback)(std::forward<Args>(args)...);
if (!result.valid())
throw_lua_error(result);
}
[[nodiscard]]
sol::table make_points_table(const std::span<const omath::Vector2<float>>& vertexes) const
{
sol::state_view lua(m_callbacks.lua_state());
sol::table points = lua.create_table(static_cast<int>(vertexes.size()), 0);
for (std::size_t i = 0; i < vertexes.size(); ++i)
points[i + 1] = vertexes[i];
return points;
}
};
[[nodiscard]]
omath::Color transparent_black()
{
return {0.f, 0.f, 0.f, 0.f};
}
[[nodiscard]]
omath::Color opaque_white()
{
return {1.f, 1.f, 1.f, 1.f};
}
[[nodiscard]]
omath::hud::EntityOverlay make_overlay(const omath::Vector2<float>& top, const omath::Vector2<float>& bottom,
const std::shared_ptr<LuaHudRenderer>& renderer)
{
if (!renderer)
throw std::invalid_argument("hud renderer must not be nil");
return {top, bottom, renderer};
}
[[nodiscard]]
std::any texture_id_from_lua_object(const sol::object& texture_id)
{
return texture_id;
}
std::vector<omath::Vector2<float>> points_from_table(const sol::table& points)
{
std::vector<omath::Vector2<float>> result;
for (std::size_t i = 1;; ++i)
{
const auto point = points[i].get<sol::optional<omath::Vector2<float>>>();
if (!point)
break;
result.push_back(*point);
}
return result;
}
template<class Object, class Value>
auto lua_field(Value Object::* member)
{
return sol::property(
[member](const Object& object) -> const Value&
{
return object.*member;
},
[member](Object& object, const Value& value)
{
object.*member = value;
});
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_hud(sol::table& omath_table)
{
auto hud_table = omath_table["hud"].get_or_create<sol::table>();
hud_table.new_usertype<omath::hud::CanvasBox>(
"CanvasBox",
sol::factories([](const omath::Vector2<float>& top, const omath::Vector2<float>& bottom)
{ return omath::hud::CanvasBox(top, bottom); },
[](const omath::Vector2<float>& top, const omath::Vector2<float>& bottom,
const float ratio) { return omath::hud::CanvasBox(top, bottom, ratio); }),
"top_left_corner", lua_field(&omath::hud::CanvasBox::top_left_corner), "top_right_corner",
lua_field(&omath::hud::CanvasBox::top_right_corner), "bottom_left_corner",
lua_field(&omath::hud::CanvasBox::bottom_left_corner), "bottom_right_corner",
lua_field(&omath::hud::CanvasBox::bottom_right_corner),
"as_table",
[](const omath::hud::CanvasBox& box, sol::this_state s) -> sol::table
{
sol::state_view lua(s);
sol::table t = lua.create_table(4, 0);
const auto points = box.as_array();
for (std::size_t i = 0; i < points.size(); ++i)
t[i + 1] = points[i];
return t;
});
hud_table.new_usertype<LuaHudRenderer>(
"Renderer",
sol::factories([](const sol::table& callbacks)
{ return std::make_shared<LuaHudRenderer>(callbacks); }),
"add_line", &LuaHudRenderer::add_line,
"add_polyline",
[](LuaHudRenderer& renderer, const sol::table& points, const omath::Color& color,
const float thickness)
{
const auto vertices = points_from_table(points);
renderer.add_polyline({vertices.data(), vertices.size()}, color, thickness);
},
"add_filled_polyline",
[](LuaHudRenderer& renderer, const sol::table& points, const omath::Color& color)
{
const auto vertices = points_from_table(points);
renderer.add_filled_polyline({vertices.data(), vertices.size()}, color);
},
"add_rectangle", &LuaHudRenderer::add_rectangle, "add_filled_rectangle",
&LuaHudRenderer::add_filled_rectangle, "add_circle",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const omath::Color& color, const float thickness, sol::optional<int> segments)
{
renderer.add_circle(center, radius, color, thickness, segments.value_or(0));
},
"add_filled_circle",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const omath::Color& color, sol::optional<int> segments)
{
renderer.add_filled_circle(center, radius, color, segments.value_or(0));
},
"add_arc",
[](LuaHudRenderer& renderer, const omath::Vector2<float>& center, const float radius,
const float a_min, const float a_max, const omath::Color& color, const float thickness,
sol::optional<int> segments)
{
renderer.add_arc(center, radius, a_min, a_max, color, thickness, segments.value_or(0));
},
"add_image",
[](LuaHudRenderer& renderer, const sol::object& texture_id, const omath::Vector2<float>& min,
const omath::Vector2<float>& max, sol::optional<omath::Color> tint)
{
renderer.add_image(texture_id_from_lua_object(texture_id), min, max,
tint.value_or(opaque_white()));
},
"add_text", &LuaHudRenderer::add_text, "calc_text_size", &LuaHudRenderer::calc_text_size);
hud_table.new_usertype<omath::hud::EntityOverlay>(
"EntityOverlay",
sol::factories(&make_overlay),
"add_2d_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& box_color,
sol::optional<omath::Color> fill_color, sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_2d_box(box_color, fill_color.value_or(transparent_black()),
thickness.value_or(1.f));
},
"add_cornered_2d_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& box_color,
sol::optional<omath::Color> fill_color, sol::optional<float> corner_ratio_len,
sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_cornered_2d_box(box_color, fill_color.value_or(transparent_black()),
corner_ratio_len.value_or(0.2f), thickness.value_or(1.f));
},
"add_dashed_box",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, sol::optional<float> dash_len,
sol::optional<float> gap_len, sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_dashed_box(color, dash_len.value_or(8.f), gap_len.value_or(5.f),
thickness.value_or(1.f));
},
"add_right_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_bar(color, outline_color, bg_color, width, ratio, offset.value_or(5.f));
},
"add_left_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_bar(color, outline_color, bg_color, width, ratio, offset.value_or(5.f));
},
"add_top_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_bar(color, outline_color, bg_color, height, ratio, offset.value_or(5.f));
},
"add_bottom_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_bar(color, outline_color, bg_color, height, ratio, offset.value_or(5.f));
},
"add_right_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_dashed_bar(color, outline_color, bg_color, width, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_left_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float width, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_dashed_bar(color, outline_color, bg_color, width, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_top_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_dashed_bar(color, outline_color, bg_color, height, ratio, dash_len, gap_len,
offset.value_or(5.f));
},
"add_bottom_dashed_bar",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& outline_color,
const omath::Color& bg_color, const float height, const float ratio, const float dash_len,
const float gap_len, sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_dashed_bar(color, outline_color, bg_color, height, ratio, dash_len,
gap_len, offset.value_or(5.f));
},
"add_right_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_right_label(color, offset, outlined, text);
},
"add_left_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_left_label(color, offset, outlined, text);
},
"add_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_top_label(color, offset, outlined, text);
},
"add_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_label(color, offset, outlined, text);
},
"add_centered_top_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_centered_top_label(color, offset, outlined, text);
},
"add_centered_bottom_label",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const float offset,
const bool outlined, const std::string& text) -> omath::hud::EntityOverlay&
{
return overlay.add_centered_bottom_label(color, offset, outlined, text);
},
"add_right_space_vertical", &omath::hud::EntityOverlay::add_right_space_vertical,
"add_right_space_horizontal", &omath::hud::EntityOverlay::add_right_space_horizontal,
"add_left_space_vertical", &omath::hud::EntityOverlay::add_left_space_vertical,
"add_left_space_horizontal", &omath::hud::EntityOverlay::add_left_space_horizontal,
"add_top_space_vertical", &omath::hud::EntityOverlay::add_top_space_vertical,
"add_top_space_horizontal", &omath::hud::EntityOverlay::add_top_space_horizontal,
"add_bottom_space_vertical", &omath::hud::EntityOverlay::add_bottom_space_vertical,
"add_bottom_space_horizontal", &omath::hud::EntityOverlay::add_bottom_space_horizontal,
"add_right_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_right_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_left_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_left_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_top_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_top_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_bottom_progress_ring",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color, const omath::Color& bg,
const float radius, const float ratio, sol::optional<float> thickness, sol::optional<float> offset,
sol::optional<int> segments) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_progress_ring(color, bg, radius, ratio, thickness.value_or(2.f),
offset.value_or(5.f), segments.value_or(0));
},
"add_right_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_right_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_left_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_left_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_top_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_top_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_bottom_icon",
[](omath::hud::EntityOverlay& overlay, const sol::object& texture_id, const float width,
const float height, sol::optional<omath::Color> tint,
sol::optional<float> offset) -> omath::hud::EntityOverlay&
{
return overlay.add_bottom_icon(texture_id_from_lua_object(texture_id), width, height,
tint.value_or(opaque_white()), offset.value_or(5.f));
},
"add_snap_line", &omath::hud::EntityOverlay::add_snap_line, "add_skeleton",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color,
sol::optional<float> thickness) -> omath::hud::EntityOverlay&
{
return overlay.add_skeleton(color, thickness.value_or(1.f));
},
"add_scan_marker",
[](omath::hud::EntityOverlay& overlay, const omath::Color& color,
sol::optional<omath::Color> outline, sol::optional<float> outline_thickness)
-> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::ScanMarker{
color, outline.value_or(transparent_black()), outline_thickness.value_or(1.f)});
},
"add_aim_dot",
[](omath::hud::EntityOverlay& overlay, const omath::Vector2<float>& position,
const omath::Color& color, sol::optional<float> radius) -> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::AimDot{position, color, radius.value_or(3.f)});
},
"add_projectile_aim",
[](omath::hud::EntityOverlay& overlay, const omath::Vector2<float>& position,
const omath::Color& color, sol::optional<float> size, sol::optional<float> line_size,
sol::optional<omath::hud::widget::ProjectileAim::Figure> figure) -> omath::hud::EntityOverlay&
{
return overlay.contents(omath::hud::widget::ProjectileAim{
position, color, size.value_or(3.f), line_size.value_or(1.f),
figure.value_or(omath::hud::widget::ProjectileAim::Figure::SQUARE)});
});
hud_table.new_enum("ProjectileAimFigure", "CIRCLE", omath::hud::widget::ProjectileAim::Figure::CIRCLE,
"SQUARE", omath::hud::widget::ProjectileAim::Figure::SQUARE);
}
} // namespace omath::lua
#endif
+156
View File
@@ -0,0 +1,156 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/linear_algebra/mat.hpp>
#include <sol/sol.hpp>
#include <stdexcept>
namespace
{
template<std::size_t Limit>
std::size_t checked_index(const int index)
{
if (index < 0 || index >= static_cast<int>(Limit))
throw std::out_of_range("matrix index is out of range");
return static_cast<std::size_t>(index);
}
template<std::size_t Rows, std::size_t Columns, class Type, omath::MatStoreType StoreType>
omath::Mat<Rows, Columns, Type, StoreType> mat_from_rows(const sol::table& rows)
{
omath::Mat<Rows, Columns, Type, StoreType> result;
for (std::size_t row = 0; row < Rows; ++row)
{
const auto row_table = rows[row + 1].get<sol::optional<sol::table>>();
if (!row_table)
throw std::invalid_argument("matrix rows must be tables");
for (std::size_t column = 0; column < Columns; ++column)
{
const auto value = (*row_table)[column + 1].get<sol::optional<Type>>();
if (!value)
throw std::invalid_argument("matrix row has missing value");
result.at(row, column) = *value;
}
}
return result;
}
template<std::size_t Rows, std::size_t Columns, class Type, omath::MatStoreType StoreType>
sol::table mat_as_table(const omath::Mat<Rows, Columns, Type, StoreType>& mat, sol::this_state state)
{
sol::state_view lua(state);
sol::table rows = lua.create_table();
for (std::size_t row = 0; row < Rows; ++row)
{
sol::table row_table = lua.create_table();
for (std::size_t column = 0; column < Columns; ++column)
row_table[column + 1] = mat.at(row, column);
rows[row + 1] = row_table;
}
return rows;
}
template<std::size_t Size, class Type, omath::MatStoreType StoreType, bool RegisterMat4Helpers = false>
void register_square_mat(sol::table& omath_table, const char* name)
{
using MatType = omath::Mat<Size, Size, Type, StoreType>;
auto type = omath_table.new_usertype<MatType>(
name, sol::constructors<MatType()>(),
"from_rows", &mat_from_rows<Size, Size, Type, StoreType>, "row_count",
[]()
{
return Size;
},
"columns_count",
[]()
{
return Size;
},
"at",
[](const MatType& mat, const int row, const int column)
{
return mat.at(checked_index<Size>(row), checked_index<Size>(column));
},
"set_at",
[](MatType& mat, const int row, const int column, const Type value)
{
mat.at(checked_index<Size>(row), checked_index<Size>(column)) = value;
return mat;
},
sol::meta_function::multiplication,
sol::overload(
[](const MatType& lhs, const MatType& rhs)
{
return lhs * rhs;
},
[](const MatType& mat, const Type scalar)
{
return mat * scalar;
},
[](const Type scalar, const MatType& mat)
{
return mat * scalar;
}),
sol::meta_function::division,
[](const MatType& mat, const Type scalar)
{
return mat / scalar;
},
sol::meta_function::equal_to, &MatType::operator==, sol::meta_function::to_string, &MatType::to_string,
"transposed", &MatType::transposed, "determinant", &MatType::determinant, "sum", &MatType::sum, "clear",
&MatType::clear, "set", &MatType::set, "inverted",
[](const MatType& mat) -> sol::optional<MatType>
{
auto result = mat.inverted();
if (!result)
return sol::nullopt;
return *result;
},
"as_table", &mat_as_table<Size, Size, Type, StoreType>);
if constexpr (RegisterMat4Helpers)
{
type["to_screen_mat"] = [](const Type screen_width, const Type screen_height)
{
return MatType{
{screen_width / Type{2}, Type{0}, Type{0}, Type{0}},
{Type{0}, -screen_height / Type{2}, Type{0}, Type{0}},
{Type{0}, Type{0}, Type{1}, Type{0}},
{screen_width / Type{2}, screen_height / Type{2}, Type{0}, Type{1}},
};
};
type["translation"] = &omath::mat_translation<Type, StoreType>;
type["scale"] = &omath::mat_scale<Type, StoreType>;
type["look_at_left_handed"] = &omath::mat_look_at_left_handed<Type, StoreType>;
type["look_at_right_handed"] = &omath::mat_look_at_right_handed<Type, StoreType>;
type["perspective_left_handed_vertical_fov"] =
&omath::mat_perspective_left_handed_vertical_fov<Type, StoreType>;
type["perspective_right_handed_vertical_fov"] =
&omath::mat_perspective_right_handed_vertical_fov<Type, StoreType>;
}
}
} // namespace
namespace omath::lua
{
void LuaInterpreter::register_matrices(sol::table& omath_table)
{
register_square_mat<3, float, omath::MatStoreType::ROW_MAJOR>(omath_table, "Mat3");
register_square_mat<4, float, omath::MatStoreType::ROW_MAJOR, true>(omath_table, "Mat4");
register_square_mat<4, float, omath::MatStoreType::COLUMN_MAJOR, true>(omath_table, "Mat4ColumnMajor");
register_square_mat<4, double, omath::MatStoreType::ROW_MAJOR>(omath_table, "Mat4d");
}
} // namespace omath::lua
#endif
+95
View File
@@ -0,0 +1,95 @@
//
// Created by orange on 07.03.2026.
//
#ifdef OMATH_ENABLE_LUA
#include "omath/lua/lua.hpp"
#include <omath/linear_algebra/quaternion.hpp>
#include <sol/sol.hpp>
namespace omath::lua
{
void LuaInterpreter::register_quaternion(sol::table& omath_table)
{
using Quatf = omath::Quaternion<float>;
using Vec3f = omath::Vector3<float>;
omath_table.new_usertype<Quatf>(
"Quaternion", sol::constructors<Quatf(), Quatf(float, float, float, float)>(),
"from_axis_angle", &Quatf::from_axis_angle,
"x",
sol::property(
[](const Quatf& q)
{
return q.x;
},
[](Quatf& q, float val)
{
q.x = val;
}),
"y",
sol::property(
[](const Quatf& q)
{
return q.y;
},
[](Quatf& q, float val)
{
q.y = val;
}),
"z",
sol::property(
[](const Quatf& q)
{
return q.z;
},
[](Quatf& q, float val)
{
q.z = val;
}),
"w",
sol::property(
[](const Quatf& q)
{
return q.w;
},
[](Quatf& q, float val)
{
q.w = val;
}),
sol::meta_function::addition, sol::resolve<Quatf(const Quatf&) const>(&Quatf::operator+),
sol::meta_function::multiplication,
sol::overload(sol::resolve<Quatf(const Quatf&) const>(&Quatf::operator*),
sol::resolve<Quatf(const float&) const>(&Quatf::operator*),
[](float s, const Quatf& q)
{
return q * s;
}),
sol::meta_function::unary_minus, sol::resolve<Quatf() const>(&Quatf::operator-),
sol::meta_function::equal_to, &Quatf::operator==, sol::meta_function::to_string,
[](const Quatf& q)
{
return std::format("Quaternion({}, {}, {}, {})", q.x, q.y, q.z, q.w);
},
"conjugate", &Quatf::conjugate, "dot", &Quatf::dot, "length", &Quatf::length, "length_sqr",
&Quatf::length_sqr, "normalized", &Quatf::normalized, "inverse", &Quatf::inverse, "rotate",
sol::resolve<Vec3f(const Vec3f&) const>(&Quatf::rotate), "to_rotation_matrix3",
&Quatf::to_rotation_matrix3, "to_rotation_matrix4", &Quatf::to_rotation_matrix4,
"as_table",
[](const Quatf& q, sol::this_state s) -> sol::table
{
sol::state_view lua(s);
sol::table t = lua.create_table();
t["x"] = q.x;
t["y"] = q.y;
t["z"] = q.z;
t["w"] = q.w;
return t;
});
}
} // namespace omath::lua
#endif
+169
View File
@@ -0,0 +1,169 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end
local function cube_points()
return {
omath.Vec3.new(-1, -1, -1),
omath.Vec3.new(1, -1, -1),
omath.Vec3.new(-1, 1, -1),
omath.Vec3.new(1, 1, -1),
omath.Vec3.new(-1, -1, 1),
omath.Vec3.new(1, -1, 1),
omath.Vec3.new(-1, 1, 1),
omath.Vec3.new(1, 1, 1),
}
end
function Collision_Aabb_constructor_and_fields()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(1, 2, 3))
assert(aabb.min.x == -1 and aabb.max.z == 3)
aabb.max = omath.Vec3.new(2, 3, 4)
assert(aabb.max.x == 2 and aabb.max.y == 3 and aabb.max.z == 4)
end
function Collision_Aabb_center_extents()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
local center = aabb:center()
local extents = aabb:extents()
assert(center.x == 1 and center.y == 2 and center.z == 3)
assert(extents.x == 2 and extents.y == 4 and extents.z == 6)
end
function Collision_Aabb_top_bottom_default_axis()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
assert(aabb:top().y == 6)
assert(aabb:bottom().y == -2)
end
function Collision_Aabb_top_bottom_explicit_axis()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(3, 6, 9))
assert(aabb:top(omath.primitives.UpAxis.X).x == 3)
assert(aabb:bottom(omath.primitives.UpAxis.Z).z == -3)
end
function Collision_Aabb_is_collide()
local a = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
local b = omath.primitives.Aabb.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(2, 2, 2))
local c = omath.primitives.Aabb.new(omath.Vec3.new(3, 3, 3), omath.Vec3.new(4, 4, 4))
assert(a:is_collide(b))
assert(not a:is_collide(c))
end
function Collision_Aabb_as_table()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -2, -3), omath.Vec3.new(1, 2, 3))
local t = aabb:as_table()
assert(t.min.x == -1 and t.max.z == 3)
end
function Collision_Obb_constructor_and_vertices()
local obb = omath.primitives.Obb.new(
omath.Vec3.new(0, 0, 0),
omath.Vec3.new(1, 0, 0),
omath.Vec3.new(0, 1, 0),
omath.Vec3.new(0, 0, 1),
omath.Vec3.new(1, 2, 3)
)
local vertices = obb:vertices()
assert(#vertices == 8)
assert(vertices[1].x == -1 and vertices[1].y == -2 and vertices[1].z == -3)
assert(vertices[8].x == 1 and vertices[8].y == 2 and vertices[8].z == 3)
end
function Collision_Ray_constructor_and_direction()
local ray = omath.collision.Ray.new(omath.Vec3.new(1, 2, 3), omath.Vec3.new(4, 6, 3), true)
local dir = ray:direction_vector()
local normal = ray:direction_vector_normalized()
assert(ray.infinite_length)
assert(dir.x == 3 and dir.y == 4 and dir.z == 0)
assert(approx(normal:length(), 1))
end
function Collision_LineTracer_triangle_hit()
local tri = omath.Triangle.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(2, 0, 0), omath.Vec3.new(0, 2, 0))
local ray = omath.collision.Ray.new(omath.Vec3.new(0.5, 0.5, -1), omath.Vec3.new(0.5, 0.5, 1))
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, tri)
assert(omath.collision.LineTracer.ray_hits_triangle(ray, tri))
assert(not omath.collision.LineTracer.can_trace_line(ray, tri))
assert(approx(hit.x, 0.5) and approx(hit.y, 0.5) and approx(hit.z, 0))
end
function Collision_LineTracer_triangle_miss()
local tri = omath.Triangle.new(omath.Vec3.new(0, 0, 0), omath.Vec3.new(1, 0, 0), omath.Vec3.new(0, 1, 0))
local ray = omath.collision.Ray.new(omath.Vec3.new(2, 2, -1), omath.Vec3.new(2, 2, 1))
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, tri)
assert(not omath.collision.LineTracer.ray_hits_triangle(ray, tri))
assert(omath.collision.LineTracer.can_trace_line(ray, tri))
assert(hit == ray["end"])
end
function Collision_LineTracer_aabb_hit()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 0, -3), omath.Vec3.new(0, 0, 3))
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, aabb)
assert(omath.collision.LineTracer.ray_hits_aabb(ray, aabb))
assert(approx(hit.x, 0) and approx(hit.y, 0) and approx(hit.z, -1))
end
function Collision_LineTracer_aabb_miss()
local aabb = omath.primitives.Aabb.new(omath.Vec3.new(-1, -1, -1), omath.Vec3.new(1, 1, 1))
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 3, -3), omath.Vec3.new(0, 3, 3))
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, aabb)
assert(not omath.collision.LineTracer.ray_hits_aabb(ray, aabb))
assert(hit == ray["end"])
end
function Collision_LineTracer_obb_hit()
local obb = omath.primitives.Obb.new(
omath.Vec3.new(0, 0, 0),
omath.Vec3.new(1, 0, 0),
omath.Vec3.new(0, 1, 0),
omath.Vec3.new(0, 0, 1),
omath.Vec3.new(1, 1, 1)
)
local ray = omath.collision.Ray.new(omath.Vec3.new(0, 0, -3), omath.Vec3.new(0, 0, 3))
local hit = omath.collision.LineTracer.get_ray_hit_point(ray, obb)
assert(omath.collision.LineTracer.ray_hits_obb(ray, obb))
assert(approx(hit.z, -1))
end
function Collision_ConvexCollider_vertices_and_support()
local collider = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(2, 0, 0))
assert(collider:vertex_count() == 8)
assert(#collider:vertices() == 8)
local support = collider:find_abs_furthest_vertex_position(omath.Vec3.new(1, 0, 0))
assert(support.x == 3)
end
function Collision_Gjk_support_vertex()
local a = omath.collision.ConvexCollider.new(cube_points())
local b = omath.collision.ConvexCollider.new(cube_points())
local support = omath.collision.gjk_support_vertex(a, b, omath.Vec3.new(1, 0, 0))
assert(support.x == 2 and support.y == 0 and support.z == 0)
end
function Collision_Gjk_collide_true()
local a = omath.collision.ConvexCollider.new(cube_points())
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(1, 0, 0))
assert(omath.collision.gjk_collide(a, b))
end
function Collision_Gjk_collide_false()
local a = omath.collision.ConvexCollider.new(cube_points())
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(5, 0, 0))
assert(not omath.collision.gjk_collide(a, b))
end
function Collision_Epa_solve_hit()
local a = omath.collision.ConvexCollider.new(cube_points())
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(1.5, 0, 0))
local result = omath.collision.epa_solve(a, b)
assert(result ~= nil)
assert(result.depth > 0)
assert(approx(result.normal:length(), 1))
assert(approx(result.penetration_vector:length(), result.depth))
end
function Collision_Epa_solve_miss()
local a = omath.collision.ConvexCollider.new(cube_points())
local b = omath.collision.ConvexCollider.new(cube_points(), omath.Vec3.new(5, 0, 0))
assert(omath.collision.epa_solve(a, b) == nil)
end
+244
View File
@@ -0,0 +1,244 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-4) end
local function color() return omath.Color.new(1, 0, 0, 1) end
local function fill() return omath.Color.new(0, 0, 0, 0.5) end
local function outline() return omath.Color.new(0, 0, 0, 1) end
local function bg() return omath.Color.new(0.2, 0.2, 0.2, 1) end
local function renderer_with(commands)
return omath.hud.Renderer.new({
add_line = function(a, b, c, thickness)
table.insert(commands, { kind = "line", a = a, b = b, color = c, thickness = thickness })
end,
add_polyline = function(points, c, thickness)
table.insert(commands, { kind = "polyline", points = points, color = c, thickness = thickness })
end,
add_filled_polyline = function(points, c)
table.insert(commands, { kind = "filled_polyline", points = points, color = c })
end,
add_rectangle = function(min, max, c)
table.insert(commands, { kind = "rectangle", min = min, max = max, color = c })
end,
add_filled_rectangle = function(min, max, c)
table.insert(commands, { kind = "filled_rectangle", min = min, max = max, color = c })
end,
add_circle = function(center, radius, c, thickness, segments)
table.insert(commands, {
kind = "circle",
center = center,
radius = radius,
color = c,
thickness = thickness,
segments = segments,
})
end,
add_filled_circle = function(center, radius, c, segments)
table.insert(commands, { kind = "filled_circle", center = center, radius = radius, color = c, segments = segments })
end,
add_arc = function(center, radius, a_min, a_max, c, thickness, segments)
table.insert(commands, {
kind = "arc",
center = center,
radius = radius,
a_min = a_min,
a_max = a_max,
color = c,
thickness = thickness,
segments = segments,
})
end,
add_image = function(texture, min, max, tint)
table.insert(commands, { kind = "image", texture = texture, min = min, max = max, tint = tint })
end,
add_text = function(pos, c, text)
table.insert(commands, { kind = "text", pos = pos, color = c, text = text })
end,
calc_text_size = function(text)
return omath.Vec2.new(#text * 6, 10)
end,
})
end
local function overlay(commands)
return omath.hud.EntityOverlay.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50), renderer_with(commands))
end
function Hud_CanvasBox_default_ratio()
local box = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50))
assert(approx(box.top_left_corner.x, 90) and approx(box.top_left_corner.y, 10))
assert(approx(box.top_right_corner.x, 110) and approx(box.bottom_right_corner.y, 50))
end
function Hud_CanvasBox_custom_ratio()
local box = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50), 2)
assert(approx(box.top_left_corner.x, 80) and approx(box.bottom_right_corner.x, 120))
end
function Hud_CanvasBox_as_table()
local points = omath.hud.CanvasBox.new(omath.Vec2.new(100, 10), omath.Vec2.new(100, 50)):as_table()
assert(#points == 4)
assert(approx(points[1].x, 90) and approx(points[3].x, 110))
end
function Hud_Renderer_callbacks()
local commands = {}
local renderer = renderer_with(commands)
renderer:add_line(omath.Vec2.new(1, 2), omath.Vec2.new(3, 4), color(), 2)
renderer:add_text(omath.Vec2.new(5, 6), color(), "hp")
local size = renderer:calc_text_size("abcd")
assert(#commands == 2)
assert(commands[1].kind == "line" and commands[2].text == "hp")
assert(size.x == 24 and size.y == 10)
end
function Hud_Renderer_polyline_table()
local commands = {}
local renderer = renderer_with(commands)
renderer:add_polyline({ omath.Vec2.new(1, 1), omath.Vec2.new(2, 2) }, color(), 3)
assert(commands[1].kind == "polyline")
assert(#commands[1].points == 2 and commands[1].points[2].x == 2)
end
function Hud_Renderer_circle_defaults()
local commands = {}
local renderer = renderer_with(commands)
renderer:add_circle(omath.Vec2.new(1, 2), 5, color(), 2)
renderer:add_filled_circle(omath.Vec2.new(3, 4), 6, color())
assert(commands[1].kind == "circle" and commands[1].segments == 0)
assert(commands[2].kind == "filled_circle" and commands[2].segments == 0)
end
function Hud_EntityOverlay_add_2d_box()
local commands = {}
overlay(commands):add_2d_box(color(), fill(), 2)
assert(#commands == 2)
assert(commands[1].kind == "polyline" and #commands[1].points == 4)
assert(commands[2].kind == "filled_polyline")
end
function Hud_EntityOverlay_add_cornered_2d_box()
local commands = {}
overlay(commands):add_cornered_2d_box(color(), fill(), 0.25, 2)
assert(#commands == 10)
assert(commands[1].kind == "polyline" and commands[10].kind == "line")
end
function Hud_EntityOverlay_add_dashed_box()
local commands = {}
overlay(commands):add_dashed_box(color(), 8, 5, 1)
assert(#commands > 4)
assert(commands[1].kind == "line")
end
function Hud_EntityOverlay_add_bar()
local commands = {}
overlay(commands):add_right_bar(color(), outline(), bg(), 4, 0.5)
assert(#commands == 3)
assert(commands[1].kind == "filled_rectangle" and commands[2].kind == "filled_rectangle")
assert(commands[3].kind == "rectangle")
end
function Hud_EntityOverlay_add_dashed_bar()
local commands = {}
overlay(commands):add_bottom_dashed_bar(color(), outline(), bg(), 4, 0.5, 8, 5)
assert(#commands >= 3)
assert(commands[1].kind == "filled_rectangle")
end
function Hud_EntityOverlay_add_label()
local commands = {}
overlay(commands):add_right_label(color(), 5, false, "HP")
assert(#commands == 1)
assert(commands[1].kind == "text" and commands[1].text == "HP")
assert(approx(commands[1].pos.x, 115))
end
function Hud_EntityOverlay_add_outlined_label()
local commands = {}
overlay(commands):add_left_label(color(), 5, true, "HP")
assert(#commands == 9)
assert(commands[9].kind == "text" and commands[9].text == "HP")
end
function Hud_EntityOverlay_add_centered_label()
local commands = {}
overlay(commands):add_centered_top_label(color(), 5, false, "HP")
assert(#commands == 1)
assert(commands[1].kind == "text")
assert(approx(commands[1].pos.x, 94))
end
function Hud_EntityOverlay_add_spaces()
local commands = {}
overlay(commands):add_right_space_vertical(10):add_right_label(color(), 5, false, "HP")
assert(#commands == 1)
assert(approx(commands[1].pos.y, 20))
end
function Hud_EntityOverlay_add_progress_ring()
local commands = {}
overlay(commands):add_right_progress_ring(color(), bg(), 6, 0.25, 2, 5, 16)
assert(#commands == 2)
assert(commands[1].kind == "circle" and commands[2].kind == "arc")
assert(commands[2].segments == 16)
end
function Hud_EntityOverlay_add_progress_ring_defaults()
local commands = {}
overlay(commands):add_right_progress_ring(color(), bg(), 6, 0.25)
assert(#commands == 2)
assert(commands[1].thickness == 2 and commands[1].segments == 0)
end
function Hud_EntityOverlay_add_icon()
local commands = {}
overlay(commands):add_right_icon("texture-id", 8, 6, color(), 5)
assert(#commands == 1)
assert(commands[1].kind == "image" and commands[1].texture == "texture-id")
end
function Hud_EntityOverlay_add_snap_line()
local commands = {}
overlay(commands):add_snap_line(omath.Vec2.new(0, 0), color(), 2)
assert(#commands == 1)
assert(commands[1].kind == "line")
assert(approx(commands[1].b.x, 100) and approx(commands[1].b.y, 50))
end
function Hud_EntityOverlay_add_skeleton()
local commands = {}
overlay(commands):add_skeleton(color())
assert(#commands == 15)
assert(commands[1].kind == "line")
end
function Hud_EntityOverlay_add_scan_marker()
local commands = {}
overlay(commands):add_scan_marker(color(), outline(), 2)
assert(#commands == 2)
assert(commands[1].kind == "filled_polyline" and #commands[1].points == 3)
assert(commands[2].kind == "polyline" and commands[2].thickness == 2)
end
function Hud_EntityOverlay_add_aim_dot()
local commands = {}
overlay(commands):add_aim_dot(omath.Vec2.new(5, 6), color())
assert(#commands == 1)
assert(commands[1].kind == "filled_circle" and commands[1].radius == 3)
end
function Hud_EntityOverlay_add_projectile_aim_circle()
local commands = {}
overlay(commands):add_projectile_aim(omath.Vec2.new(5, 6), color(), 4, 2, omath.hud.ProjectileAimFigure.CIRCLE)
assert(#commands == 2)
assert(commands[1].kind == "line")
assert(commands[2].kind == "filled_circle" and commands[2].radius == 4)
end
function Hud_EntityOverlay_add_projectile_aim_square_default()
local commands = {}
overlay(commands):add_projectile_aim(omath.Vec2.new(5, 6), color())
assert(#commands == 2)
assert(commands[1].kind == "line")
assert(commands[2].kind == "filled_rectangle")
end
+195
View File
@@ -0,0 +1,195 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end
function Mat4_Constructor_default()
local m = omath.Mat4.new()
assert(m:row_count() == 4 and m:columns_count() == 4)
assert(m:at(0, 0) == 0 and m:at(3, 3) == 0)
end
function Mat4_FromRows()
local m = omath.Mat4.from_rows({
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
})
assert(m:at(0, 0) == 1 and m:at(3, 3) == 16)
end
function Mat4_SetAt()
local m = omath.Mat4.new()
m:set_at(2, 3, 42)
assert(m:at(2, 3) == 42)
end
function Mat4_SetAndClear()
local m = omath.Mat4.new()
m:set(2)
assert(m:sum() == 32)
m:clear()
assert(m:sum() == 0)
end
function Mat4_Multiplication_matrix()
local identity = omath.Mat4.from_rows({
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
})
local m = omath.Mat4.from_rows({
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
})
local result = m * identity
assert(result == m)
end
function Mat4_Multiplication_scalar()
local m = omath.Mat4.from_rows({
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}) * 2
assert(m:at(0, 0) == 2 and m:at(3, 3) == 2)
end
function Mat4_Multiplication_scalar_reversed()
local m = 2 * omath.Mat4.from_rows({
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
})
assert(m:at(0, 0) == 2 and m:at(3, 3) == 2)
end
function Mat4_Division_scalar()
local m = omath.Mat4.from_rows({
{2, 0, 0, 0},
{0, 2, 0, 0},
{0, 0, 2, 0},
{0, 0, 0, 2},
}) / 2
assert(m:at(0, 0) == 1 and m:at(3, 3) == 1)
end
function Mat4_Transposed()
local m = omath.Mat4.from_rows({
{1, 2, 0, 0},
{3, 4, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}):transposed()
assert(m:at(0, 1) == 3 and m:at(1, 0) == 2)
end
function Mat4_Determinant()
local m = omath.Mat4.from_rows({
{1, 0, 0, 0},
{0, 2, 0, 0},
{0, 0, 3, 0},
{0, 0, 0, 4},
})
assert(m:determinant() == 24)
end
function Mat4_Inverted_success()
local m = omath.Mat4.from_rows({
{2, 0, 0, 0},
{0, 2, 0, 0},
{0, 0, 2, 0},
{0, 0, 0, 2},
})
local inv = m:inverted()
assert(inv ~= nil)
assert(approx(inv:at(0, 0), 0.5) and approx(inv:at(3, 3), 0.5))
end
function Mat4_Inverted_singular()
local m = omath.Mat4.new()
assert(m:inverted() == nil)
end
function Mat4_ToScreenMat()
local m = omath.Mat4.to_screen_mat(800, 600)
assert(m:at(0, 0) == 400 and m:at(1, 1) == -300 and m:at(3, 1) == 300)
end
function Mat4_Translation()
local m = omath.Mat4.translation(omath.Vec3.new(1, 2, 3))
assert(m:at(0, 3) == 1 and m:at(1, 3) == 2 and m:at(2, 3) == 3)
end
function Mat4_Scale()
local m = omath.Mat4.scale(omath.Vec3.new(2, 3, 4))
assert(m:at(0, 0) == 2 and m:at(1, 1) == 3 and m:at(2, 2) == 4)
end
function Mat4_Perspective()
local m = omath.Mat4.perspective_left_handed_vertical_fov(90, 16 / 9, 0.1, 1000)
assert(m:at(0, 0) > 0 and m:at(1, 1) > 0)
end
function Mat4_AsTable()
local m = omath.Mat4.from_rows({
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
})
local t = m:as_table()
assert(t[1][1] == 1 and t[4][4] == 16)
end
function Mat4_ToString()
local m = omath.Mat4.from_rows({
{1, 2, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
})
assert(string.find(tostring(m), "%[%[") ~= nil)
end
function Mat3_FromRows()
local m = omath.Mat3.from_rows({
{1, 2, 3},
{0, 1, 4},
{5, 6, 0},
})
assert(m:determinant() == 1)
end
function Mat4ColumnMajor_FromRows()
local m = omath.Mat4ColumnMajor.from_rows({
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16},
})
assert(m:at(0, 1) == 2 and m:at(3, 2) == 15)
end
function Mat4ColumnMajor_ToScreenMat()
local m = omath.Mat4ColumnMajor.to_screen_mat(800, 600)
assert(m:at(0, 0) == 400 and m:at(1, 1) == -300 and m:at(3, 1) == 300)
end
function Mat4ColumnMajor_Translation()
local m = omath.Mat4ColumnMajor.translation(omath.Vec3.new(1, 2, 3))
assert(m:at(0, 3) == 1 and m:at(1, 3) == 2 and m:at(2, 3) == 3)
end
function Mat4d_FromRows()
local m = omath.Mat4d.from_rows({
{1, 0, 0, 0},
{0, 2, 0, 0},
{0, 0, 3, 0},
{0, 0, 0, 4},
})
assert(m:determinant() == 24)
end
+111
View File
@@ -0,0 +1,111 @@
local function approx(a, b, eps) return math.abs(a - b) < (eps or 1e-5) end
function Quaternion_Constructor_default()
local q = omath.Quaternion.new()
assert(q.x == 0 and q.y == 0 and q.z == 0 and q.w == 1)
end
function Quaternion_Constructor_xyzw()
local q = omath.Quaternion.new(1, 2, 3, 4)
assert(q.x == 1 and q.y == 2 and q.z == 3 and q.w == 4)
end
function Quaternion_Field_mutation()
local q = omath.Quaternion.new()
q.x = 1; q.y = 2; q.z = 3; q.w = 4
assert(q.x == 1 and q.y == 2 and q.z == 3 and q.w == 4)
end
function Quaternion_FromAxisAngle_zero_angle()
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), 0)
assert(approx(q.x, 0) and approx(q.y, 0) and approx(q.z, 0) and approx(q.w, 1))
end
function Quaternion_Addition()
local q = omath.Quaternion.new(1, 2, 3, 4) + omath.Quaternion.new(4, 3, 2, 1)
assert(q.x == 5 and q.y == 5 and q.z == 5 and q.w == 5)
end
function Quaternion_Multiplication_scalar()
local q = omath.Quaternion.new(1, 2, 3, 4) * 2
assert(q.x == 2 and q.y == 4 and q.z == 6 and q.w == 8)
end
function Quaternion_Multiplication_scalar_reversed()
local q = 2 * omath.Quaternion.new(1, 2, 3, 4)
assert(q.x == 2 and q.y == 4 and q.z == 6 and q.w == 8)
end
function Quaternion_Multiplication_quaternion()
local i = omath.Quaternion.new(1, 0, 0, 0)
local j = omath.Quaternion.new(0, 1, 0, 0)
local k = i * j
assert(k.x == 0 and k.y == 0 and k.z == 1 and k.w == 0)
end
function Quaternion_UnaryMinus()
local q = -omath.Quaternion.new(1, -2, 3, -4)
assert(q.x == -1 and q.y == 2 and q.z == -3 and q.w == 4)
end
function Quaternion_EqualTo_true()
assert(omath.Quaternion.new(1, 2, 3, 4) == omath.Quaternion.new(1, 2, 3, 4))
end
function Quaternion_EqualTo_false()
assert(not (omath.Quaternion.new(1, 2, 3, 4) == omath.Quaternion.new(1, 2, 3, 5)))
end
function Quaternion_ToString()
assert(tostring(omath.Quaternion.new(1, 2, 3, 4)) == "Quaternion(1, 2, 3, 4)")
end
function Quaternion_Conjugate()
local q = omath.Quaternion.new(1, 2, 3, 4):conjugate()
assert(q.x == -1 and q.y == -2 and q.z == -3 and q.w == 4)
end
function Quaternion_Dot()
assert(omath.Quaternion.new(1, 0, 0, 0):dot(omath.Quaternion.new(0, 1, 0, 0)) == 0)
end
function Quaternion_Length()
assert(approx(omath.Quaternion.new(1, 1, 1, 1):length(), 2))
end
function Quaternion_LengthSqr()
assert(omath.Quaternion.new(1, 2, 3, 4):length_sqr() == 30)
end
function Quaternion_Normalized()
local q = omath.Quaternion.new(1, 1, 1, 1):normalized()
assert(approx(q:length(), 1))
assert(approx(q.x, 0.5) and approx(q.y, 0.5) and approx(q.z, 0.5) and approx(q.w, 0.5))
end
function Quaternion_Inverse()
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), math.pi / 3)
local identity = q * q:inverse()
assert(approx(identity.x, 0) and approx(identity.y, 0) and approx(identity.z, 0) and approx(identity.w, 1))
end
function Quaternion_Rotate()
local q = omath.Quaternion.from_axis_angle(omath.Vec3.new(0, 0, 1), math.pi / 2)
local v = q:rotate(omath.Vec3.new(1, 0, 0))
assert(approx(v.x, 0, 1e-4) and approx(v.y, 1, 1e-4) and approx(v.z, 0, 1e-4))
end
function Quaternion_ToRotationMatrix3()
local m = omath.Quaternion.new():to_rotation_matrix3()
assert(m:at(0, 0) == 1 and m:at(1, 1) == 1 and m:at(2, 2) == 1)
end
function Quaternion_ToRotationMatrix4()
local m = omath.Quaternion.new():to_rotation_matrix4()
assert(m:at(0, 0) == 1 and m:at(1, 1) == 1 and m:at(2, 2) == 1 and m:at(3, 3) == 1)
end
function Quaternion_AsTable()
local t = omath.Quaternion.new(1, 2, 3, 4):as_table()
assert(t.x == 1 and t.y == 2 and t.z == 3 and t.w == 4)
end
+40
View File
@@ -175,6 +175,46 @@ function Source_Camera_get_up()
assert(approx(make_camera():get_up():length(), 1.0))
end
function Source_Camera_get_view_matrix()
local view = make_camera():get_view_matrix()
assert(view ~= nil)
assert(view:row_count() == 4 and view:columns_count() == 4)
end
function Source_Camera_get_projection_matrix()
local projection = make_camera():get_projection_matrix()
assert(projection ~= nil)
assert(projection:at(0, 0) > 0 and projection:at(1, 1) > 0)
end
function Source_Camera_get_view_projection_matrix()
local view_projection = make_camera():get_view_projection_matrix()
assert(view_projection ~= nil)
assert(view_projection:row_count() == 4 and view_projection:columns_count() == 4)
end
function Source_Camera_extract_projection_params()
local projection = make_camera():get_projection_matrix()
local fov, aspect_ratio = omath.source.Camera.extract_projection_params(projection)
assert(fov ~= nil)
assert(approx(aspect_ratio, 1920 / 1080))
end
function Source_Camera_calc_view_angles_from_view_matrix()
local cam = make_camera()
local view = cam:get_view_matrix()
local angles = omath.source.Camera.calc_view_angles_from_view_matrix(view)
assert(approx(angles.pitch:as_degrees(), 0))
assert(approx(angles.yaw:as_degrees(), 0))
end
function Source_Camera_calc_origin_from_view_matrix()
local cam = make_camera()
cam:set_origin(omath.Vec3.new(1, 2, 3))
local origin = omath.source.Camera.calc_origin_from_view_matrix(cam:get_view_matrix())
assert(approx(origin.x, 1) and approx(origin.y, 2) and approx(origin.z, 3))
end
function Source_Camera_world_to_screen_success()
local cam = make_camera()
cam:look_at(omath.Vec3.new(1, 0, 0))
+53
View File
@@ -0,0 +1,53 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaCollision : 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 "/collision_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(LuaCollision, Aabb_constructor_and_fields) { check("Collision_Aabb_constructor_and_fields"); }
TEST_F(LuaCollision, Aabb_center_extents) { check("Collision_Aabb_center_extents"); }
TEST_F(LuaCollision, Aabb_top_bottom_default_axis) { check("Collision_Aabb_top_bottom_default_axis"); }
TEST_F(LuaCollision, Aabb_top_bottom_explicit_axis) { check("Collision_Aabb_top_bottom_explicit_axis"); }
TEST_F(LuaCollision, Aabb_is_collide) { check("Collision_Aabb_is_collide"); }
TEST_F(LuaCollision, Aabb_as_table) { check("Collision_Aabb_as_table"); }
TEST_F(LuaCollision, Obb_constructor_and_vertices) { check("Collision_Obb_constructor_and_vertices"); }
TEST_F(LuaCollision, Ray_constructor_and_direction) { check("Collision_Ray_constructor_and_direction"); }
TEST_F(LuaCollision, LineTracer_triangle_hit) { check("Collision_LineTracer_triangle_hit"); }
TEST_F(LuaCollision, LineTracer_triangle_miss) { check("Collision_LineTracer_triangle_miss"); }
TEST_F(LuaCollision, LineTracer_aabb_hit) { check("Collision_LineTracer_aabb_hit"); }
TEST_F(LuaCollision, LineTracer_aabb_miss) { check("Collision_LineTracer_aabb_miss"); }
TEST_F(LuaCollision, LineTracer_obb_hit) { check("Collision_LineTracer_obb_hit"); }
TEST_F(LuaCollision, ConvexCollider_vertices_support) { check("Collision_ConvexCollider_vertices_and_support"); }
TEST_F(LuaCollision, Gjk_support_vertex) { check("Collision_Gjk_support_vertex"); }
TEST_F(LuaCollision, Gjk_collide_true) { check("Collision_Gjk_collide_true"); }
TEST_F(LuaCollision, Gjk_collide_false) { check("Collision_Gjk_collide_false"); }
TEST_F(LuaCollision, Epa_solve_hit) { check("Collision_Epa_solve_hit"); }
TEST_F(LuaCollision, Epa_solve_miss) { check("Collision_Epa_solve_miss"); }
+58
View File
@@ -0,0 +1,58 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaHud : 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 "/hud_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(LuaHud, CanvasBox_default_ratio) { check("Hud_CanvasBox_default_ratio"); }
TEST_F(LuaHud, CanvasBox_custom_ratio) { check("Hud_CanvasBox_custom_ratio"); }
TEST_F(LuaHud, CanvasBox_as_table) { check("Hud_CanvasBox_as_table"); }
TEST_F(LuaHud, Renderer_callbacks) { check("Hud_Renderer_callbacks"); }
TEST_F(LuaHud, Renderer_polyline_table) { check("Hud_Renderer_polyline_table"); }
TEST_F(LuaHud, Renderer_circle_defaults) { check("Hud_Renderer_circle_defaults"); }
TEST_F(LuaHud, EntityOverlay_add_2d_box) { check("Hud_EntityOverlay_add_2d_box"); }
TEST_F(LuaHud, EntityOverlay_add_cornered_2d_box) { check("Hud_EntityOverlay_add_cornered_2d_box"); }
TEST_F(LuaHud, EntityOverlay_add_dashed_box) { check("Hud_EntityOverlay_add_dashed_box"); }
TEST_F(LuaHud, EntityOverlay_add_bar) { check("Hud_EntityOverlay_add_bar"); }
TEST_F(LuaHud, EntityOverlay_add_dashed_bar) { check("Hud_EntityOverlay_add_dashed_bar"); }
TEST_F(LuaHud, EntityOverlay_add_label) { check("Hud_EntityOverlay_add_label"); }
TEST_F(LuaHud, EntityOverlay_add_outlined_label) { check("Hud_EntityOverlay_add_outlined_label"); }
TEST_F(LuaHud, EntityOverlay_add_centered_label) { check("Hud_EntityOverlay_add_centered_label"); }
TEST_F(LuaHud, EntityOverlay_add_spaces) { check("Hud_EntityOverlay_add_spaces"); }
TEST_F(LuaHud, EntityOverlay_add_progress_ring) { check("Hud_EntityOverlay_add_progress_ring"); }
TEST_F(LuaHud, EntityOverlay_add_progress_ring_defaults) { check("Hud_EntityOverlay_add_progress_ring_defaults"); }
TEST_F(LuaHud, EntityOverlay_add_icon) { check("Hud_EntityOverlay_add_icon"); }
TEST_F(LuaHud, EntityOverlay_add_snap_line) { check("Hud_EntityOverlay_add_snap_line"); }
TEST_F(LuaHud, EntityOverlay_add_skeleton) { check("Hud_EntityOverlay_add_skeleton"); }
TEST_F(LuaHud, EntityOverlay_add_scan_marker) { check("Hud_EntityOverlay_add_scan_marker"); }
TEST_F(LuaHud, EntityOverlay_add_aim_dot) { check("Hud_EntityOverlay_add_aim_dot"); }
TEST_F(LuaHud, EntityOverlay_add_projectile_aim_circle) { check("Hud_EntityOverlay_add_projectile_aim_circle"); }
TEST_F(LuaHud, EntityOverlay_add_projectile_aim_square_default) { check("Hud_EntityOverlay_add_projectile_aim_square_default"); }
+129
View File
@@ -0,0 +1,129 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaMat : 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 "/mat_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(LuaMat, Constructor_default)
{
check("Mat4_Constructor_default");
}
TEST_F(LuaMat, FromRows)
{
check("Mat4_FromRows");
}
TEST_F(LuaMat, SetAt)
{
check("Mat4_SetAt");
}
TEST_F(LuaMat, SetAndClear)
{
check("Mat4_SetAndClear");
}
TEST_F(LuaMat, Multiplication_matrix)
{
check("Mat4_Multiplication_matrix");
}
TEST_F(LuaMat, Multiplication_scalar)
{
check("Mat4_Multiplication_scalar");
}
TEST_F(LuaMat, Multiplication_scalar_reversed)
{
check("Mat4_Multiplication_scalar_reversed");
}
TEST_F(LuaMat, Division_scalar)
{
check("Mat4_Division_scalar");
}
TEST_F(LuaMat, Transposed)
{
check("Mat4_Transposed");
}
TEST_F(LuaMat, Determinant)
{
check("Mat4_Determinant");
}
TEST_F(LuaMat, Inverted_success)
{
check("Mat4_Inverted_success");
}
TEST_F(LuaMat, Inverted_singular)
{
check("Mat4_Inverted_singular");
}
TEST_F(LuaMat, ToScreenMat)
{
check("Mat4_ToScreenMat");
}
TEST_F(LuaMat, Translation)
{
check("Mat4_Translation");
}
TEST_F(LuaMat, Scale)
{
check("Mat4_Scale");
}
TEST_F(LuaMat, Perspective)
{
check("Mat4_Perspective");
}
TEST_F(LuaMat, AsTable)
{
check("Mat4_AsTable");
}
TEST_F(LuaMat, ToString)
{
check("Mat4_ToString");
}
TEST_F(LuaMat, Mat3_FromRows)
{
check("Mat3_FromRows");
}
TEST_F(LuaMat, Mat4ColumnMajor_FromRows)
{
check("Mat4ColumnMajor_FromRows");
}
TEST_F(LuaMat, Mat4ColumnMajor_ToScreenMat)
{
check("Mat4ColumnMajor_ToScreenMat");
}
TEST_F(LuaMat, Mat4ColumnMajor_Translation)
{
check("Mat4ColumnMajor_Translation");
}
TEST_F(LuaMat, Mat4d_FromRows)
{
check("Mat4d_FromRows");
}
+125
View File
@@ -0,0 +1,125 @@
//
// Created by orange on 07.03.2026.
//
#include <gtest/gtest.h>
#include <lua.hpp>
#include <omath/lua/lua.hpp>
class LuaQuaternion : 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 "/quaternion_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(LuaQuaternion, Constructor_default)
{
check("Quaternion_Constructor_default");
}
TEST_F(LuaQuaternion, Constructor_xyzw)
{
check("Quaternion_Constructor_xyzw");
}
TEST_F(LuaQuaternion, Field_mutation)
{
check("Quaternion_Field_mutation");
}
TEST_F(LuaQuaternion, FromAxisAngle_zero_angle)
{
check("Quaternion_FromAxisAngle_zero_angle");
}
TEST_F(LuaQuaternion, Addition)
{
check("Quaternion_Addition");
}
TEST_F(LuaQuaternion, Multiplication_scalar)
{
check("Quaternion_Multiplication_scalar");
}
TEST_F(LuaQuaternion, Multiplication_scalar_reversed)
{
check("Quaternion_Multiplication_scalar_reversed");
}
TEST_F(LuaQuaternion, Multiplication_quaternion)
{
check("Quaternion_Multiplication_quaternion");
}
TEST_F(LuaQuaternion, UnaryMinus)
{
check("Quaternion_UnaryMinus");
}
TEST_F(LuaQuaternion, EqualTo_true)
{
check("Quaternion_EqualTo_true");
}
TEST_F(LuaQuaternion, EqualTo_false)
{
check("Quaternion_EqualTo_false");
}
TEST_F(LuaQuaternion, ToString)
{
check("Quaternion_ToString");
}
TEST_F(LuaQuaternion, Conjugate)
{
check("Quaternion_Conjugate");
}
TEST_F(LuaQuaternion, Dot)
{
check("Quaternion_Dot");
}
TEST_F(LuaQuaternion, Length)
{
check("Quaternion_Length");
}
TEST_F(LuaQuaternion, LengthSqr)
{
check("Quaternion_LengthSqr");
}
TEST_F(LuaQuaternion, Normalized)
{
check("Quaternion_Normalized");
}
TEST_F(LuaQuaternion, Inverse)
{
check("Quaternion_Inverse");
}
TEST_F(LuaQuaternion, Rotate)
{
check("Quaternion_Rotate");
}
TEST_F(LuaQuaternion, ToRotationMatrix3)
{
check("Quaternion_ToRotationMatrix3");
}
TEST_F(LuaQuaternion, ToRotationMatrix4)
{
check("Quaternion_ToRotationMatrix4");
}
TEST_F(LuaQuaternion, AsTable)
{
check("Quaternion_AsTable");
}
@@ -74,6 +74,12 @@ TEST_F(LuaSourceEngine, Camera_look_at) { check("Source_Camera_l
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_get_view_matrix) { check("Source_Camera_get_view_matrix"); }
TEST_F(LuaSourceEngine, Camera_get_projection_matrix) { check("Source_Camera_get_projection_matrix"); }
TEST_F(LuaSourceEngine, Camera_get_view_projection_matrix) { check("Source_Camera_get_view_projection_matrix"); }
TEST_F(LuaSourceEngine, Camera_extract_projection_params) { check("Source_Camera_extract_projection_params"); }
TEST_F(LuaSourceEngine, Camera_calc_view_angles_from_view_matrix) { check("Source_Camera_calc_view_angles_from_view_matrix"); }
TEST_F(LuaSourceEngine, Camera_calc_origin_from_view_matrix) { check("Source_Camera_calc_origin_from_view_matrix"); }
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"); }