diff --git a/Engine/cpp/Runtime/Core/Math/AABB.cppm b/Engine/cpp/Runtime/Core/Math/AABB.cppm new file mode 100644 index 00000000..3060d778 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/AABB.cppm @@ -0,0 +1,56 @@ +module; + +#include + +export module core.math.aabb; + +import core.defs; +import core.stdtypes; +import core.math.types; + +export namespace draco::math { + + /** + * @brief An axis-aligned bounding box defined by its minimum and maximum corners. + */ + struct AABB + { + Vector3 min{0.0f, 0.0f, 0.0f}; + Vector3 max{0.0f, 0.0f, 0.0f}; + + /** + * @brief Returns an inverted box (min = +inf, max = -inf) so that the first expand() + * call correctly snaps both corners to the target point. + */ + [[nodiscard]] static constexpr AABB empty() noexcept + { + constexpr f32 inf = std::numeric_limits::infinity(); + return AABB{Vector3{inf}, Vector3{-inf}}; + } + + /** + * @brief Grows the bounding box to encapsulate the given point `p`. + */ + constexpr void expand(const Vector3 &p) noexcept + { + min = draco::math::min(min, p); + max = draco::math::max(max, p); + } + + /** + * @brief Computes and returns the center point of the bounding box. + */ + [[nodiscard]] constexpr Vector3 center() const noexcept + { + return (min + max) * 0.5f; + } + + /** + * @brief Computes and returns the total dimensions/size of the bounding box. + */ + [[nodiscard]] constexpr Vector3 size() const noexcept + { + return max - min; + } + }; +} diff --git a/Engine/cpp/Runtime/Core/Math/AABB.test.cpp b/Engine/cpp/Runtime/Core/Math/AABB.test.cpp new file mode 100644 index 00000000..68fec630 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/AABB.test.cpp @@ -0,0 +1,97 @@ +#include + +import core.stdtypes; +import core.math.types; +import core.math.aabb; + +using namespace draco; +using namespace draco::math; + +TEST_SUITE("math::AABB") +{ + + TEST_CASE("AABB: empty() initialization behaves correctly on first expand") + { + constexpr AABB box = AABB::empty(); + + // Assert bounding box properties are correctly inverted initially + CHECK(box.min.x > box.max.x); + CHECK(box.min.y > box.max.y); + CHECK(box.min.z > box.max.z); + + AABB mutableBox = box; + mutableBox.expand(Vector3{1.0f, 2.0f, 3.0f}); + + CHECK(mutableBox.min.x == 1.0f); + CHECK(mutableBox.max.x == 1.0f); + CHECK(mutableBox.min.y == 2.0f); + CHECK(mutableBox.max.y == 2.0f); + CHECK(mutableBox.min.z == 3.0f); + CHECK(mutableBox.max.z == 3.0f); + } + + TEST_CASE("AABB: expand properly handles multiple points") + { + AABB box = AABB::empty(); + box.expand(Vector3{-1.0f, -2.0f, -3.0f}); + box.expand(Vector3{4.0f, 5.0f, 6.0f}); + box.expand(Vector3{0.0f, 0.0f, 0.0f}); + + CHECK(box.min.x == -1.0f); + CHECK(box.min.y == -2.0f); + CHECK(box.min.z == -3.0f); + + CHECK(box.max.x == 4.0f); + CHECK(box.max.y == 5.0f); + CHECK(box.max.z == 6.0f); + } + + TEST_CASE("AABB: center and size calculations match expected properties") + { + constexpr AABB box{Vector3{-2.0f, 0.0f, 1.0f}, Vector3{4.0f, 6.0f, 5.0f}}; + + constexpr Vector3 c = box.center(); + CHECK(c.x == doctest::Approx(1.0f)); + CHECK(c.y == doctest::Approx(3.0f)); + CHECK(c.z == doctest::Approx(3.0f)); + + constexpr Vector3 s = box.size(); + CHECK(s.x == doctest::Approx(6.0f)); + CHECK(s.y == doctest::Approx(6.0f)); + CHECK(s.z == doctest::Approx(4.0f)); + } +} + +TEST_SUITE("math::types alignment and behavior validation") +{ + + TEST_CASE("Vector2 layout constraints") + { + Vector2 v1{1.0f, 2.0f}; + Vector2 v2{3.0f, 4.0f}; + + CHECK(sizeof(Vector2) == 8); + CHECK(alignof(Vector2) == 8); + CHECK((v1 + v2).x == 4.0f); + CHECK((v1 + v2).y == 6.0f); + } + + TEST_CASE("Vector3 layout constraints") + { + Vector3 v{3.0f, 4.0f, 0.0f}; + + CHECK(sizeof(Vector3) == 16); + CHECK(alignof(Vector3) == 16); + CHECK(length(v) == doctest::Approx(5.0f)); + } + + TEST_CASE("Vector4 layout constraints") + { + Vector4 v1{1.0f, 2.0f, 3.0f, 4.0f}; + Vector4 v2{2.0f, 2.0f, 2.0f, 2.0f}; + + CHECK(sizeof(Vector4) == 16); + CHECK(alignof(Vector4) == 16); + CHECK(dot(v1, v2) == doctest::Approx(20.0f)); + } +} diff --git a/Engine/cpp/Runtime/Core/Math/PackedTypes.bench.cpp b/Engine/cpp/Runtime/Core/Math/PackedTypes.bench.cpp new file mode 100644 index 00000000..964638ea --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/PackedTypes.bench.cpp @@ -0,0 +1,101 @@ +#define ANKERL_NANOBENCH_IMPLEMENT +#include + +#include +#include +#include +#include + +import core.math; + +namespace { + +// Build config output: +void print_metadata() { +#if defined(__clang__) + std::printf("# compiler : clang %d.%d.%d\n", __clang_major__, __clang_minor__, __clang_patchlevel__); +#elif defined(__GNUC__) + std::printf("# compiler : gcc %d.%d.%d\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#elif defined(_MSC_VER) + std::printf("# compiler : msvc %d\n", _MSC_VER); +#endif + + std::printf("# sizeof(Float2) = %zu, alignof = %zu\n", sizeof(draco::math::Float2), alignof(draco::math::Float2)); + std::printf("# sizeof(Float3) = %zu, alignof = %zu\n", sizeof(draco::math::Float3), alignof(draco::math::Float3)); + std::printf("# sizeof(Float4) = %zu, alignof = %zu\n", sizeof(draco::math::Float4), alignof(draco::math::Float4)); + std::printf("# sizeof(Float4x4) = %zu, alignof = %zu\n", sizeof(draco::math::Float4x4), alignof(draco::math::Float4x4)); +} + +void benchmark_vector_operations() { + constexpr std::size_t COUNT = 1024; + + std::mt19937 rng(0xDEADBABE); + std::uniform_real_distribution dist(-1.0f, 1.0f); + auto rnd = [&] { return dist(rng); }; + + std::vector a(COUNT), b(COUNT); + std::vector mats(COUNT); + + for (std::size_t i = 0; i < COUNT; ++i) { + a[i] = { rnd(), rnd(), rnd(), rnd() }; + b[i] = { rnd(), rnd(), rnd(), rnd() }; + mats[i] = draco::math::Float4x4( + draco::math::Float4{rnd(), rnd(), rnd(), rnd()}, + draco::math::Float4{rnd(), rnd(), rnd(), rnd()}, + draco::math::Float4{rnd(), rnd(), rnd(), rnd()}, + draco::math::Float4{rnd(), rnd(), rnd(), rnd()} + ); + } + + const auto configure = [&](ankerl::nanobench::Bench& bench, const char* title) { + bench + .title(title) + .relative(true) + .epochs(20) + .epochIterations(5000); + }; + + ankerl::nanobench::Bench throughputBench; + configure(throughputBench, "Float4 dot throughput"); + throughputBench.run("packed dot", [&] { + draco::f32 acc0 = 0.0f, acc1 = 0.0f, acc2 = 0.0f, acc3 = 0.0f; + for (std::size_t i = 0; i < COUNT; i += 4) { + acc0 += draco::math::dot(a[i + 0], b[i + 0]); + acc1 += draco::math::dot(a[i + 1], b[i + 1]); + acc2 += draco::math::dot(a[i + 2], b[i + 2]); + acc3 += draco::math::dot(a[i + 3], b[i + 3]); + } + ankerl::nanobench::doNotOptimizeAway(acc0 + acc1 + acc2 + acc3); + }); + + ankerl::nanobench::Bench chainBench; + configure(chainBench, "Float4 dot latency chain"); + chainBench.run("packed dependency chain", [&] { + draco::math::Float4 v = a[0]; + draco::f32 s = 0.25f; + for (std::size_t i = 0; i < COUNT; ++i) { + v = { s, s, s, s }; + s = draco::math::dot(v, b[i]); + } + ankerl::nanobench::doNotOptimizeAway(s); + ankerl::nanobench::doNotOptimizeAway(v); + }); + + // Matrix-Vector multiplication + ankerl::nanobench::Bench matBench; + configure(matBench, "Float4x4 * Float4 performance"); + matBench.run("matrix * vector throughput", [&] { + draco::math::Float4 total{0.0f}; + for (std::size_t i = 0; i < COUNT; ++i) { + total += mats[i] * a[i]; + } + ankerl::nanobench::doNotOptimizeAway(total); + }); +} + +} // namespace + +int main() { + print_metadata(); + benchmark_vector_operations(); +} diff --git a/Engine/cpp/Runtime/Core/Math/PackedTypes.cppm b/Engine/cpp/Runtime/Core/Math/PackedTypes.cppm new file mode 100644 index 00000000..25440374 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/PackedTypes.cppm @@ -0,0 +1,633 @@ +module; + +#include +#include +#include +#include + +export module core.math.types:packed; + +import core.defs; +import core.stdtypes; +import core.math.constants; +import core.math.functions; + +export namespace draco::math { + + // Forward declarations + struct Float2; + struct Float3; + struct Float4; + struct Float4x4; + + // Index selection helpers for runtime/constexpr swizzling + template + constexpr const T &select(const i32 i, const T &v1, const T &v2) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + default: + return v1; + } + } + + template + constexpr T &select(const i32 i, T &v1, T &v2) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + default: + return v1; + } + } + + template + constexpr const T &select(const i32 i, const T &v1, const T &v2, const T &v3) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + case 2: + return v3; + default: + return v1; + } + } + + template + constexpr T &select(const i32 i, T &v1, T &v2, T &v3) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + case 2: + return v3; + default: + return v1; + } + } + + template + constexpr const T &select(const i32 i, const T &v1, const T &v2, const T &v3, const T &v4) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + case 2: + return v3; + case 3: + return v4; + default: + return v1; + } + } + + template + constexpr T &select(const i32 i, T &v1, T &v2, T &v3, T &v4) + { + switch (i) + { + case 0: + return v1; + case 1: + return v2; + case 2: + return v3; + case 3: + return v4; + default: + return v1; + } + } + + /** + * @brief Float2 is a packed 2-component vector of 32-bit floating-point values. + */ + struct alignas(8) Float2 + { + f32 x{0.0f}, y{0.0f}; + + [[nodiscard]] constexpr Float2() noexcept = default; + [[nodiscard]] constexpr explicit Float2(f32 n) noexcept : x{n}, y{n} {} + [[nodiscard]] constexpr Float2(f32 x, f32 y) noexcept : x{x}, y{y} {} + [[nodiscard]] constexpr explicit Float2(const Float3 &xyz) noexcept; + [[nodiscard]] constexpr explicit Float2(const Float4 &xyzw) noexcept; + + [[nodiscard]] static constexpr Float2 xAxis(f32 x = 1.0f) noexcept { return {x, 0.0f}; } + [[nodiscard]] static constexpr Float2 yAxis(f32 y = 1.0f) noexcept { return {0.0f, y}; } + [[nodiscard]] static Float2 polar(f32 angle, f32 radius = 1.0f) noexcept + { + return {radius * std::cos(angle), radius * std::sin(angle)}; + } + + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept + { + return select(i, x, y); + } + [[nodiscard]] constexpr const f32 &operator[](i32 i) const noexcept + { + return select(i, x, y); + } + + [[nodiscard]] constexpr Float2 operator[](i32 i0, i32 i1) const noexcept + { + return {select(i0, x, y), select(i1, x, y)}; + } + [[nodiscard]] constexpr Float3 operator[](i32 i0, i32 i1, i32 i2) const noexcept; + [[nodiscard]] constexpr Float4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; + + [[nodiscard]] constexpr Float2 operator+() const noexcept { return {x, y}; } + [[nodiscard]] constexpr Float2 operator-() const noexcept { return {-x, -y}; } + [[nodiscard]] constexpr bool operator==(const Float2 &other) const noexcept = default; + + constexpr Float2 &operator+=(const Float2 &other) noexcept + { + x += other.x; + y += other.y; + return *this; + } + constexpr Float2 &operator+=(f32 other) noexcept + { + x += other; + y += other; + return *this; + } + constexpr Float2 &operator-=(const Float2 &other) noexcept + { + x -= other.x; + y -= other.y; + return *this; + } + constexpr Float2 &operator-=(f32 other) noexcept + { + x -= other; + y -= other; + return *this; + } + constexpr Float2 &operator*=(const Float2 &other) noexcept + { + x *= other.x; + y *= other.y; + return *this; + } + constexpr Float2 &operator*=(f32 other) noexcept + { + x *= other; + y *= other; + return *this; + } + constexpr Float2 &operator/=(const Float2 &other) noexcept + { + x /= other.x; + y /= other.y; + return *this; + } + constexpr Float2 &operator/=(f32 other) noexcept + { + f32 inv = 1.0f / other; + x *= inv; + y *= inv; + return *this; + } + constexpr Float2 &operator=(f32 other) noexcept + { + x = other; + y = other; + return *this; + } + }; + + static_assert(sizeof(Float2) == 8 && alignof(Float2) == 8, "Float2 Layout Mismatch"); + + /** + * @brief Float3 is a packed 3-component vector of 32-bit floating-point values. + */ + struct alignas(16) Float3 + { + f32 x{0.0f}, y{0.0f}, z{0.0f}; + + [[nodiscard]] constexpr Float3() noexcept = default; + [[nodiscard]] constexpr explicit Float3(f32 n) noexcept : x{n}, y{n}, z{n} {} + [[nodiscard]] constexpr Float3(f32 x, f32 y, f32 z) noexcept : x{x}, y{y}, z{z} {} + [[nodiscard]] constexpr explicit Float3(const Float2 &xy, f32 z = 0.0f) noexcept : x{xy.x}, y{xy.y}, z{z} {} + [[nodiscard]] constexpr Float3(f32 x, const Float2 &yz) noexcept : x{x}, y{yz.x}, z{yz.y} {} + [[nodiscard]] constexpr explicit Float3(const Float4 &xyzw) noexcept; + + [[nodiscard]] static constexpr Float3 xAxis(f32 x = 1.0f) noexcept { return {x, 0.0f, 0.0f}; } + [[nodiscard]] static constexpr Float3 yAxis(f32 y = 1.0f) noexcept { return {0.0f, y, 0.0f}; } + [[nodiscard]] static constexpr Float3 zAxis(f32 z = 1.0f) noexcept { return {0.0f, 0.0f, z}; } + [[nodiscard]] static Float3 spherical(f32 azimuth, f32 inclination, f32 radius = 1.0f) noexcept + { + f32 sin_incl = radius * std::sin(inclination); + return {sin_incl * std::cos(azimuth), radius * std::cos(inclination), sin_incl * std::sin(azimuth)}; + } + + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept + { + return select(i, x, y, z); + } + [[nodiscard]] constexpr const f32 &operator[](i32 i) const noexcept + { + return select(i, x, y, z); + } + + [[nodiscard]] constexpr Float2 operator[](i32 i0, i32 i1) const noexcept + { + return {select(i0, x, y, z), select(i1, x, y, z)}; + } + [[nodiscard]] constexpr Float3 operator[](i32 i0, i32 i1, i32 i2) const noexcept + { + return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z)}; + } + [[nodiscard]] constexpr Float4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; + + [[nodiscard]] constexpr Float3 operator+() const noexcept { return {x, y, z}; } + [[nodiscard]] constexpr Float3 operator-() const noexcept { return {-x, -y, -z}; } + [[nodiscard]] constexpr bool operator==(const Float3 &other) const noexcept = default; + + constexpr Float3 &operator+=(const Float3 &other) noexcept + { + x += other.x; + y += other.y; + z += other.z; + return *this; + } + constexpr Float3 &operator+=(f32 other) noexcept + { + x += other; + y += other; + z += other; + return *this; + } + constexpr Float3 &operator-=(const Float3 &other) noexcept + { + x -= other.x; + y -= other.y; + z -= other.z; + return *this; + } + constexpr Float3 &operator-=(f32 other) noexcept + { + x -= other; + y -= other; + z -= other; + return *this; + } + constexpr Float3 &operator*=(const Float3 &other) noexcept + { + x *= other.x; + y *= other.y; + z *= other.z; + return *this; + } + constexpr Float3 &operator*=(f32 other) noexcept + { + x *= other; + y *= other; + z *= other; + return *this; + } + constexpr Float3 &operator/=(const Float3 &other) noexcept + { + x /= other.x; + y /= other.y; + z /= other.z; + return *this; + } + constexpr Float3 &operator/=(f32 other) noexcept + { + f32 inv = 1.0f / other; + x *= inv; + y *= inv; + z *= inv; + return *this; + } + constexpr Float3 &operator=(f32 other) noexcept + { + x = other; + y = other; + z = other; + return *this; + } + }; + + static_assert(sizeof(Float3) == 16 && alignof(Float3) == 16, "Float3 Layout Mismatch"); + + /** + * @brief Float4 is a packed 4-component vector of 32-bit floating-point values. + */ + struct alignas(16) Float4 + { + f32 x{0.0f}, y{0.0f}, z{0.0f}, w{0.0f}; + + [[nodiscard]] constexpr Float4() noexcept = default; + [[nodiscard]] constexpr explicit Float4(f32 n) noexcept : x{n}, y{n}, z{n}, w{n} {} + [[nodiscard]] constexpr Float4(f32 x, f32 y, f32 z, f32 w) noexcept : x{x}, y{y}, z{z}, w{w} {} + [[nodiscard]] constexpr explicit Float4(const Float2 &xy) noexcept : x{xy.x}, y{xy.y}, z{0.0f}, w{0.0f} {} + [[nodiscard]] constexpr Float4(const Float2 &xy, f32 z, f32 w) noexcept : x{xy.x}, y{xy.y}, z{z}, w{w} {} + [[nodiscard]] constexpr explicit Float4(const Float3 &xyz, f32 w = 0.0f) noexcept : x{xyz.x}, y{xyz.y}, z{xyz.z}, w{w} {} + + [[nodiscard]] static constexpr Float4 xAxis(f32 x = 1.0f) noexcept { return {x, 0.0f, 0.0f, 0.0f}; } + [[nodiscard]] static constexpr Float4 yAxis(f32 y = 1.0f) noexcept { return {0.0f, y, 0.0f, 0.0f}; } + [[nodiscard]] static constexpr Float4 zAxis(f32 z = 1.0f) noexcept { return {0.0f, 0.0f, z, 0.0f}; } + [[nodiscard]] static constexpr Float4 wAxis(f32 w = 1.0f) noexcept { return {0.0f, 0.0f, 0.0f, w}; } + + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept + { + return select(i, x, y, z, w); + } + [[nodiscard]] constexpr const f32 &operator[](i32 i) const noexcept + { + return select(i, x, y, z, w); + } + + [[nodiscard]] constexpr Float2 operator[](i32 i0, i32 i1) const noexcept + { + return {select(i0, x, y, z, w), select(i1, x, y, z, w)}; + } + [[nodiscard]] constexpr Float3 operator[](i32 i0, i32 i1, i32 i2) const noexcept + { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w)}; + } + [[nodiscard]] constexpr Float4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept + { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w), select(i3, x, y, z, w)}; + } + + [[nodiscard]] constexpr Float4 operator+() const noexcept { return {x, y, z, w}; } + [[nodiscard]] constexpr Float4 operator-() const noexcept { return {-x, -y, -z, -w}; } + [[nodiscard]] constexpr bool operator==(const Float4 &other) const noexcept = default; + + constexpr Float4 &operator+=(const Float4 &other) noexcept + { + x += other.x; + y += other.y; + z += other.z; + w += other.w; + return *this; + } + constexpr Float4 &operator+=(f32 other) noexcept + { + x += other; + y += other; + z += other; + w += other; + return *this; + } + constexpr Float4 &operator-=(const Float4 &other) noexcept + { + x -= other.x; + y -= other.y; + z -= other.z; + w -= other.w; + return *this; + } + constexpr Float4 &operator-=(f32 other) noexcept + { + x -= other; + y -= other; + z -= other; + w -= other; + return *this; + } + constexpr Float4 &operator*=(const Float4 &other) noexcept + { + x *= other.x; + y *= other.y; + z *= other.z; + w *= other.w; + return *this; + } + constexpr Float4 &operator*=(f32 other) noexcept + { + x *= other; + y *= other; + z *= other; + w *= other; + return *this; + } + constexpr Float4 &operator/=(const Float4 &other) noexcept + { + x /= other.x; + y /= other.y; + z /= other.z; + w /= other.w; + return *this; + } + constexpr Float4 &operator/=(f32 other) noexcept + { + f32 inv = 1.0f / other; + x *= inv; + y *= inv; + z *= inv; + w *= inv; + return *this; + } + constexpr Float4 &operator=(f32 other) noexcept + { + x = other; + y = other; + z = other; + w = other; + return *this; + } + }; + + static_assert(sizeof(Float4) == 16 && alignof(Float4) == 16, "Float4 Layout Mismatch"); + + // These are out of order implementations due to structural dependency + constexpr Float2::Float2(const Float3 &xyz) noexcept : x{xyz.x}, y{xyz.y} {} + constexpr Float2::Float2(const Float4 &xyzw) noexcept : x{xyzw.x}, y{xyzw.y} {} + constexpr Float3::Float3(const Float4 &xyzw) noexcept : x{xyzw.x}, y{xyzw.y}, z{xyzw.z} {} + constexpr Float3 Float2::operator[](i32 i0, i32 i1, i32 i2) const noexcept { return {select(i0, x, y), select(i1, x, y), select(i2, x, y)}; } + constexpr Float4 Float2::operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept { return {select(i0, x, y), select(i1, x, y), select(i2, x, y), select(i3, x, y)}; } + constexpr Float4 Float3::operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept { return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z), select(i3, x, y, z)}; } + + /** + * @brief Float4x4 is a packed 4x4 column-major matrix composed of four Float4 columns. + */ + struct alignas(16) Float4x4 + { + Float4 columns[4]; + + [[nodiscard]] constexpr Float4x4() noexcept : columns{Float4{1.0f, 0.0f, 0.0f, 0.0f}, Float4{0.0f, 1.0f, 0.0f, 0.0f}, Float4{0.0f, 0.0f, 1.0f, 0.0f}, Float4{0.0f, 0.0f, 0.0f, 1.0f}} {} + + [[nodiscard]] constexpr explicit Float4x4(f32 diagonal) noexcept : columns{Float4{diagonal, 0.0f, 0.0f, 0.0f}, Float4{0.0f, diagonal, 0.0f, 0.0f}, Float4{0.0f, 0.0f, diagonal, 0.0f}, Float4{0.0f, 0.0f, 0.0f, diagonal}} {} + + [[nodiscard]] constexpr Float4x4(const Float4 &c0, const Float4 &c1, const Float4 &c2, const Float4 &c3) noexcept : columns{c0, c1, c2, c3} {} + + [[nodiscard]] constexpr Float4 &operator[](i32 column_idx) noexcept { return columns[column_idx]; } + [[nodiscard]] constexpr const Float4 &operator[](i32 column_idx) const noexcept { return columns[column_idx]; } + + [[nodiscard]] constexpr bool operator==(const Float4x4 &other) const noexcept = default; + + /** + * @brief Multiplies this matrix with another Float4x4 column-major matrix. + */ + [[nodiscard]] constexpr Float4x4 operator*(const Float4x4 &other) const noexcept + { + Float4x4 result; + for (i32 col = 0; col < 4; ++col) + { + for (i32 row = 0; row < 4; ++row) + { + result[col][row] = columns[0][row] * other[col][0] + columns[1][row] * other[col][1] + columns[2][row] * other[col][2] + columns[3][row] * other[col][3]; + } + } + return result; + } + + /** + * @brief Multiplies this matrix by a Float4 vector. + */ + [[nodiscard]] constexpr Float4 operator*(const Float4 &vector) const noexcept + { + return { + columns[0].x * vector.x + columns[1].x * vector.y + columns[2].x * vector.z + columns[3].x * vector.w, + columns[0].y * vector.x + columns[1].y * vector.y + columns[2].y * vector.z + columns[3].y * vector.w, + columns[0].z * vector.x + columns[1].z * vector.y + columns[2].z * vector.z + columns[3].z * vector.w, + columns[0].w * vector.x + columns[1].w * vector.y + columns[2].w * vector.z + columns[3].w * vector.w + }; + } + }; + + static_assert(sizeof(Float4x4) == 64 && alignof(Float4x4) == 16, "Float4x4 Layout Mismatch"); + + // Float2 Operators + [[nodiscard]] constexpr Float2 operator+(const Float2 &a, const Float2 &b) noexcept { return {a.x + b.x, a.y + b.y}; } + [[nodiscard]] constexpr Float2 operator+(const Float2 &a, f32 b) noexcept { return {a.x + b, a.y + b}; } + [[nodiscard]] constexpr Float2 operator+(f32 a, const Float2 &b) noexcept { return b + a; } + [[nodiscard]] constexpr Float2 operator-(const Float2 &a, const Float2 &b) noexcept { return {a.x - b.x, a.y - b.y}; } + [[nodiscard]] constexpr Float2 operator-(const Float2 &a, f32 b) noexcept { return {a.x - b, a.y - b}; } + [[nodiscard]] constexpr Float2 operator-(f32 a, const Float2 &b) noexcept { return {a - b.x, a - b.y}; } + [[nodiscard]] constexpr Float2 operator*(const Float2 &a, const Float2 &b) noexcept { return {a.x * b.x, a.y * b.y}; } + [[nodiscard]] constexpr Float2 operator*(const Float2 &a, f32 b) noexcept { return {a.x * b, a.y * b}; } + [[nodiscard]] constexpr Float2 operator*(f32 a, const Float2 &b) noexcept { return b * a; } + [[nodiscard]] constexpr Float2 operator/(const Float2 &a, const Float2 &b) noexcept { return {a.x / b.x, a.y / b.y}; } + [[nodiscard]] constexpr Float2 operator/(const Float2 &a, f32 b) noexcept { return a * (1.0f / b); } + + // Float3 Operators + [[nodiscard]] constexpr Float3 operator+(const Float3 &a, const Float3 &b) noexcept { return {a.x + b.x, a.y + b.y, a.z + b.z}; } + [[nodiscard]] constexpr Float3 operator+(const Float3 &a, f32 b) noexcept { return {a.x + b, a.y + b, a.z + b}; } + [[nodiscard]] constexpr Float3 operator+(f32 a, const Float3 &b) noexcept { return b + a; } + [[nodiscard]] constexpr Float3 operator-(const Float3 &a, const Float3 &b) noexcept { return {a.x - b.x, a.y - b.y, a.z - b.z}; } + [[nodiscard]] constexpr Float3 operator-(const Float3 &a, f32 b) noexcept { return {a.x - b, a.y - b, a.z - b}; } + [[nodiscard]] constexpr Float3 operator-(f32 a, const Float3 &b) noexcept { return {a - b.x, a - b.y, a - b.z}; } + [[nodiscard]] constexpr Float3 operator*(const Float3 &a, const Float3 &b) noexcept { return {a.x * b.x, a.y * b.y, a.z * b.z}; } + [[nodiscard]] constexpr Float3 operator*(const Float3 &a, f32 b) noexcept { return {a.x * b, a.y * b, a.z * b}; } + [[nodiscard]] constexpr Float3 operator*(f32 a, const Float3 &b) noexcept { return b * a; } + [[nodiscard]] constexpr Float3 operator/(const Float3 &a, const Float3 &b) noexcept { return {a.x / b.x, a.y / b.y, a.z / b.z}; } + [[nodiscard]] constexpr Float3 operator/(const Float3 &a, f32 b) noexcept { return a * (1.0f / b); } + + // Float4 Operators + [[nodiscard]] constexpr Float4 operator+(const Float4 &a, const Float4 &b) noexcept { return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; } + [[nodiscard]] constexpr Float4 operator+(const Float4 &a, f32 b) noexcept { return {a.x + b, a.y + b, a.z + b, a.w + b}; } + [[nodiscard]] constexpr Float4 operator+(f32 a, const Float4 &b) noexcept { return b + a; } + [[nodiscard]] constexpr Float4 operator-(const Float4 &a, const Float4 &b) noexcept { return {a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w}; } + [[nodiscard]] constexpr Float4 operator-(const Float4 &a, f32 b) noexcept { return {a.x - b, a.y - b, a.z - b, a.w - b}; } + [[nodiscard]] constexpr Float4 operator-(f32 a, const Float4 &b) noexcept { return {a - b.x, a - b.y, a - b.z, a - b.w}; } + [[nodiscard]] constexpr Float4 operator*(const Float4 &a, const Float4 &b) noexcept { return {a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w}; } + [[nodiscard]] constexpr Float4 operator*(const Float4 &a, f32 b) noexcept { return {a.x * b, a.y * b, a.z * b, a.w * b}; } + [[nodiscard]] constexpr Float4 operator*(f32 a, const Float4 &b) noexcept { return b * a; } + [[nodiscard]] constexpr Float4 operator/(const Float4 &a, const Float4 &b) noexcept { return {a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w}; } + [[nodiscard]] constexpr Float4 operator/(const Float4 &a, f32 b) noexcept { return a * (1.0f / b); } + + // Vector Operations + [[nodiscard]] constexpr f32 dot(const Float2 &a, const Float2 &b) noexcept { return a.x * b.x + a.y * b.y; } + [[nodiscard]] constexpr f32 dot(const Float3 &a, const Float3 &b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z; } + [[nodiscard]] constexpr f32 dot(const Float4 &a, const Float4 &b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } + + [[nodiscard]] constexpr f32 length_sq(const Float2 &v) noexcept { return dot(v, v); } + [[nodiscard]] constexpr f32 length_sq(const Float3 &v) noexcept { return dot(v, v); } + [[nodiscard]] constexpr f32 length_sq(const Float4 &v) noexcept { return dot(v, v); } + + [[nodiscard]] f32 length(const Float2 &v) noexcept { return std::sqrt(length_sq(v)); } + [[nodiscard]] f32 length(const Float3 &v) noexcept { return std::sqrt(length_sq(v)); } + [[nodiscard]] f32 length(const Float4 &v) noexcept { return std::sqrt(length_sq(v)); } + + [[nodiscard]] Float2 normalize(const Float2 &v) noexcept + { + f32 len = length(v); + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Float2(); + } + [[nodiscard]] Float3 normalize(const Float3 &v) noexcept + { + f32 len = length(v); + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Float3(); + } + [[nodiscard]] Float4 normalize(const Float4 &v) noexcept + { + f32 len = length(v); + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Float4(); + } + + [[nodiscard]] constexpr Float3 cross(const Float3 &a, const Float3 &b) noexcept + { + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; + } +} // namespace draco::math + +// Custom STD formatters for Math types +export namespace std { + template <> + struct formatter : formatter + { + auto format(const draco::math::Float2 &v, format_context &ctx) const + { + ctx.advance_to(format_to(ctx.out(), "{{")); + ctx.advance_to(formatter::format(v.x, ctx)); + ctx.advance_to(format_to(ctx.out(), ", ")); + ctx.advance_to(formatter::format(v.y, ctx)); + return format_to(ctx.out(), "}}"); + } + }; + + template <> + struct formatter : formatter + { + auto format(const draco::math::Float3 &v, format_context &ctx) const + { + ctx.advance_to(format_to(ctx.out(), "{{")); + for (draco::i32 i = 0; i < 3; ++i) + { + if (i) + ctx.advance_to(format_to(ctx.out(), ", ")); + ctx.advance_to(formatter::format(v[i], ctx)); + } + return format_to(ctx.out(), "}}"); + } + }; + + template <> + struct formatter : formatter + { + auto format(const draco::math::Float4 &v, format_context &ctx) const + { + ctx.advance_to(format_to(ctx.out(), "{{")); + for (draco::i32 i = 0; i < 4; ++i) + { + if (i) + ctx.advance_to(format_to(ctx.out(), ", ")); + ctx.advance_to(formatter::format(v[i], ctx)); + } + return format_to(ctx.out(), "}}"); + } + }; +} diff --git a/Engine/cpp/Runtime/Core/Math/Types.cppm b/Engine/cpp/Runtime/Core/Math/Types.cppm index 0dbef119..95d870f9 100644 --- a/Engine/cpp/Runtime/Core/Math/Types.cppm +++ b/Engine/cpp/Runtime/Core/Math/Types.cppm @@ -1,6 +1,7 @@ export module core.math.types; export import :common; +export import :packed; export import :vector2; export import :vector3; export import :vector4; \ No newline at end of file