Skip to content
Merged
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
1 change: 1 addition & 0 deletions lib/Materials/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Image::Image()
}

Image::Image(const char* file_name)
: m_image_width(0), m_image_height(0)
{
const std::string image_file_name(file_name);

Expand Down
1 change: 1 addition & 0 deletions lib/Materials/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Colour ImageTexture::Value(double u, double v, const Point3& point) const
{
if (m_image.Width() <= 0)
{
// Cyan
return Colour(0.0, 1.0, 1.0);
}

Expand Down
35 changes: 34 additions & 1 deletion lib/Maths/Vec3Int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ Vec3Int& Vec3Int::operator*=(int t)

Vec3Int& Vec3Int::operator/=(int t)
{
return *this *= static_cast<int>(1.0 / t);
m_x /= t;
m_y /= t;
m_z /= t;
return *this;
}

double Vec3Int::Length() const
Expand All @@ -104,4 +107,34 @@ Vec3Int Vec3Int::Random(int min, int max)
return static_cast<int32_t>(RandomDouble(min, max + 1.0));
}

Vec3Int operator+(const Vec3Int& vec1, const Vec3Int& vec2)
{
return Vec3Int(vec1.m_x + vec2.m_x, vec1.m_y + vec2.m_y, vec1.m_z + vec2.m_z);
}

Vec3Int operator-(const Vec3Int& vec1, const Vec3Int& vec2)
{
return Vec3Int(vec1.m_x - vec2.m_x, vec1.m_y - vec2.m_y, vec1.m_z - vec2.m_z);
}

Vec3Int operator*(const Vec3Int& vec1, const Vec3Int& vec2)
{
return Vec3Int(vec1.m_x * vec2.m_x, vec1.m_y * vec2.m_y, vec1.m_z * vec2.m_z);
}

Vec3Int operator*(const Vec3Int& vec, int t)
{
return Vec3Int(vec.m_x * t, vec.m_y * t, vec.m_z * t);
}

Vec3Int operator*(int t, const Vec3Int& vec)
{
return vec * t;
}

Vec3Int operator/(const Vec3Int& vec, int t)
{
return Vec3Int(vec.m_x / t, vec.m_y / t, vec.m_z / t);
}

} // namespace ART
160 changes: 160 additions & 0 deletions tests/AxisAlignedBoxTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright Mia Rolfe. All rights reserved.
#include <Catch2/catch.hpp>

#include <Geometry/AxisAlignedBox.h>
#include <Materials/Material.h>
#include <Materials/Texture.h>
#include <Maths/Ray.h>
#include <Core/Constants.h>

namespace ART
{

// Utility helper function.
// Unit box centred at origin: [-1, 1] on all axes
static AxisAlignedBox MakeUnitBox(Material* material)
{
return AxisAlignedBox(Point3(-1.0), Point3(1.0), material);
}

TEST_CASE("AxisAlignedBox Hit detects ray from +z hitting front face", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

// Ray from (0, 0, 5) going in -z direction hits the +z face at t=4
const Ray ray(Point3(0.0, 0.0, 5.0), Vec3(0.0, 0.0, -1.0));
RayHitResult result;
const bool hit = box.Hit(ray, Interval(0.001, infinity), result);

REQUIRE(hit == true);
REQUIRE(result.m_t == Approx(4.0));
REQUIRE(result.m_material == &material);
REQUIRE(result.m_is_front_facing == true);
}

TEST_CASE("AxisAlignedBox Hit detects ray from -x hitting left face", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

// Ray from (-5, 0, 0) going in +x direction hits the -x face at t=4
const Ray ray(Point3(-5.0, 0.0, 0.0), Vec3(1.0, 0.0, 0.0));
RayHitResult result;
const bool hit = box.Hit(ray, Interval(0.001, infinity), result);

REQUIRE(hit == true);
REQUIRE(result.m_t == Approx(4.0));
REQUIRE(result.m_is_front_facing == true);
}

TEST_CASE("AxisAlignedBox Hit misses a ray that does not intersect the box", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

// Ray going in +y direction from below offset misses entirely
const Ray ray(Point3(10.0, -5.0, 0.0), Vec3(0.0, 1.0, 0.0));
RayHitResult result;
const bool hit = box.Hit(ray, Interval(0.001, infinity), result);

REQUIRE(hit == false);
}

TEST_CASE("AxisAlignedBox Hit returns false for ray origin inside the box", "[AxisAlignedBox]")
{
// Interior intersections are not supported by AxisAlignedBox.

SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

// Ray origin inside the box going in +z
const Ray ray(Point3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 1.0));
RayHitResult result;
const bool hit = box.Hit(ray, Interval(0.001, infinity), result);

REQUIRE(hit == false);
}

