mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-19 07:23:27 +00:00
Compare commits
2 Commits
91c2e0d74b
...
feature/vm
| Author | SHA1 | Date | |
|---|---|---|---|
| cb45b9bb04 | |||
| f3656f9d2c |
7
.idea/dictionaries/project.xml
generated
Normal file
7
.idea/dictionaries/project.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="project">
|
||||||
|
<words>
|
||||||
|
<w>vmprotect</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
@@ -31,6 +31,10 @@ option(OMATH_SUPRESS_SAFETY_CHECKS
|
|||||||
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
|
option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF)
|
||||||
option(OMATH_ENABLE_FORCE_INLINE
|
option(OMATH_ENABLE_FORCE_INLINE
|
||||||
"Will for compiler to make some functions to be force inlined no matter what" ON)
|
"Will for compiler to make some functions to be force inlined no matter what" ON)
|
||||||
|
option(OMATH_VMPROTECT_INTEGRATION
|
||||||
|
"omath will use vmprotect sdk to protect sensitive parts of code from reverse engineering"
|
||||||
|
OFF)
|
||||||
|
|
||||||
if(VCPKG_MANIFEST_FEATURES)
|
if(VCPKG_MANIFEST_FEATURES)
|
||||||
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
|
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
|
||||||
if(omath_feature STREQUAL "imgui")
|
if(omath_feature STREQUAL "imgui")
|
||||||
@@ -43,6 +47,8 @@ if(VCPKG_MANIFEST_FEATURES)
|
|||||||
set(OMATH_BUILD_BENCHMARK ON)
|
set(OMATH_BUILD_BENCHMARK ON)
|
||||||
elseif(omath_feature STREQUAL "examples")
|
elseif(omath_feature STREQUAL "examples")
|
||||||
set(OMATH_BUILD_EXAMPLES ON)
|
set(OMATH_BUILD_EXAMPLES ON)
|
||||||
|
elseif(omath_feature STREQUAL "vmprotect")
|
||||||
|
set(OMATH_VMPROTECT_INTEGRATION ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
endforeach()
|
endforeach()
|
||||||
@@ -107,6 +113,11 @@ if(OMATH_IMGUI_INTEGRATION)
|
|||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(OMATH_VMPROTECT_INTEGRATION)
|
||||||
|
find_package(vmprotect_sdk CONFIG REQUIRED)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC vmprotect_sdk::vmprotect_sdk)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(OMATH_USE_AVX2)
|
if(OMATH_USE_AVX2)
|
||||||
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2)
|
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_USE_AVX2)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
"hidden": true,
|
"hidden": true,
|
||||||
"inherits": ["linux-base", "vcpkg-base"],
|
"inherits": ["linux-base", "vcpkg-base"],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
|
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;vmprotect;examples"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ add_subdirectory(example_barycentric)
|
|||||||
add_subdirectory(example_glfw3)
|
add_subdirectory(example_glfw3)
|
||||||
add_subdirectory(example_proj_mat_builder)
|
add_subdirectory(example_proj_mat_builder)
|
||||||
add_subdirectory(example_signature_scan)
|
add_subdirectory(example_signature_scan)
|
||||||
|
add_subdirectory(exmple_var_encryption)
|
||||||
if(OMATH_ENABLE_VALGRIND)
|
if(OMATH_ENABLE_VALGRIND)
|
||||||
omath_setup_valgrind(example_projection_matrix_builder)
|
omath_setup_valgrind(example_projection_matrix_builder)
|
||||||
omath_setup_valgrind(example_signature_scan)
|
omath_setup_valgrind(example_signature_scan)
|
||||||
|
|||||||
10
examples/exmple_var_encryption/CMakeLists.txt
Normal file
10
examples/exmple_var_encryption/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
project(example_var_encryption)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME} main.cpp)
|
||||||
|
set_target_properties(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PROPERTIES CXX_STANDARD 23
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath)
|
||||||
15
examples/exmple_var_encryption/main.cpp
Normal file
15
examples/exmple_var_encryption/main.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Created by orange on 24.02.2026.
|
||||||
|
//
|
||||||
|
#include "omath/containers/encrypted_variable.hpp"
|
||||||
|
#include <omath/omath.hpp>
|
||||||
|
#include <print>
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
OMATH_DEF_CRYPT_VAR(int, 64) var{5};
|
||||||
|
var.encrypt();
|
||||||
|
std::println("{}", var.value());
|
||||||
|
var.decrypt();
|
||||||
|
std::println("{}", var.value());
|
||||||
|
return var.value();
|
||||||
|
}
|
||||||
@@ -14,15 +14,11 @@ namespace omath::collision
|
|||||||
Simplex<VertexType> simplex; // valid only if hit == true and size==4
|
Simplex<VertexType> simplex; // valid only if hit == true and size==4
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GjkSettings final
|
|
||||||
{
|
|
||||||
float epsilon = 1e-6f;
|
|
||||||
std::size_t max_iterations = 64;
|
|
||||||
};
|
|
||||||
template<class ColliderInterfaceType>
|
template<class ColliderInterfaceType>
|
||||||
class GjkAlgorithm final
|
class GjkAlgorithm final
|
||||||
{
|
{
|
||||||
using VectorType = ColliderInterfaceType::VectorType;
|
using VectorType = ColliderInterfaceType::VectorType;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static VectorType find_support_vertex(const ColliderInterfaceType& collider_a,
|
static VectorType find_support_vertex(const ColliderInterfaceType& collider_a,
|
||||||
@@ -40,8 +36,7 @@ namespace omath::collision
|
|||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static GjkHitInfo<VectorType> is_collide_with_simplex_info(const ColliderInterfaceType& collider_a,
|
static GjkHitInfo<VectorType> is_collide_with_simplex_info(const ColliderInterfaceType& collider_a,
|
||||||
const ColliderInterfaceType& collider_b,
|
const ColliderInterfaceType& collider_b)
|
||||||
const GjkSettings& settings = {})
|
|
||||||
{
|
{
|
||||||
auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0});
|
auto support = find_support_vertex(collider_a, collider_b, VectorType{1, 0, 0});
|
||||||
|
|
||||||
@@ -50,11 +45,11 @@ namespace omath::collision
|
|||||||
|
|
||||||
auto direction = -support;
|
auto direction = -support;
|
||||||
|
|
||||||
for (std::size_t iteration = 0; iteration < settings.max_iterations; ++iteration)
|
while (true)
|
||||||
{
|
{
|
||||||
support = find_support_vertex(collider_a, collider_b, direction);
|
support = find_support_vertex(collider_a, collider_b, direction);
|
||||||
|
|
||||||
if (support.dot(direction) <= settings.epsilon)
|
if (support.dot(direction) <= 0.f)
|
||||||
return {false, simplex};
|
return {false, simplex};
|
||||||
|
|
||||||
simplex.push_front(support);
|
simplex.push_front(support);
|
||||||
@@ -62,7 +57,6 @@ namespace omath::collision
|
|||||||
if (simplex.handle(direction))
|
if (simplex.handle(direction))
|
||||||
return {true, simplex};
|
return {true, simplex};
|
||||||
}
|
}
|
||||||
return {false, simplex};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace omath::collision
|
} // namespace omath::collision
|
||||||
@@ -46,26 +46,9 @@ namespace omath::collision
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
const VertexType& find_furthest_vertex(const VectorType& direction) const
|
const VertexType& find_furthest_vertex(const VectorType& direction) const
|
||||||
{
|
{
|
||||||
// The support query arrives in world space, but vertex positions are stored
|
return *std::ranges::max_element(
|
||||||
// in local space. We need argmax_v { world(v) · d }.
|
m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
|
||||||
//
|
{ return first.position.dot(direction) < second.position.dot(direction); });
|
||||||
// world(v) = M·v (ignoring translation, which is constant across vertices)
|
|
||||||
// world(v) · d = v · Mᵀ·d
|
|
||||||
//
|
|
||||||
// So we transform the direction to local space once — O(1) — then compare
|
|
||||||
// raw local positions, which is far cheaper than calling
|
|
||||||
// vertex_position_to_world_space (full 4×4 multiply) for every vertex.
|
|
||||||
//
|
|
||||||
// d_local = upper-left 3×3 of M, transposed, times d_world:
|
|
||||||
// d_local[j] = sum_i M.at(i,j) * d[i] (i.e. column j of M dotted with d)
|
|
||||||
const auto& m = m_mesh.get_to_world_matrix();
|
|
||||||
const VectorType d_local = {
|
|
||||||
m[0, 0] * direction.x + m[1, 0] * direction.y + m[2, 0] * direction.z,
|
|
||||||
m[0, 1] * direction.x + m[1, 1] * direction.y + m[2, 1] * direction.z,
|
|
||||||
m[0, 2] * direction.x + m[1, 2] * direction.y + m[2, 2] * direction.z,
|
|
||||||
};
|
|
||||||
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&d_local](const auto& first, const auto& second)
|
|
||||||
{ return first.position.dot(d_local) < second.position.dot(d_local); });
|
|
||||||
}
|
}
|
||||||
MeshType m_mesh;
|
MeshType m_mesh;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
// Created by Vladislav on 04.01.2026.
|
// Created by Vladislav on 04.01.2026.
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <VMProtectSDK.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <source_location>
|
||||||
|
|
||||||
|
|
||||||
#ifdef OMATH_ENABLE_FORCE_INLINE
|
#ifdef OMATH_ENABLE_FORCE_INLINE
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#define OMATH_FORCE_INLINE __forceinline
|
#define OMATH_FORCE_INLINE __forceinline
|
||||||
@@ -110,16 +114,18 @@ namespace omath
|
|||||||
bool m_is_encrypted{};
|
bool m_is_encrypted{};
|
||||||
value_type m_data{};
|
value_type m_data{};
|
||||||
|
|
||||||
OMATH_FORCE_INLINE constexpr void xor_contained_var_by_key()
|
OMATH_FORCE_INLINE void xor_contained_var_by_key()
|
||||||
{
|
{
|
||||||
|
VMProtectBeginVirtualization(nullptr);
|
||||||
// Safe, keeps const-correctness, and avoids reinterpret_cast issues
|
// Safe, keeps const-correctness, and avoids reinterpret_cast issues
|
||||||
auto bytes = std::as_writable_bytes(std::span<value_type, 1>{&m_data, 1});
|
auto bytes = std::as_writable_bytes(std::span<value_type, 1>{&m_data, 1});
|
||||||
|
|
||||||
for (std::size_t i = 0; i < bytes.size(); ++i)
|
for (std::size_t i = 0; i < bytes.size(); ++i)
|
||||||
{
|
{
|
||||||
const std::uint8_t k = static_cast<std::uint8_t>(key[i % key_size] + (i * key_size));
|
const auto k = static_cast<std::uint8_t>(key[i % key_size] + (i * key_size));
|
||||||
bytes[i] ^= static_cast<std::byte>(k);
|
bytes[i] ^= static_cast<std::byte>(k);
|
||||||
}
|
}
|
||||||
|
VMProtectEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -134,7 +140,7 @@ namespace omath
|
|||||||
return m_is_encrypted;
|
return m_is_encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
OMATH_FORCE_INLINE constexpr void decrypt()
|
OMATH_FORCE_INLINE void decrypt()
|
||||||
{
|
{
|
||||||
if (!m_is_encrypted)
|
if (!m_is_encrypted)
|
||||||
return;
|
return;
|
||||||
@@ -142,7 +148,7 @@ namespace omath
|
|||||||
m_is_encrypted = false;
|
m_is_encrypted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OMATH_FORCE_INLINE constexpr void encrypt()
|
OMATH_FORCE_INLINE void encrypt()
|
||||||
{
|
{
|
||||||
if (m_is_encrypted)
|
if (m_is_encrypted)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by vlad on 3/1/2026.
|
|
||||||
//
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "omath/linear_algebra/mat.hpp"
|
|
||||||
#include "omath/linear_algebra/vector3.hpp"
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
#include <format>
|
|
||||||
|
|
||||||
namespace omath
|
|
||||||
{
|
|
||||||
template<class Type>
|
|
||||||
requires std::is_arithmetic_v<Type>
|
|
||||||
class Quaternion
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using ContainedType = Type;
|
|
||||||
|
|
||||||
Type x = static_cast<Type>(0);
|
|
||||||
Type y = static_cast<Type>(0);
|
|
||||||
Type z = static_cast<Type>(0);
|
|
||||||
Type w = static_cast<Type>(1); // identity quaternion
|
|
||||||
|
|
||||||
constexpr Quaternion() noexcept = default;
|
|
||||||
|
|
||||||
constexpr Quaternion(const Type& x, const Type& y, const Type& z, const Type& w) noexcept
|
|
||||||
: x(x), y(y), z(z), w(w)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Factory: build from a normalized axis and an angle in radians
|
|
||||||
[[nodiscard]]
|
|
||||||
static Quaternion from_axis_angle(const Vector3<Type>& axis, const Type& angle_rad) noexcept
|
|
||||||
{
|
|
||||||
const Type half = angle_rad / static_cast<Type>(2);
|
|
||||||
const Type s = std::sin(half);
|
|
||||||
return {axis.x * s, axis.y * s, axis.z * s, std::cos(half)};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool operator==(const Quaternion& other) const noexcept
|
|
||||||
{
|
|
||||||
return x == other.x && y == other.y && z == other.z && w == other.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool operator!=(const Quaternion& other) const noexcept
|
|
||||||
{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hamilton product: this * other
|
|
||||||
[[nodiscard]] constexpr Quaternion operator*(const Quaternion& other) const noexcept
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
w * other.x + x * other.w + y * other.z - z * other.y,
|
|
||||||
w * other.y - x * other.z + y * other.w + z * other.x,
|
|
||||||
w * other.z + x * other.y - y * other.x + z * other.w,
|
|
||||||
w * other.w - x * other.x - y * other.y - z * other.z,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Quaternion& operator*=(const Quaternion& other) noexcept
|
|
||||||
{
|
|
||||||
return *this = *this * other;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Quaternion operator*(const Type& scalar) const noexcept
|
|
||||||
{
|
|
||||||
return {x * scalar, y * scalar, z * scalar, w * scalar};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Quaternion& operator*=(const Type& scalar) noexcept
|
|
||||||
{
|
|
||||||
x *= scalar;
|
|
||||||
y *= scalar;
|
|
||||||
z *= scalar;
|
|
||||||
w *= scalar;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Quaternion operator+(const Quaternion& other) const noexcept
|
|
||||||
{
|
|
||||||
return {x + other.x, y + other.y, z + other.z, w + other.w};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Quaternion& operator+=(const Quaternion& other) noexcept
|
|
||||||
{
|
|
||||||
x += other.x;
|
|
||||||
y += other.y;
|
|
||||||
z += other.z;
|
|
||||||
w += other.w;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Quaternion operator-() const noexcept
|
|
||||||
{
|
|
||||||
return {-x, -y, -z, -w};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conjugate: negates the vector part (x, y, z)
|
|
||||||
[[nodiscard]] constexpr Quaternion conjugate() const noexcept
|
|
||||||
{
|
|
||||||
return {-x, -y, -z, w};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Type dot(const Quaternion& other) const noexcept
|
|
||||||
{
|
|
||||||
return x * other.x + y * other.y + z * other.z + w * other.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Type length_sqr() const noexcept
|
|
||||||
{
|
|
||||||
return x * x + y * y + z * z + w * w;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
[[nodiscard]] constexpr Type length() const noexcept
|
|
||||||
{
|
|
||||||
return std::sqrt(length_sqr());
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr Quaternion normalized() const noexcept
|
|
||||||
{
|
|
||||||
const Type len = length();
|
|
||||||
return len != static_cast<Type>(0) ? *this * (static_cast<Type>(1) / len) : *this;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
[[nodiscard]] Type length() const noexcept
|
|
||||||
{
|
|
||||||
return std::sqrt(length_sqr());
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] Quaternion normalized() const noexcept
|
|
||||||
{
|
|
||||||
const Type len = length();
|
|
||||||
return len != static_cast<Type>(0) ? *this * (static_cast<Type>(1) / len) : *this;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Inverse: q* / |q|^2 (for unit quaternions inverse == conjugate)
|
|
||||||
[[nodiscard]] constexpr Quaternion inverse() const noexcept
|
|
||||||
{
|
|
||||||
return conjugate() * (static_cast<Type>(1) / length_sqr());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate a 3D vector: v' = q * pure(v) * q^-1
|
|
||||||
// Computed via Rodrigues' formula to avoid full quaternion product overhead
|
|
||||||
[[nodiscard]] constexpr Vector3<Type> rotate(const Vector3<Type>& v) const noexcept
|
|
||||||
{
|
|
||||||
const Vector3<Type> q_vec{x, y, z};
|
|
||||||
const Vector3<Type> cross = q_vec.cross(v);
|
|
||||||
return v + cross * (static_cast<Type>(2) * w) + q_vec.cross(cross) * static_cast<Type>(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3x3 rotation matrix from this (unit) quaternion
|
|
||||||
[[nodiscard]] constexpr Mat<3, 3, Type> to_rotation_matrix3() const noexcept
|
|
||||||
{
|
|
||||||
const Type xx = x * x, yy = y * y, zz = z * z;
|
|
||||||
const Type xy = x * y, xz = x * z, yz = y * z;
|
|
||||||
const Type wx = w * x, wy = w * y, wz = w * z;
|
|
||||||
const Type one = static_cast<Type>(1);
|
|
||||||
const Type two = static_cast<Type>(2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
{one - two * (yy + zz), two * (xy - wz), two * (xz + wy) },
|
|
||||||
{two * (xy + wz), one - two * (xx + zz), two * (yz - wx) },
|
|
||||||
{two * (xz - wy), two * (yz + wx), one - two * (xx + yy)},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4x4 rotation matrix (with homogeneous row/column)
|
|
||||||
[[nodiscard]] constexpr Mat<4, 4, Type> to_rotation_matrix4() const noexcept
|
|
||||||
{
|
|
||||||
const Type xx = x * x, yy = y * y, zz = z * z;
|
|
||||||
const Type xy = x * y, xz = x * z, yz = y * z;
|
|
||||||
const Type wx = w * x, wy = w * y, wz = w * z;
|
|
||||||
const Type one = static_cast<Type>(1);
|
|
||||||
const Type two = static_cast<Type>(2);
|
|
||||||
const Type zero = static_cast<Type>(0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
{one - two * (yy + zz), two * (xy - wz), two * (xz + wy), zero},
|
|
||||||
{two * (xy + wz), one - two * (xx + zz), two * (yz - wx), zero},
|
|
||||||
{two * (xz - wy), two * (yz + wx), one - two * (xx + yy), zero},
|
|
||||||
{zero, zero, zero, one },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::array<Type, 4> as_array() const noexcept
|
|
||||||
{
|
|
||||||
return {x, y, z, w};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace omath
|
|
||||||
|
|
||||||
template<class Type>
|
|
||||||
struct std::formatter<omath::Quaternion<Type>> // NOLINT(*-dcl58-cpp)
|
|
||||||
{
|
|
||||||
[[nodiscard]]
|
|
||||||
static constexpr auto parse(std::format_parse_context& ctx)
|
|
||||||
{
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class FormatContext>
|
|
||||||
[[nodiscard]]
|
|
||||||
static auto format(const omath::Quaternion<Type>& q, FormatContext& ctx)
|
|
||||||
{
|
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
|
|
||||||
return std::format_to(ctx.out(), "[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
|
|
||||||
return std::format_to(ctx.out(), L"[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
|
|
||||||
return std::format_to(ctx.out(), u8"[{}, {}, {}, {}]", q.x, q.y, q.z, q.w);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -17,9 +17,6 @@
|
|||||||
// Matrix classes
|
// Matrix classes
|
||||||
#include "omath/linear_algebra/mat.hpp"
|
#include "omath/linear_algebra/mat.hpp"
|
||||||
|
|
||||||
// Quaternion
|
|
||||||
#include "omath/linear_algebra/quaternion.hpp"
|
|
||||||
|
|
||||||
// Color functionality
|
// Color functionality
|
||||||
#include "omath/utility/color.hpp"
|
#include "omath/utility/color.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -16,28 +16,19 @@ namespace omath
|
|||||||
float value{};
|
float value{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Color final
|
class Color final : public Vector4<float>
|
||||||
{
|
{
|
||||||
Vector4<float> m_value;
|
|
||||||
public:
|
public:
|
||||||
constexpr const Vector4<float>& value() const
|
constexpr Color(const float r, const float g, const float b, const float a) noexcept: Vector4(r, g, b, a)
|
||||||
{
|
{
|
||||||
return m_value;
|
clamp(0.f, 1.f);
|
||||||
}
|
|
||||||
constexpr Color(const float r, const float g, const float b, const float a) noexcept: m_value(r, g, b, a)
|
|
||||||
{
|
|
||||||
m_value.clamp(0.f, 1.f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr explicit Color(const Vector4<float>& value) : m_value(value)
|
|
||||||
{
|
|
||||||
m_value.clamp(0.f, 1.f);
|
|
||||||
}
|
|
||||||
constexpr explicit Color() noexcept = default;
|
constexpr explicit Color() noexcept = default;
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept
|
constexpr static Color from_rgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) noexcept
|
||||||
{
|
{
|
||||||
return Color(Vector4<float>(r, g, b, a) / 255.f);
|
return Color{Vector4(r, g, b, a) / 255.f};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -91,9 +82,9 @@ namespace omath
|
|||||||
{
|
{
|
||||||
Hsv hsv_data;
|
Hsv hsv_data;
|
||||||
|
|
||||||
const float& red = m_value.x;
|
const float& red = x;
|
||||||
const float& green = m_value.y;
|
const float& green = y;
|
||||||
const float& blue = m_value.z;
|
const float& blue = z;
|
||||||
|
|
||||||
const float max = std::max({red, green, blue});
|
const float max = std::max({red, green, blue});
|
||||||
const float min = std::min({red, green, blue});
|
const float min = std::min({red, green, blue});
|
||||||
@@ -118,6 +109,11 @@ namespace omath
|
|||||||
|
|
||||||
return hsv_data;
|
return hsv_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr explicit Color(const Vector4& vec) noexcept: Vector4(vec)
|
||||||
|
{
|
||||||
|
clamp(0.f, 1.f);
|
||||||
|
}
|
||||||
constexpr void set_hue(const float hue) noexcept
|
constexpr void set_hue(const float hue) noexcept
|
||||||
{
|
{
|
||||||
auto hsv = to_hsv();
|
auto hsv = to_hsv();
|
||||||
@@ -145,7 +141,7 @@ namespace omath
|
|||||||
constexpr Color blend(const Color& other, float ratio) const noexcept
|
constexpr Color blend(const Color& other, float ratio) const noexcept
|
||||||
{
|
{
|
||||||
ratio = std::clamp(ratio, 0.f, 1.f);
|
ratio = std::clamp(ratio, 0.f, 1.f);
|
||||||
return Color(this->m_value * (1.f - ratio) + other.m_value * ratio);
|
return Color(*this * (1.f - ratio) + other * ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static constexpr Color red()
|
[[nodiscard]] static constexpr Color red()
|
||||||
@@ -164,26 +160,16 @@ namespace omath
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
ImColor to_im_color() const noexcept
|
ImColor to_im_color() const noexcept
|
||||||
{
|
{
|
||||||
return {m_value.to_im_vec4()};
|
return {to_im_vec4()};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
[[nodiscard]] std::string to_string() const noexcept
|
[[nodiscard]] std::string to_string() const noexcept
|
||||||
{
|
{
|
||||||
return std::format("[r:{}, g:{}, b:{}, a:{}]",
|
return std::format("[r:{}, g:{}, b:{}, a:{}]",
|
||||||
static_cast<int>(m_value.x * 255.f),
|
static_cast<int>(x * 255.f),
|
||||||
static_cast<int>(m_value.y * 255.f),
|
static_cast<int>(y * 255.f),
|
||||||
static_cast<int>(m_value.z * 255.f),
|
static_cast<int>(z * 255.f),
|
||||||
static_cast<int>(m_value.w * 255.f));
|
static_cast<int>(w * 255.f));
|
||||||
}
|
|
||||||
[[nodiscard]] std::string to_rgbf_string() const noexcept
|
|
||||||
{
|
|
||||||
return std::format("[r:{}, g:{}, b:{}, a:{}]",
|
|
||||||
m_value.x, m_value.y, m_value.z, m_value.w);
|
|
||||||
}
|
|
||||||
[[nodiscard]] std::string to_hsv_string() const noexcept
|
|
||||||
{
|
|
||||||
const auto [hue, saturation, value] = to_hsv();
|
|
||||||
return std::format("[h:{}, s:{}, v:{}]", hue, saturation, value);
|
|
||||||
}
|
}
|
||||||
[[nodiscard]] std::wstring to_wstring() const noexcept
|
[[nodiscard]] std::wstring to_wstring() const noexcept
|
||||||
{
|
{
|
||||||
@@ -202,55 +188,23 @@ namespace omath
|
|||||||
template<>
|
template<>
|
||||||
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
|
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
|
||||||
{
|
{
|
||||||
enum class ColorFormat { rgb, rgbf, hsv };
|
[[nodiscard]]
|
||||||
ColorFormat color_format = ColorFormat::rgb;
|
static constexpr auto parse(const std::format_parse_context& ctx)
|
||||||
|
|
||||||
constexpr auto parse(std::format_parse_context& ctx)
|
|
||||||
{
|
{
|
||||||
const auto it = ctx.begin();
|
return ctx.begin();
|
||||||
const auto end = ctx.end();
|
|
||||||
|
|
||||||
if (it == end || *it == '}')
|
|
||||||
return it;
|
|
||||||
|
|
||||||
const std::string_view spec(it, end);
|
|
||||||
|
|
||||||
if (spec.starts_with("rgbf"))
|
|
||||||
{
|
|
||||||
color_format = ColorFormat::rgbf;
|
|
||||||
return it + 4;
|
|
||||||
}
|
|
||||||
if (spec.starts_with("rgb"))
|
|
||||||
{
|
|
||||||
color_format = ColorFormat::rgb;
|
|
||||||
return it + 3;
|
|
||||||
}
|
|
||||||
if (spec.starts_with("hsv"))
|
|
||||||
{
|
|
||||||
color_format = ColorFormat::hsv;
|
|
||||||
return it + 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::format_error("Invalid format specifier for omath::Color. Use rgb, rgbf, or hsv.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class FormatContext>
|
template<class FormatContext>
|
||||||
auto format(const omath::Color& col, FormatContext& ctx) const
|
[[nodiscard]]
|
||||||
|
static auto format(const omath::Color& col, FormatContext& ctx)
|
||||||
{
|
{
|
||||||
std::string str;
|
|
||||||
switch (color_format)
|
|
||||||
{
|
|
||||||
case ColorFormat::rgb: str = col.to_string(); break;
|
|
||||||
case ColorFormat::rgbf: str = col.to_rgbf_string(); break;
|
|
||||||
case ColorFormat::hsv: str = col.to_hsv_string(); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
|
if constexpr (std::is_same_v<typename FormatContext::char_type, char>)
|
||||||
return std::format_to(ctx.out(), "{}", str);
|
return std::format_to(ctx.out(), "{}", col.to_string());
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
|
if constexpr (std::is_same_v<typename FormatContext::char_type, wchar_t>)
|
||||||
return std::format_to(ctx.out(), L"{}", std::wstring(str.cbegin(), str.cend()));
|
return std::format_to(ctx.out(), L"{}", col.to_wstring());
|
||||||
|
|
||||||
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
|
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
|
||||||
return std::format_to(ctx.out(), u8"{}", std::u8string(str.cbegin(), str.cend()));
|
return std::format_to(ctx.out(), u8"{}", col.to_u8string());
|
||||||
|
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <VMProtectSDK.h>
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -26,38 +26,38 @@ protected:
|
|||||||
TEST_F(UnitTestColorGrouped, Constructor_Float)
|
TEST_F(UnitTestColorGrouped, Constructor_Float)
|
||||||
{
|
{
|
||||||
constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f);
|
constexpr Color color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().x, 0.5f);
|
EXPECT_FLOAT_EQ(color.x, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(color.value().y, 0.5f);
|
EXPECT_FLOAT_EQ(color.y, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(color.value().z, 0.5f);
|
EXPECT_FLOAT_EQ(color.z, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(color.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, Constructor_Vector4)
|
TEST_F(UnitTestColorGrouped, Constructor_Vector4)
|
||||||
{
|
{
|
||||||
constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f);
|
constexpr omath::Vector4 vec(0.2f, 0.4f, 0.6f, 0.8f);
|
||||||
constexpr Color color(vec);
|
constexpr Color color(vec);
|
||||||
EXPECT_FLOAT_EQ(color.value().x, 0.2f);
|
EXPECT_FLOAT_EQ(color.x, 0.2f);
|
||||||
EXPECT_FLOAT_EQ(color.value().y, 0.4f);
|
EXPECT_FLOAT_EQ(color.y, 0.4f);
|
||||||
EXPECT_FLOAT_EQ(color.value().z, 0.6f);
|
EXPECT_FLOAT_EQ(color.z, 0.6f);
|
||||||
EXPECT_FLOAT_EQ(color.value().w, 0.8f);
|
EXPECT_FLOAT_EQ(color.w, 0.8f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, FromRGBA)
|
TEST_F(UnitTestColorGrouped, FromRGBA)
|
||||||
{
|
{
|
||||||
constexpr Color color = Color::from_rgba(128, 64, 32, 255);
|
constexpr Color color = Color::from_rgba(128, 64, 32, 255);
|
||||||
EXPECT_FLOAT_EQ(color.value().x, 128.0f / 255.0f);
|
EXPECT_FLOAT_EQ(color.x, 128.0f / 255.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().y, 64.0f / 255.0f);
|
EXPECT_FLOAT_EQ(color.y, 64.0f / 255.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().z, 32.0f / 255.0f);
|
EXPECT_FLOAT_EQ(color.z, 32.0f / 255.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, FromHSV)
|
TEST_F(UnitTestColorGrouped, FromHSV)
|
||||||
{
|
{
|
||||||
constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV
|
constexpr Color color = Color::from_hsv(0.0f, 1.0f, 1.0f); // Red in HSV
|
||||||
EXPECT_FLOAT_EQ(color.value().x, 1.0f);
|
EXPECT_FLOAT_EQ(color.x, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().y, 0.0f);
|
EXPECT_FLOAT_EQ(color.y, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(color.z, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(color.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(color.w, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, ToHSV)
|
TEST_F(UnitTestColorGrouped, ToHSV)
|
||||||
@@ -71,10 +71,10 @@ TEST_F(UnitTestColorGrouped, ToHSV)
|
|||||||
TEST_F(UnitTestColorGrouped, Blend)
|
TEST_F(UnitTestColorGrouped, Blend)
|
||||||
{
|
{
|
||||||
const Color blended = color1.blend(color2, 0.5f);
|
const Color blended = color1.blend(color2, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().x, 0.5f);
|
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().y, 0.5f);
|
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(blended.w, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, PredefinedColors)
|
TEST_F(UnitTestColorGrouped, PredefinedColors)
|
||||||
@@ -83,20 +83,20 @@ TEST_F(UnitTestColorGrouped, PredefinedColors)
|
|||||||
constexpr Color green = Color::green();
|
constexpr Color green = Color::green();
|
||||||
constexpr Color blue = Color::blue();
|
constexpr Color blue = Color::blue();
|
||||||
|
|
||||||
EXPECT_FLOAT_EQ(red.value().x, 1.0f);
|
EXPECT_FLOAT_EQ(red.x, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(red.value().y, 0.0f);
|
EXPECT_FLOAT_EQ(red.y, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(red.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(red.z, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(red.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(red.w, 1.0f);
|
||||||
|
|
||||||
EXPECT_FLOAT_EQ(green.value().x, 0.0f);
|
EXPECT_FLOAT_EQ(green.x, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(green.value().y, 1.0f);
|
EXPECT_FLOAT_EQ(green.y, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(green.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(green.z, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(green.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(green.w, 1.0f);
|
||||||
|
|
||||||
EXPECT_FLOAT_EQ(blue.value().x, 0.0f);
|
EXPECT_FLOAT_EQ(blue.x, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(blue.value().y, 0.0f);
|
EXPECT_FLOAT_EQ(blue.y, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(blue.value().z, 1.0f);
|
EXPECT_FLOAT_EQ(blue.z, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(blue.value().w, 1.0f);
|
EXPECT_FLOAT_EQ(blue.w, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(UnitTestColorGrouped, BlendVector3)
|
TEST_F(UnitTestColorGrouped, BlendVector3)
|
||||||
@@ -104,9 +104,9 @@ TEST_F(UnitTestColorGrouped, BlendVector3)
|
|||||||
constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red
|
constexpr Color v1(1.0f, 0.0f, 0.0f, 1.f); // Red
|
||||||
constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green
|
constexpr Color v2(0.0f, 1.0f, 0.0f, 1.f); // Green
|
||||||
constexpr Color blended = v1.blend(v2, 0.5f);
|
constexpr Color blended = v1.blend(v2, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().x, 0.5f);
|
EXPECT_FLOAT_EQ(blended.x, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().y, 0.5f);
|
EXPECT_FLOAT_EQ(blended.y, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(blended.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(blended.z, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// From unit_test_color_extra.cpp
|
// From unit_test_color_extra.cpp
|
||||||
@@ -148,37 +148,37 @@ TEST(UnitTestColorGrouped_Extra, BlendEdgeCases)
|
|||||||
constexpr Color a = Color::red();
|
constexpr Color a = Color::red();
|
||||||
constexpr Color b = Color::blue();
|
constexpr Color b = Color::blue();
|
||||||
constexpr auto r0 = a.blend(b, 0.f);
|
constexpr auto r0 = a.blend(b, 0.f);
|
||||||
EXPECT_FLOAT_EQ(r0.value().x, a.value().x);
|
EXPECT_FLOAT_EQ(r0.x, a.x);
|
||||||
constexpr auto r1 = a.blend(b, 1.f);
|
constexpr auto r1 = a.blend(b, 1.f);
|
||||||
EXPECT_FLOAT_EQ(r1.value().x, b.value().x);
|
EXPECT_FLOAT_EQ(r1.x, b.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
// From unit_test_color_more.cpp
|
// From unit_test_color_more.cpp
|
||||||
TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
|
TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
|
||||||
{
|
{
|
||||||
constexpr Color c;
|
constexpr Color c;
|
||||||
EXPECT_FLOAT_EQ(c.value().x, 0.0f);
|
EXPECT_FLOAT_EQ(c.x, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().y, 0.0f);
|
EXPECT_FLOAT_EQ(c.y, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().z, 0.0f);
|
EXPECT_FLOAT_EQ(c.z, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().w, 0.0f);
|
EXPECT_FLOAT_EQ(c.w, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
|
TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
|
||||||
{
|
{
|
||||||
constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f);
|
constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().x, 1.0f);
|
EXPECT_FLOAT_EQ(c.x, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().y, 0.0f);
|
EXPECT_FLOAT_EQ(c.y, 0.0f);
|
||||||
EXPECT_FLOAT_EQ(c.value().z, 0.5f);
|
EXPECT_FLOAT_EQ(c.z, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(c.value().w, 2.0f);
|
EXPECT_FLOAT_EQ(c.w, 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
|
TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
|
||||||
{
|
{
|
||||||
constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u);
|
constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u);
|
||||||
EXPECT_NEAR(c.value().x, 25.0f/255.0f, 1e-6f);
|
EXPECT_NEAR(c.x, 25.0f/255.0f, 1e-6f);
|
||||||
EXPECT_NEAR(c.value().y, 128.0f/255.0f, 1e-6f);
|
EXPECT_NEAR(c.y, 128.0f/255.0f, 1e-6f);
|
||||||
EXPECT_NEAR(c.value().z, 230.0f/255.0f, 1e-6f);
|
EXPECT_NEAR(c.z, 230.0f/255.0f, 1e-6f);
|
||||||
EXPECT_NEAR(c.value().w, 64.0f/255.0f, 1e-6f);
|
EXPECT_NEAR(c.w, 64.0f/255.0f, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
|
TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
|
||||||
@@ -186,10 +186,10 @@ TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
|
|||||||
constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f);
|
constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f);
|
constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f);
|
||||||
constexpr Color mid = c0.blend(c1, 0.5f);
|
constexpr Color mid = c0.blend(c1, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(mid.value().x, 0.5f);
|
EXPECT_FLOAT_EQ(mid.x, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(mid.value().y, 0.5f);
|
EXPECT_FLOAT_EQ(mid.y, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(mid.value().z, 0.5f);
|
EXPECT_FLOAT_EQ(mid.z, 0.5f);
|
||||||
EXPECT_FLOAT_EQ(mid.value().w, 0.5f);
|
EXPECT_FLOAT_EQ(mid.w, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More, HsvRoundTrip)
|
TEST(UnitTestColorGrouped_More, HsvRoundTrip)
|
||||||
@@ -197,9 +197,9 @@ TEST(UnitTestColorGrouped_More, HsvRoundTrip)
|
|||||||
constexpr Color red = Color::red();
|
constexpr Color red = Color::red();
|
||||||
const auto hsv = red.to_hsv();
|
const auto hsv = red.to_hsv();
|
||||||
const Color back = Color::from_hsv(hsv);
|
const Color back = Color::from_hsv(hsv);
|
||||||
EXPECT_NEAR(back.value().x, 1.0f, 1e-6f);
|
EXPECT_NEAR(back.x, 1.0f, 1e-6f);
|
||||||
EXPECT_NEAR(back.value().y, 0.0f, 1e-6f);
|
EXPECT_NEAR(back.y, 0.0f, 1e-6f);
|
||||||
EXPECT_NEAR(back.value().z, 0.0f, 1e-6f);
|
EXPECT_NEAR(back.z, 0.0f, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
|
TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
|
||||||
@@ -230,18 +230,18 @@ TEST(UnitTestColorGrouped_More2, FromHsvCases)
|
|||||||
auto check_hue = [&](float h) {
|
auto check_hue = [&](float h) {
|
||||||
SCOPED_TRACE(::testing::Message() << "h=" << h);
|
SCOPED_TRACE(::testing::Message() << "h=" << h);
|
||||||
Color c = Color::from_hsv(h, 1.f, 1.f);
|
Color c = Color::from_hsv(h, 1.f, 1.f);
|
||||||
EXPECT_TRUE(std::isfinite(c.value().x));
|
EXPECT_TRUE(std::isfinite(c.x));
|
||||||
EXPECT_TRUE(std::isfinite(c.value().y));
|
EXPECT_TRUE(std::isfinite(c.y));
|
||||||
EXPECT_TRUE(std::isfinite(c.value().z));
|
EXPECT_TRUE(std::isfinite(c.z));
|
||||||
EXPECT_GE(c.value().x, -eps);
|
EXPECT_GE(c.x, -eps);
|
||||||
EXPECT_LE(c.value().x, 1.f + eps);
|
EXPECT_LE(c.x, 1.f + eps);
|
||||||
EXPECT_GE(c.value().y, -eps);
|
EXPECT_GE(c.y, -eps);
|
||||||
EXPECT_LE(c.value().y, 1.f + eps);
|
EXPECT_LE(c.y, 1.f + eps);
|
||||||
EXPECT_GE(c.value().z, -eps);
|
EXPECT_GE(c.z, -eps);
|
||||||
EXPECT_LE(c.value().z, 1.f + eps);
|
EXPECT_LE(c.z, 1.f + eps);
|
||||||
|
|
||||||
float mx = std::max({c.value().x, c.value().y, c.value().z});
|
float mx = std::max({c.x, c.y, c.z});
|
||||||
float mn = std::min({c.value().x, c.value().y, c.value().z});
|
float mn = std::min({c.x, c.y, c.z});
|
||||||
EXPECT_GE(mx, 0.999f);
|
EXPECT_GE(mx, 0.999f);
|
||||||
EXPECT_LE(mn, 1e-3f + 1e-4f);
|
EXPECT_LE(mn, 1e-3f + 1e-4f);
|
||||||
};
|
};
|
||||||
@@ -261,13 +261,13 @@ TEST(UnitTestColorGrouped_More2, ToHsvAndSetters)
|
|||||||
EXPECT_NEAR(hsv.value, 0.6f, 1e-6f);
|
EXPECT_NEAR(hsv.value, 0.6f, 1e-6f);
|
||||||
|
|
||||||
c.set_hue(0.0f);
|
c.set_hue(0.0f);
|
||||||
EXPECT_TRUE(std::isfinite(c.value().x));
|
EXPECT_TRUE(std::isfinite(c.x));
|
||||||
|
|
||||||
c.set_saturation(0.0f);
|
c.set_saturation(0.0f);
|
||||||
EXPECT_TRUE(std::isfinite(c.value().y));
|
EXPECT_TRUE(std::isfinite(c.y));
|
||||||
|
|
||||||
c.set_value(0.5f);
|
c.set_value(0.5f);
|
||||||
EXPECT_TRUE(std::isfinite(c.value().z));
|
EXPECT_TRUE(std::isfinite(c.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
|
TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
|
||||||
@@ -275,14 +275,14 @@ TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
|
|||||||
constexpr Color a = Color::red();
|
constexpr Color a = Color::red();
|
||||||
constexpr Color b = Color::blue();
|
constexpr Color b = Color::blue();
|
||||||
constexpr auto mid = a.blend(b, 0.5f);
|
constexpr auto mid = a.blend(b, 0.5f);
|
||||||
EXPECT_GT(mid.value().x, 0.f);
|
EXPECT_GT(mid.x, 0.f);
|
||||||
EXPECT_GT(mid.value().z, 0.f);
|
EXPECT_GT(mid.z, 0.f);
|
||||||
|
|
||||||
constexpr auto all_a = a.blend(b, -1.f);
|
constexpr auto all_a = a.blend(b, -1.f);
|
||||||
EXPECT_NEAR(all_a.value().x, a.value().x, 1e-6f);
|
EXPECT_NEAR(all_a.x, a.x, 1e-6f);
|
||||||
|
|
||||||
constexpr auto all_b = a.blend(b, 2.f);
|
constexpr auto all_b = a.blend(b, 2.f);
|
||||||
EXPECT_NEAR(all_b.value().z, b.value().z, 1e-6f);
|
EXPECT_NEAR(all_b.z, b.z, 1e-6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
|
TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
|
||||||
@@ -291,35 +291,3 @@ TEST(UnitTestColorGrouped_More2, FormatterUsesToString)
|
|||||||
const auto formatted = std::format("{}", c);
|
const auto formatted = std::format("{}", c);
|
||||||
EXPECT_NE(formatted.find("r:10"), std::string::npos);
|
EXPECT_NE(formatted.find("r:10"), std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More2, FormatterRgb)
|
|
||||||
{
|
|
||||||
constexpr Color c = Color::from_rgba(255, 128, 0, 64);
|
|
||||||
const auto s = std::format("{:rgb}", c);
|
|
||||||
EXPECT_NE(s.find("r:255"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("g:128"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("b:0"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("a:64"), std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More2, FormatterRgbf)
|
|
||||||
{
|
|
||||||
constexpr Color c(0.5f, 0.25f, 1.0f, 0.75f);
|
|
||||||
const auto s = std::format("{:rgbf}", c);
|
|
||||||
EXPECT_NE(s.find("r:"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("g:"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("b:"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("a:"), std::string::npos);
|
|
||||||
// Values should be in [0,1] float range, not 0-255
|
|
||||||
EXPECT_EQ(s.find("r:127"), std::string::npos);
|
|
||||||
EXPECT_EQ(s.find("r:255"), std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UnitTestColorGrouped_More2, FormatterHsv)
|
|
||||||
{
|
|
||||||
const Color c = Color::red();
|
|
||||||
const auto s = std::format("{:hsv}", c);
|
|
||||||
EXPECT_NE(s.find("h:"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("s:"), std::string::npos);
|
|
||||||
EXPECT_NE(s.find("v:"), std::string::npos);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,402 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by vlad on 3/1/2026.
|
|
||||||
//
|
|
||||||
#include <omath/linear_algebra/quaternion.hpp>
|
|
||||||
#include <cmath>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <numbers>
|
|
||||||
|
|
||||||
using namespace omath;
|
|
||||||
|
|
||||||
static constexpr float kEps = 1e-5f;
|
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
static void expect_quat_near(const Quaternion<float>& a, const Quaternion<float>& b, float eps = kEps)
|
|
||||||
{
|
|
||||||
EXPECT_NEAR(a.x, b.x, eps);
|
|
||||||
EXPECT_NEAR(a.y, b.y, eps);
|
|
||||||
EXPECT_NEAR(a.z, b.z, eps);
|
|
||||||
EXPECT_NEAR(a.w, b.w, eps);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void expect_vec3_near(const Vector3<float>& a, const Vector3<float>& b, float eps = kEps)
|
|
||||||
{
|
|
||||||
EXPECT_NEAR(a.x, b.x, eps);
|
|
||||||
EXPECT_NEAR(a.y, b.y, eps);
|
|
||||||
EXPECT_NEAR(a.z, b.z, eps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Constructors ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, DefaultConstructorIsIdentity)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q;
|
|
||||||
EXPECT_FLOAT_EQ(q.x, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.y, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.z, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.w, 1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, ValueConstructor)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
EXPECT_FLOAT_EQ(q.x, 1.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.y, 2.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.z, 3.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.w, 4.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, DoubleInstantiation)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<double> q{0.0, 0.0, 0.0, 1.0};
|
|
||||||
EXPECT_DOUBLE_EQ(q.w, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Equality ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, EqualityOperators)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> a{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr Quaternion<float> b{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr Quaternion<float> c{1.f, 2.f, 3.f, 5.f};
|
|
||||||
|
|
||||||
EXPECT_TRUE(a == b);
|
|
||||||
EXPECT_FALSE(a == c);
|
|
||||||
EXPECT_FALSE(a != b);
|
|
||||||
EXPECT_TRUE(a != c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Arithmetic ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, ScalarMultiply)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr auto r = q * 2.f;
|
|
||||||
EXPECT_FLOAT_EQ(r.x, 2.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.y, 4.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.z, 6.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.w, 8.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, ScalarMultiplyAssign)
|
|
||||||
{
|
|
||||||
Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
q *= 3.f;
|
|
||||||
EXPECT_FLOAT_EQ(q.x, 3.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.y, 6.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.z, 9.f);
|
|
||||||
EXPECT_FLOAT_EQ(q.w, 12.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, Addition)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> a{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr Quaternion<float> b{4.f, 3.f, 2.f, 1.f};
|
|
||||||
constexpr auto r = a + b;
|
|
||||||
EXPECT_FLOAT_EQ(r.x, 5.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.y, 5.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.z, 5.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.w, 5.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, AdditionAssign)
|
|
||||||
{
|
|
||||||
Quaternion<float> a{1.f, 0.f, 0.f, 0.f};
|
|
||||||
const Quaternion<float> b{0.f, 1.f, 0.f, 0.f};
|
|
||||||
a += b;
|
|
||||||
EXPECT_FLOAT_EQ(a.x, 1.f);
|
|
||||||
EXPECT_FLOAT_EQ(a.y, 1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, UnaryNegation)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, -2.f, 3.f, -4.f};
|
|
||||||
constexpr auto r = -q;
|
|
||||||
EXPECT_FLOAT_EQ(r.x, -1.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.y, 2.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.z, -3.f);
|
|
||||||
EXPECT_FLOAT_EQ(r.w, 4.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Hamilton product ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, MultiplyByIdentityIsNoop)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> identity;
|
|
||||||
constexpr Quaternion<float> q{0.5f, 0.5f, 0.5f, 0.5f};
|
|
||||||
expect_quat_near(q * identity, q);
|
|
||||||
expect_quat_near(identity * q, q);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, MultiplyAssign)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> identity;
|
|
||||||
Quaternion<float> q{0.5f, 0.5f, 0.5f, 0.5f};
|
|
||||||
q *= identity;
|
|
||||||
expect_quat_near(q, {0.5f, 0.5f, 0.5f, 0.5f});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, MultiplyKnownResult)
|
|
||||||
{
|
|
||||||
// i * j = k → (1,0,0,0) * (0,1,0,0) = (0,0,1,0)
|
|
||||||
constexpr Quaternion<float> i{1.f, 0.f, 0.f, 0.f};
|
|
||||||
constexpr Quaternion<float> j{0.f, 1.f, 0.f, 0.f};
|
|
||||||
constexpr auto k = i * j;
|
|
||||||
EXPECT_FLOAT_EQ(k.x, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(k.y, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(k.z, 1.f);
|
|
||||||
EXPECT_FLOAT_EQ(k.w, 0.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, MultiplyByInverseGivesIdentity)
|
|
||||||
{
|
|
||||||
const Quaternion<float> q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f},
|
|
||||||
std::numbers::pi_v<float> / 3.f);
|
|
||||||
const auto result = q * q.inverse();
|
|
||||||
expect_quat_near(result, Quaternion<float>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Conjugate ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, Conjugate)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr auto c = q.conjugate();
|
|
||||||
EXPECT_FLOAT_EQ(c.x, -1.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.y, -2.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.z, -3.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.w, 4.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, ConjugateOfIdentityIsIdentity)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> id;
|
|
||||||
constexpr auto c = id.conjugate();
|
|
||||||
EXPECT_FLOAT_EQ(c.x, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.y, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.z, 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(c.w, 1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Dot / length ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, Dot)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> a{1.f, 0.f, 0.f, 0.f};
|
|
||||||
constexpr Quaternion<float> b{0.f, 1.f, 0.f, 0.f};
|
|
||||||
EXPECT_FLOAT_EQ(a.dot(b), 0.f);
|
|
||||||
EXPECT_FLOAT_EQ(a.dot(a), 1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, LengthSqrIdentity)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> id;
|
|
||||||
EXPECT_FLOAT_EQ(id.length_sqr(), 1.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, LengthSqrGeneral)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
EXPECT_FLOAT_EQ(q.length_sqr(), 30.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, LengthIdentity)
|
|
||||||
{
|
|
||||||
const Quaternion<float> id;
|
|
||||||
EXPECT_NEAR(id.length(), 1.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, Normalized)
|
|
||||||
{
|
|
||||||
const Quaternion<float> q{1.f, 1.f, 1.f, 1.f};
|
|
||||||
const auto n = q.normalized();
|
|
||||||
EXPECT_NEAR(n.length(), 1.f, kEps);
|
|
||||||
EXPECT_NEAR(n.x, 0.5f, kEps);
|
|
||||||
EXPECT_NEAR(n.y, 0.5f, kEps);
|
|
||||||
EXPECT_NEAR(n.z, 0.5f, kEps);
|
|
||||||
EXPECT_NEAR(n.w, 0.5f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, NormalizedOfZeroLengthReturnsSelf)
|
|
||||||
{
|
|
||||||
// length_sqr = 0 would be UB, but zero-vector part + zero w is degenerate;
|
|
||||||
// we just verify the guard branch (divides by zero) doesn't crash by
|
|
||||||
// keeping length > 0 via the default constructor path.
|
|
||||||
const Quaternion<float> unit;
|
|
||||||
const auto n = unit.normalized();
|
|
||||||
expect_quat_near(n, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Inverse ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, InverseOfUnitIsConjugate)
|
|
||||||
{
|
|
||||||
const Quaternion<float> q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f},
|
|
||||||
std::numbers::pi_v<float> / 4.f);
|
|
||||||
const auto inv = q.inverse();
|
|
||||||
const auto conj = q.conjugate();
|
|
||||||
expect_quat_near(inv, conj);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── from_axis_angle ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, FromAxisAngleZeroAngleIsIdentity)
|
|
||||||
{
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, 0.f);
|
|
||||||
EXPECT_NEAR(q.x, 0.f, kEps);
|
|
||||||
EXPECT_NEAR(q.y, 0.f, kEps);
|
|
||||||
EXPECT_NEAR(q.z, 0.f, kEps);
|
|
||||||
EXPECT_NEAR(q.w, 1.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, FromAxisAngle90DegZ)
|
|
||||||
{
|
|
||||||
const float half_pi = std::numbers::pi_v<float> / 2.f;
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, half_pi);
|
|
||||||
const float s = std::sin(half_pi / 2.f);
|
|
||||||
const float c = std::cos(half_pi / 2.f);
|
|
||||||
EXPECT_NEAR(q.x, 0.f, kEps);
|
|
||||||
EXPECT_NEAR(q.y, 0.f, kEps);
|
|
||||||
EXPECT_NEAR(q.z, s, kEps);
|
|
||||||
EXPECT_NEAR(q.w, c, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── rotate ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, RotateByIdentityIsNoop)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> id;
|
|
||||||
constexpr Vector3<float> v{1.f, 2.f, 3.f};
|
|
||||||
const auto r = id.rotate(v);
|
|
||||||
expect_vec3_near(r, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, Rotate90DegAroundZ)
|
|
||||||
{
|
|
||||||
// Rotating (1,0,0) by 90° around Z should give (0,1,0)
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, std::numbers::pi_v<float> / 2.f);
|
|
||||||
const auto r = q.rotate({1.f, 0.f, 0.f});
|
|
||||||
expect_vec3_near(r, {0.f, 1.f, 0.f});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, Rotate180DegAroundY)
|
|
||||||
{
|
|
||||||
// Rotating (1,0,0) by 180° around Y should give (-1,0,0)
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({0.f, 1.f, 0.f}, std::numbers::pi_v<float>);
|
|
||||||
const auto r = q.rotate({1.f, 0.f, 0.f});
|
|
||||||
expect_vec3_near(r, {-1.f, 0.f, 0.f});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, Rotate90DegAroundX)
|
|
||||||
{
|
|
||||||
// Rotating (0,1,0) by 90° around X should give (0,0,1)
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, std::numbers::pi_v<float> / 2.f);
|
|
||||||
const auto r = q.rotate({0.f, 1.f, 0.f});
|
|
||||||
expect_vec3_near(r, {0.f, 0.f, 1.f});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── to_rotation_matrix3 ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix3FromIdentityIsIdentityMatrix)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> id;
|
|
||||||
constexpr auto m = id.to_rotation_matrix3();
|
|
||||||
for (size_t i = 0; i < 3; ++i)
|
|
||||||
for (size_t j = 0; j < 3; ++j)
|
|
||||||
EXPECT_NEAR(m.at(i, j), i == j ? 1.f : 0.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix3From90DegZ)
|
|
||||||
{
|
|
||||||
// Expected: | 0 -1 0 |
|
|
||||||
// | 1 0 0 |
|
|
||||||
// | 0 0 1 |
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({0.f, 0.f, 1.f}, std::numbers::pi_v<float> / 2.f);
|
|
||||||
const auto m = q.to_rotation_matrix3();
|
|
||||||
EXPECT_NEAR(m.at(0, 0), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(0, 1), -1.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(0, 2), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(1, 0), 1.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(1, 1), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(1, 2), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(2, 0), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(2, 1), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(2, 2), 1.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix3ConsistentWithRotate)
|
|
||||||
{
|
|
||||||
// Matrix-vector multiply must agree with the rotate() method
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({1.f, 1.f, 0.f}, std::numbers::pi_v<float> / 3.f);
|
|
||||||
const Vector3<float> v{2.f, -1.f, 0.5f};
|
|
||||||
|
|
||||||
const auto rotated = q.rotate(v);
|
|
||||||
const auto m = q.to_rotation_matrix3();
|
|
||||||
|
|
||||||
// manual mat-vec multiply (row-major)
|
|
||||||
const float rx = m.at(0, 0) * v.x + m.at(0, 1) * v.y + m.at(0, 2) * v.z;
|
|
||||||
const float ry = m.at(1, 0) * v.x + m.at(1, 1) * v.y + m.at(1, 2) * v.z;
|
|
||||||
const float rz = m.at(2, 0) * v.x + m.at(2, 1) * v.y + m.at(2, 2) * v.z;
|
|
||||||
|
|
||||||
EXPECT_NEAR(rotated.x, rx, kEps);
|
|
||||||
EXPECT_NEAR(rotated.y, ry, kEps);
|
|
||||||
EXPECT_NEAR(rotated.z, rz, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── to_rotation_matrix4 ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix4FromIdentityIsIdentityMatrix)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> id;
|
|
||||||
constexpr auto m = id.to_rotation_matrix4();
|
|
||||||
for (size_t i = 0; i < 4; ++i)
|
|
||||||
for (size_t j = 0; j < 4; ++j)
|
|
||||||
EXPECT_NEAR(m.at(i, j), i == j ? 1.f : 0.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix4HomogeneousRowAndColumn)
|
|
||||||
{
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({1.f, 0.f, 0.f}, std::numbers::pi_v<float> / 5.f);
|
|
||||||
const auto m = q.to_rotation_matrix4();
|
|
||||||
|
|
||||||
// Last row and last column must be (0,0,0,1)
|
|
||||||
for (size_t i = 0; i < 3; ++i)
|
|
||||||
{
|
|
||||||
EXPECT_NEAR(m.at(3, i), 0.f, kEps);
|
|
||||||
EXPECT_NEAR(m.at(i, 3), 0.f, kEps);
|
|
||||||
}
|
|
||||||
EXPECT_NEAR(m.at(3, 3), 1.f, kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Quaternion, RotationMatrix4Upper3x3MatchesMatrix3)
|
|
||||||
{
|
|
||||||
const auto q = Quaternion<float>::from_axis_angle({0.f, 1.f, 0.f}, std::numbers::pi_v<float> / 7.f);
|
|
||||||
const auto m3 = q.to_rotation_matrix3();
|
|
||||||
const auto m4 = q.to_rotation_matrix4();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 3; ++i)
|
|
||||||
for (size_t j = 0; j < 3; ++j)
|
|
||||||
EXPECT_NEAR(m4.at(i, j), m3.at(i, j), kEps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── as_array ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, AsArray)
|
|
||||||
{
|
|
||||||
constexpr Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
constexpr auto arr = q.as_array();
|
|
||||||
EXPECT_FLOAT_EQ(arr[0], 1.f);
|
|
||||||
EXPECT_FLOAT_EQ(arr[1], 2.f);
|
|
||||||
EXPECT_FLOAT_EQ(arr[2], 3.f);
|
|
||||||
EXPECT_FLOAT_EQ(arr[3], 4.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── std::formatter ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
TEST(Quaternion, Formatter)
|
|
||||||
{
|
|
||||||
const Quaternion<float> q{1.f, 2.f, 3.f, 4.f};
|
|
||||||
const auto s = std::format("{}", q);
|
|
||||||
EXPECT_EQ(s, "[1, 2, 3, 4]");
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"default-registry": {
|
"default-registry": {
|
||||||
"kind": "git",
|
"kind": "git",
|
||||||
"baseline": "b1b19307e2d2ec1eefbdb7ea069de7d4bcd31f01",
|
"baseline": "05442024c3fda64320bd25d2251cc9807b84fb6f",
|
||||||
"repository": "https://github.com/microsoft/vcpkg"
|
"repository": "https://github.com/microsoft/vcpkg"
|
||||||
},
|
},
|
||||||
"registries": [
|
"registries": [
|
||||||
|
|||||||
11
vcpkg.json
11
vcpkg.json
@@ -17,9 +17,16 @@
|
|||||||
],
|
],
|
||||||
"features": {
|
"features": {
|
||||||
"avx2": {
|
"avx2": {
|
||||||
"description": "Omath will use AVX2 to boost performance",
|
"description": "omath will use AVX2 to boost performance",
|
||||||
"supports": "!arm"
|
"supports": "!arm"
|
||||||
},
|
},
|
||||||
|
"vmprotect": {
|
||||||
|
"description": "omath will use vmprotect sdk to protect sensitive parts of code from reverse engineering",
|
||||||
|
"supports": "windows | linux | osx | android",
|
||||||
|
"dependencies": [
|
||||||
|
"orange-vmprotect-sdk"
|
||||||
|
]
|
||||||
|
},
|
||||||
"benchmark": {
|
"benchmark": {
|
||||||
"description": "Build benchmarks",
|
"description": "Build benchmarks",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -35,7 +42,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"imgui": {
|
"imgui": {
|
||||||
"description": "Omath will define method to convert omath types to imgui types",
|
"description": "omath will define method to convert omath types to imgui types",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"imgui"
|
"imgui"
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user