Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Engine/cpp/Runtime/Core/Math/AABB.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module;

#include <limits>

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<f32>::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;
}
};
}
97 changes: 97 additions & 0 deletions Engine/cpp/Runtime/Core/Math/AABB.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include <doctest_with_main.h>

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));
}
}
101 changes: 101 additions & 0 deletions Engine/cpp/Runtime/Core/Math/PackedTypes.bench.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#define ANKERL_NANOBENCH_IMPLEMENT
#include <nanobench.h>

#include <cstddef>
#include <cstdio>
#include <random>
#include <vector>

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<draco::f32> dist(-1.0f, 1.0f);
auto rnd = [&] { return dist(rng); };

std::vector<draco::math::Float4> a(COUNT), b(COUNT);
std::vector<draco::math::Float4x4> 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();
}
Loading
Loading