Compare commits

...

9 Commits

Author SHA1 Message Date
a9ff7868cf simplified code 2026-03-23 05:52:35 +03:00
be80a5d243 added as_vector3 to view angles 2026-03-23 05:23:53 +03:00
881d3b9a2a added fields 2026-03-22 19:07:38 +03:00
f60e18b6ba replaced with table offset 2026-03-22 18:58:07 +03:00
0769d3d079 replaced with auto 2026-03-22 17:30:25 +03:00
b6755e21f9 fix 2026-03-22 16:32:00 +03:00
2287602fa2 Merge pull request #176 from orange-cpp/feature/vtable_index
added stuff
2026-03-22 16:21:39 +03:00
663890706e test fix 2026-03-22 16:06:57 +03:00
ab103f626b swaped to std::uintptr_t 2026-03-22 16:05:09 +03:00
12 changed files with 404 additions and 28 deletions

View File

@@ -35,13 +35,8 @@ namespace omath::algorithm
const auto current_target_angles = camera.calc_look_at_angles(get_position(*current));
const auto best_target_angles = camera.calc_look_at_angles(get_position(*best_target));
const Vector2<float> current_angles_vec = {current_target_angles.pitch.as_degrees(),
current_target_angles.yaw.as_degrees()};
const Vector2<float> best_angles_vec = {best_target_angles.pitch.as_degrees(),
best_target_angles.yaw.as_degrees()};
const auto current_target_distance = camera_angles_vec.distance_to(current_angles_vec);
const auto best_target_distance = camera_angles_vec.distance_to(best_angles_vec);
const auto current_target_distance = camera_angles_vec.distance_to(current_target_angles.as_vector3());
const auto best_target_distance = camera_angles.as_vector3().distance_to(best_target_angles.as_vector3());
if (current_target_distance < best_target_distance)
best_target = current;
}

View File

@@ -116,19 +116,31 @@ namespace omath::rev_eng
return call_method<ReturnType>(vtable[Id], arg_list...);
}
template<std::size_t TableIndex, std::size_t Id, class ReturnType>
template<std::ptrdiff_t TableOffset, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list)
{
const auto vtable = *reinterpret_cast<void***>(
reinterpret_cast<std::uintptr_t>(this) + TableIndex * sizeof(void*));
return call_method<ReturnType>(vtable[Id], arg_list...);
auto sub_this = reinterpret_cast<void*>(
reinterpret_cast<std::uintptr_t>(this) + TableOffset);
const auto vtable = *reinterpret_cast<void***>(sub_this);
#ifdef _MSC_VER
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using Fn = ReturnType(*)(void*, decltype(arg_list)...);
#endif
return reinterpret_cast<Fn>(vtable[Id])(sub_this, arg_list...);
}
template<std::size_t TableIndex, std::size_t Id, class ReturnType>
template<std::ptrdiff_t TableOffset, std::size_t Id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) const
{
const auto vtable = *reinterpret_cast<void* const* const*>(
reinterpret_cast<std::uintptr_t>(this) + TableIndex * sizeof(void*));
return call_method<ReturnType>(vtable[Id], arg_list...);
auto sub_this = reinterpret_cast<const void*>(
reinterpret_cast<std::uintptr_t>(this) + TableOffset);
const auto vtable = *reinterpret_cast<void* const* const*>(sub_this);
#ifdef _MSC_VER
using Fn = ReturnType(__thiscall*)(const void*, decltype(arg_list)...);
#else
using Fn = ReturnType(*)(const void*, decltype(arg_list)...);
#endif
return reinterpret_cast<Fn>(vtable[Id])(sub_this, arg_list...);
}
private:

View File