TEST_CASE("AxisAlignedBox Hit produces UV coordinates in [0, 1]", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

// Several rays hitting different faces
const Ray rays[] =
{
Ray(Point3(0.0, 0.0, 5.0), Vec3( 0.0, 0.0, -1.0)), // +z face
Ray(Point3(0.0, 0.0, -5.0), Vec3( 0.0, 0.0, 1.0)), // -z face
Ray(Point3(5.0, 0.0, 0.0), Vec3(-1.0, 0.0, 0.0)), // +x face
Ray(Point3(0.0, 5.0, 0.0), Vec3( 0.0, -1.0, 0.0)), // +y face
};

for (const Ray& ray : rays)
{
RayHitResult result;
const bool hit = box.Hit(ray, Interval(0.001, infinity), result);

REQUIRE(hit == true);
REQUIRE(result.m_u >= 0.0);
REQUIRE(result.m_u <= 1.0);
REQUIRE(result.m_v >= 0.0);
REQUIRE(result.m_v <= 1.0);
}
}

TEST_CASE("AxisAlignedBox Hit respects interval bounds", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box = MakeUnitBox(&material);

const Ray ray(Point3(0.0, 0.0, 5.0), Vec3(0.0, 0.0, -1.0));
RayHitResult result;

// Hit is at t=4; interval starting at t=10 should miss
const bool hit = box.Hit(ray, Interval(10.0, infinity), result);

REQUIRE(hit == false);
}

TEST_CASE("AxisAlignedBox BoundingBox returns correct min and max (extents)", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);
const AxisAlignedBox box(Point3(-2.0, -3.0, -4.0), Point3(2.0, 3.0, 4.0), &material);

const AABB aabb = box.BoundingBox();

REQUIRE(aabb.m_x.m_min == Approx(-2.0));
REQUIRE(aabb.m_x.m_max == Approx(2.0));
REQUIRE(aabb.m_y.m_min == Approx(-3.0));
REQUIRE(aabb.m_y.m_max == Approx(3.0));
REQUIRE(aabb.m_z.m_min == Approx(-4.0));
REQUIRE(aabb.m_z.m_max == Approx(4.0));
}

TEST_CASE("AxisAlignedBox BoundingBox constructor from AABB matches min/max constructor", "[AxisAlignedBox]")
{
SolidColourTexture texture(Colour(0.5));
LambertianMaterial material(&texture);

const AABB aabb(Point3(-1.0, -1.0, -1.0), Point3(1.0, 1.0, 1.0));
const AxisAlignedBox box_from_aabb(aabb, &material);
const AxisAlignedBox box_from_min_and_max_points(Point3(-1.0, -1.0, -1.0), Point3(1.0, 1.0, 1.0), &material);

const AABB result_aabb = box_from_aabb.BoundingBox();
const AABB result_min_and_max_points = box_from_min_and_max_points.BoundingBox();

REQUIRE(result_aabb.m_x.m_min == Approx(result_min_and_max_points.m_x.m_min));
REQUIRE(result_aabb.m_x.m_max == Approx(result_min_and_max_points.m_x.m_max));
REQUIRE(result_aabb.m_z.m_min == Approx(result_min_and_max_points.m_z.m_min));
REQUIRE(result_aabb.m_z.m_max == Approx(result_min_and_max_points.m_z.m_max));
}

} // namespace ART
Loading
Loading