@@ -36,6 +36,7 @@ namespace omath
}
public:
using ArithmeticType = Type;
[[nodiscard]]
constexpr static Angle from_degrees(const Type& degrees) noexcept
{

View File

@@ -2,14 +2,25 @@
// Created by Orange on 11/30/2024.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
#include <type_traits>
namespace omath
{
template<class PitchType, class YawType, class RollType>
requires std::is_same_v<typename PitchType::ArithmeticType, typename YawType::ArithmeticType>
&& std::is_same_v<typename YawType::ArithmeticType, typename RollType::ArithmeticType>
struct ViewAngles
{
using ArithmeticType = PitchType::ArithmeticType;
PitchType pitch;
YawType yaw;
RollType roll;
[[nodiscard]]
Vector3<ArithmeticType> as_vector3() const
{
return {pitch.as_degrees(), yaw.as_degrees(), roll.as_degrees()};
}
};
} // namespace omath

View File

@@ -237,4 +237,54 @@ TEST(unit_test_cry_engine, loook_at_random_z_axis)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_cry_engine, ViewAnglesAsVector3Zero)
{
const omath::cry_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_cry_engine, ViewAnglesAsVector3Values)
{
const omath::cry_engine::ViewAngles angles{
omath::cry_engine::PitchAngle::from_degrees(45.f),
omath::cry_engine::YawAngle::from_degrees(-90.f),
omath::cry_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_cry_engine, ViewAnglesAsVector3ClampedPitch)
{
// Pitch is clamped to [-90, 90]
const omath::cry_engine::ViewAngles angles{
omath::cry_engine::PitchAngle::from_degrees(120.f),
omath::cry_engine::YawAngle::from_degrees(0.f),
omath::cry_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f);
}
TEST(unit_test_cry_engine, ViewAnglesAsVector3NormalizedYaw)
{
// Yaw is normalized to [-180, 180], 270 wraps to -90
const omath::cry_engine::ViewAngles angles{
omath::cry_engine::PitchAngle::from_degrees(0.f),
omath::cry_engine::YawAngle::from_degrees(270.f),
omath::cry_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -405,3 +405,51 @@ TEST(unit_test_frostbite_engine, look_at_down)
std::views::zip(dir_vector.as_array(), (-omath::frostbite_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3Zero)
{
const omath::frostbite_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3Values)
{
const omath::frostbite_engine::ViewAngles angles{
omath::frostbite_engine::PitchAngle::from_degrees(45.f),
omath::frostbite_engine::YawAngle::from_degrees(-90.f),
omath::frostbite_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3ClampedPitch)
{
const omath::frostbite_engine::ViewAngles angles{
omath::frostbite_engine::PitchAngle::from_degrees(120.f),
omath::frostbite_engine::YawAngle::from_degrees(0.f),
omath::frostbite_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f);
}
TEST(unit_test_frostbite_engine, ViewAnglesAsVector3NormalizedYaw)
{
const omath::frostbite_engine::ViewAngles angles{
omath::frostbite_engine::PitchAngle::from_degrees(0.f),
omath::frostbite_engine::YawAngle::from_degrees(270.f),
omath::frostbite_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -280,4 +280,54 @@ TEST(unit_test_iw_engine, look_at_down)
EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f);
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
}
TEST(unit_test_iw_engine, ViewAnglesAsVector3Zero)
{
const omath::iw_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_iw_engine, ViewAnglesAsVector3Values)
{
const omath::iw_engine::ViewAngles angles{
omath::iw_engine::PitchAngle::from_degrees(45.f),
omath::iw_engine::YawAngle::from_degrees(-90.f),
omath::iw_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_iw_engine, ViewAnglesAsVector3ClampedPitch)
{
// Pitch is clamped to [-89, 89]
const omath::iw_engine::ViewAngles angles{
omath::iw_engine::PitchAngle::from_degrees(120.f),
omath::iw_engine::YawAngle::from_degrees(0.f),
omath::iw_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 89.f);
}
TEST(unit_test_iw_engine, ViewAnglesAsVector3NormalizedYaw)
{
// Yaw is normalized to [-180, 180], 270 wraps to -90
const omath::iw_engine::ViewAngles angles{
omath::iw_engine::PitchAngle::from_degrees(0.f),
omath::iw_engine::YawAngle::from_degrees(270.f),
omath::iw_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -394,4 +394,52 @@ TEST(unit_test_opengl_engine, look_at_down)
const auto dir_vector = omath::opengl_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::opengl_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_opengl, ViewAnglesAsVector3Zero)
{
const omath::opengl_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_opengl, ViewAnglesAsVector3Values)
{
const omath::opengl_engine::ViewAngles angles{
omath::opengl_engine::PitchAngle::from_degrees(45.f),
omath::opengl_engine::YawAngle::from_degrees(-90.f),
omath::opengl_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_opengl, ViewAnglesAsVector3ClampedPitch)
{
const omath::opengl_engine::ViewAngles angles{
omath::opengl_engine::PitchAngle::from_degrees(120.f),
omath::opengl_engine::YawAngle::from_degrees(0.f),
omath::opengl_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f);
}
TEST(unit_test_opengl, ViewAnglesAsVector3NormalizedYaw)
{
const omath::opengl_engine::ViewAngles angles{
omath::opengl_engine::PitchAngle::from_degrees(0.f),
omath::opengl_engine::YawAngle::from_degrees(270.f),
omath::opengl_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -422,4 +422,54 @@ TEST(unit_test_source_engine, look_at_down)
EXPECT_NEAR(dir_vector.z, -0.99984f, 0.0001f);
EXPECT_NEAR(dir_vector.x,- 0.017f, 0.01f);
EXPECT_NEAR(dir_vector.y, 0.f, 0.001f);
}
TEST(unit_test_source_engine, ViewAnglesAsVector3Zero)
{
const omath::source_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_source_engine, ViewAnglesAsVector3Values)
{
const omath::source_engine::ViewAngles angles{
omath::source_engine::PitchAngle::from_degrees(45.f),
omath::source_engine::YawAngle::from_degrees(-90.f),
omath::source_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_source_engine, ViewAnglesAsVector3ClampedPitch)
{
// Pitch is clamped to [-89, 89]
const omath::source_engine::ViewAngles angles{
omath::source_engine::PitchAngle::from_degrees(120.f),
omath::source_engine::YawAngle::from_degrees(0.f),
omath::source_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 89.f);
}
TEST(unit_test_source_engine, ViewAnglesAsVector3NormalizedYaw)
{
// Yaw is normalized to [-180, 180], 270 wraps to -90
const omath::source_engine::ViewAngles angles{
omath::source_engine::PitchAngle::from_degrees(0.f),
omath::source_engine::YawAngle::from_degrees(270.f),
omath::source_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -417,3 +417,51 @@ TEST(unit_test_unity_engine, look_at_down)
std::views::zip(dir_vector.as_array(), (-omath::unity_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unity_engine, ViewAnglesAsVector3Zero)
{
const omath::unity_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_unity_engine, ViewAnglesAsVector3Values)
{
const omath::unity_engine::ViewAngles angles{
omath::unity_engine::PitchAngle::from_degrees(45.f),
omath::unity_engine::YawAngle::from_degrees(-90.f),
omath::unity_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_unity_engine, ViewAnglesAsVector3ClampedPitch)
{
const omath::unity_engine::ViewAngles angles{
omath::unity_engine::PitchAngle::from_degrees(120.f),
omath::unity_engine::YawAngle::from_degrees(0.f),
omath::unity_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f);
}
TEST(unit_test_unity_engine, ViewAnglesAsVector3NormalizedYaw)
{
const omath::unity_engine::ViewAngles angles{
omath::unity_engine::PitchAngle::from_degrees(0.f),
omath::unity_engine::YawAngle::from_degrees(270.f),
omath::unity_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -417,4 +417,52 @@ TEST(unit_test_unreal_engine, look_at_down)
const auto dir_vector = omath::unreal_engine::forward_vector(angles);
for (const auto& [result, etalon] : std::views::zip(dir_vector.as_array(), (-omath::unreal_engine::k_abs_up).as_array()))
EXPECT_NEAR(result, etalon, 0.0001f);
}
TEST(unit_test_unreal_engine, ViewAnglesAsVector3Zero)
{
const omath::unreal_engine::ViewAngles angles{};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 0.f);
EXPECT_FLOAT_EQ(vec.y, 0.f);
EXPECT_FLOAT_EQ(vec.z, 0.f);
}
TEST(unit_test_unreal_engine, ViewAnglesAsVector3Values)
{
const omath::unreal_engine::ViewAngles angles{
omath::unreal_engine::PitchAngle::from_degrees(45.f),
omath::unreal_engine::YawAngle::from_degrees(-90.f),
omath::unreal_engine::RollAngle::from_degrees(30.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 45.f);
EXPECT_FLOAT_EQ(vec.y, -90.f);
EXPECT_FLOAT_EQ(vec.z, 30.f);
}
TEST(unit_test_unreal_engine, ViewAnglesAsVector3ClampedPitch)
{
const omath::unreal_engine::ViewAngles angles{
omath::unreal_engine::PitchAngle::from_degrees(120.f),
omath::unreal_engine::YawAngle::from_degrees(0.f),
omath::unreal_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_FLOAT_EQ(vec.x, 90.f);
}
TEST(unit_test_unreal_engine, ViewAnglesAsVector3NormalizedYaw)
{
const omath::unreal_engine::ViewAngles angles{
omath::unreal_engine::PitchAngle::from_degrees(0.f),
omath::unreal_engine::YawAngle::from_degrees(270.f),
omath::unreal_engine::RollAngle::from_degrees(0.f)
};
const auto vec = angles.as_vector3();
EXPECT_NEAR(vec.y, -90.f, 0.01f);
}

View File

@@ -30,7 +30,7 @@ inline const void* get_vtable_entry(const void* obj, const std::size_t index)
class BaseA
{
public:
virtual ~BaseA() = default;
int m_field_a{42};
[[nodiscard]] virtual int get_a() const { return 10; }
[[nodiscard]] virtual int get_a2() const { return 11; }
};
@@ -38,7 +38,8 @@ public:
class BaseB
{
public:
virtual ~BaseB() = default;
float m_field_b{3.14f};
double m_field_b2{2.71};
[[nodiscard]] virtual int get_b() const { return 20; }
[[nodiscard]] virtual int get_b2() const { return 21; }
};
@@ -46,26 +47,31 @@ public:
class MultiPlayer final : public BaseA, public BaseB
{
public:
int m_own_field{999};
[[nodiscard]] int get_a() const override { return 100; }
[[nodiscard]] int get_a2() const override { return 101; }
[[nodiscard]] int get_b() const override { return 200; }
[[nodiscard]] int get_b2() const override { return 201; }
};
// BaseA layout: [vptr_a][m_field_a(int)] — sizeof(BaseA) gives the full subobject size
// BaseB starts right after BaseA in MultiPlayer's layout
constexpr std::ptrdiff_t BASE_B_OFFSET = static_cast<std::ptrdiff_t>(sizeof(BaseA));
class RevMultiPlayer final : omath::rev_eng::InternalReverseEngineeredObject
{
public:
// Table 0 (BaseA vtable): index 0 = destructor, 1 = get_a, 2 = get_a2
[[nodiscard]] int rev_get_a() const { return call_virtual_method<0, 1, int>(); }
[[nodiscard]] int rev_get_a2() const { return call_virtual_method<0, 2, int>(); }
// Table at offset 0 (BaseA vtable): index 0 = get_a, 1 = get_a2
[[nodiscard]] int rev_get_a() const { return call_virtual_method<0, 0, int>(); }
[[nodiscard]] int rev_get_a2() const { return call_virtual_method<0, 1, int>(); }
// Table 1 (BaseB vtable): index 0 = destructor, 1 = get_b, 2 = get_b2
[[nodiscard]] int rev_get_b() const { return call_virtual_method<1, 1, int>(); }
[[nodiscard]] int rev_get_b2() const { return call_virtual_method<1, 2, int>(); }
// Table at BaseB offset (BaseB vtable): index 0 = get_b, 1 = get_b2
[[nodiscard]] int rev_get_b() const { return call_virtual_method<BASE_B_OFFSET, 0, int>(); }
[[nodiscard]] int rev_get_b2() const { return call_virtual_method<BASE_B_OFFSET, 1, int>(); }
// Non-const versions
int rev_get_a_mut() { return call_virtual_method<0, 1, int>(); }
int rev_get_b_mut() { return call_virtual_method<1, 1, int>(); }
int rev_get_a_mut() { return call_virtual_method<0, 0, int>(); }
int rev_get_b_mut() { return call_virtual_method<BASE_B_OFFSET, 0, int>(); }
};
class RevPlayer final : omath::rev_eng::InternalReverseEngineeredObject
@@ -160,6 +166,15 @@ TEST(unit_test_reverse_enineering, call_virtual_method_delegates_to_call_method)
EXPECT_EQ(2, rev->rev_bar_const());
}
TEST(unit_test_reverse_enineering, multi_player_base_b_offset_is_correct)
{
// Verify our compile-time offset matches the actual layout
MultiPlayer mp;
const auto* mp_addr = reinterpret_cast<const char*>(&mp);
const auto* b_addr = reinterpret_cast<const char*>(static_cast<const BaseB*>(&mp));
EXPECT_EQ(b_addr - mp_addr, BASE_B_OFFSET);
}
TEST(unit_test_reverse_enineering, call_virtual_method_table_index_first_table)
{
MultiPlayer mp;
@@ -173,7 +188,7 @@ TEST(unit_test_reverse_enineering, call_virtual_method_table_index_first_table)
TEST(unit_test_reverse_enineering, call_virtual_method_table_index_second_table)
{
MultiPlayer mp;
constexpr MultiPlayer mp;
const auto* rev = reinterpret_cast<const RevMultiPlayer*>(&mp);
EXPECT_EQ(mp.get_b(), rev->rev_get_b());
@@ -194,7 +209,7 @@ TEST(unit_test_reverse_enineering, call_virtual_method_table_index_non_const)
TEST(unit_test_reverse_enineering, call_virtual_method_table_zero_matches_default)
{
// Table 0 with the TableIndex overload should match the original non-TableIndex overload
MultiPlayer mp;
constexpr MultiPlayer mp;
const auto* rev = reinterpret_cast<const RevMultiPlayer*>(&mp);
// Both access table 0, method index 1 — should return the same value