├── .gitignore ├── CMakeLists.txt ├── README.md ├── TODO ├── apps ├── CMakeLists.txt ├── cannon │ ├── cannon.cpp │ ├── cannon_ppm.cpp │ ├── projectile.cpp │ ├── projectile.h │ ├── worldconditions.cpp │ └── worldconditions.h ├── clock │ └── clock.cpp ├── scenes │ ├── cover.cpp │ ├── cylinders.cpp │ ├── draw_scene.cpp │ ├── draw_scene2.cpp │ ├── draw_scene3.cpp │ ├── draw_sphere.cpp │ ├── reflect_refract.cpp │ ├── shadow_puppets.cpp │ ├── table.cpp │ └── uv_mapping.cpp └── timer.h ├── output ├── .DS_Store ├── cylinders.png ├── reflect_refract.png └── table.png ├── src ├── CMakeLists.txt ├── affine_transform.h ├── camera.cpp ├── camera.h ├── canvas.cpp ├── canvas.h ├── common.h ├── constmath.h ├── impl │ ├── bounding_box.cpp │ ├── bounding_box.h │ ├── cube_functions.cpp │ ├── cube_functions.h │ ├── hit.cpp │ ├── hit.h │ ├── instance_manager.cpp │ ├── instance_manager.h │ ├── intersection.cpp │ ├── intersection.h │ ├── ray.cpp │ └── ray.h ├── material.cpp ├── material.h ├── matrix.h ├── patterns │ ├── checkerpattern.cpp │ ├── checkerpattern.h │ ├── gradientpattern.cpp │ ├── gradientpattern.h │ ├── pattern.cpp │ ├── pattern.h │ ├── perlin.h │ ├── ringpattern.cpp │ ├── ringpattern.h │ ├── solidpattern.cpp │ ├── solidpattern.h │ ├── stripepattern.cpp │ └── stripepattern.h ├── pointlight.h ├── shapes │ ├── cone.cpp │ ├── cone.h │ ├── cube.cpp │ ├── cube.h │ ├── cylinder.cpp │ ├── cylinder.h │ ├── group.cpp │ ├── group.h │ ├── plane.cpp │ ├── plane.h │ ├── shape.cpp │ ├── shape.h │ ├── sphere.cpp │ └── sphere.h ├── transformers.h ├── vec.h ├── world.cpp └── world.h └── test ├── CMakeLists.txt ├── TestBounds.cpp ├── TestCamera.cpp ├── TestCanvas.cpp ├── TestColour.cpp ├── TestCone.cpp ├── TestCube.cpp ├── TestCylinder.cpp ├── TestGroup.cpp ├── TestIntersection.cpp ├── TestLights.cpp ├── TestMaterial.cpp ├── TestMatrix.cpp ├── TestPattern.cpp ├── TestPattern.h ├── TestPlane.cpp ├── TestRay.cpp ├── TestShape.cpp ├── TestShape.h ├── TestSphere.cpp ├── TestTransform.cpp ├── TestTuple.cpp ├── TestWorld.cpp ├── catch.hpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # *** CMake *** 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | 13 | # *** CLion *** 14 | .idea/**/workspace.xml 15 | .idea/**/tasks.xml 16 | /cmake-build-*/ 17 | build/ 18 | *-prefix 19 | *-prefix/** 20 | *.dSYM 21 | *.dSYM/** 22 | s 23 | # Prerequisites 24 | *.d 25 | 26 | # Compiled Object files 27 | *.slo 28 | *.lo 29 | *.o 30 | *.obj 31 | 32 | # Precompiled Headers 33 | *.gch 34 | *.pch 35 | 36 | # Compiled Dynamic libraries 37 | *.so 38 | *.dylib 39 | *.dll 40 | 41 | # Fortran module files 42 | *.mod 43 | *.smod 44 | 45 | # Compiled Static libraries 46 | *.lai 47 | *.la 48 | *.a 49 | *.lib 50 | 51 | # Executables 52 | *.exe 53 | *.out 54 | *.app 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt 2 | # By Sebastian Raaphorst, 2018. 3 | 4 | cmake_minimum_required(VERSION 3.12) 5 | project(raytracer_top) 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | if (MSVC) 9 | add_compile_options(/W4) 10 | else() 11 | add_compile_options(-Wall -Wextra -Wpedantic -Wno-narrowing -ftemplate-depth=10000 -fconstexpr-depth=2000 -std=c++17 -g) 12 | endif() 13 | 14 | #find_package(OpenMP REQUIRED) 15 | #if (OPENMP_FOUND) 16 | # set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 17 | # set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 18 | # set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") 19 | #endif() 20 | 21 | include_directories( 22 | "${PROJECT_SOURCE_DIR}/src" 23 | "${PROJECT_SOURCE_DIR}/src/impl" 24 | "${PROJECT_SOURCE_DIR}/src/patterns" 25 | "${PROJECT_SOURCE_DIR}/src/shapes" 26 | ) 27 | 28 | add_subdirectory(src) 29 | add_subdirectory(test) 30 | add_subdirectory(apps) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ray Tracer 2 | 3 | **Current status:** In progress. 4 | 5 | A ray tracer implemented in C++17 based on Jamis Buck's book: 6 | 7 | https://pragprog.com/book/jbtracer/the-ray-tracer-challenge 8 | 9 | **NOTE:** I originally intended to attempt to write a `constexpr` ray tracer to learn more about it; however, due to the size of the project and having done many other projects in `constexpr`, as well as being under a time restriction due to being a technical reviewer for the book, I decided to implement it as a run-time library. 10 | 11 | The current code may not compile under clang, and requires GCC in C++17 mode. It also requires cmake 3.12, as it uses list transformers. 12 | 13 | My current goals are to: 14 | 15 | 1. Complete the book and any bonus material; 16 | 17 | 2. Simplify scene creation by adding convenience methods, and trying to hide the use of smart pointers more; and 18 | 19 | 3. Implement a parser for a modeling language to simplify modeling. 20 | 21 | 22 | Here are some images generated by the ray tracer thus far: 23 | 24 | Showing reflection and refraction: 25 | 26 | ![reflect-refract](output/reflect_refract.png) 27 | 28 | A scene made entirely of cubes: 29 | 30 | ![table](output/table.png) 31 | 32 | Cylinders: 33 | 34 | ![cylinders](output/cylinders.png) 35 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1. Why does Shape have a setTransformation and setMaterial with no const argument? Can we get rid of it? 2 | 2. Shape::normalAt can probably be made protected in the final API. -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt 2 | # By Sebastian Raaphorst, 2018. 3 | 4 | add_executable(cannon cannon/cannon.cpp cannon/projectile.cpp cannon/worldconditions.cpp) 5 | target_link_libraries(cannon PRIVATE raytracer)# OpenMP::OpenMP_CXX) 6 | 7 | add_executable(cannon_ppm cannon/cannon_ppm.cpp cannon/projectile.cpp cannon/worldconditions.cpp) 8 | target_link_libraries(cannon_ppm PRIVATE raytracer)# OpenMP::OpenMP_CXX) 9 | 10 | add_executable(clock clock/clock.cpp) 11 | target_link_libraries(clock PRIVATE raytracer)# OpenMP::OpenMP_CXX) 12 | 13 | add_executable(draw_sphere scenes/draw_sphere.cpp) 14 | target_link_libraries(draw_sphere PRIVATE raytracer)# OpenMP::OpenMP_CXX) 15 | 16 | add_executable(draw_scene scenes/draw_scene.cpp) 17 | target_link_libraries(draw_scene PRIVATE raytracer)# OpenMP::OpenMP_CXX) 18 | 19 | add_executable(draw_scene2 scenes/draw_scene2.cpp) 20 | target_link_libraries(draw_scene2 PRIVATE raytracer) #OpenMP::OpenMP_CXX) 21 | 22 | add_executable(draw_scene3 scenes/draw_scene3.cpp) 23 | target_link_libraries(draw_scene3 PRIVATE raytracer) #OpenMP::OpenMP_CXX) 24 | 25 | add_executable(shadow_puppets scenes/shadow_puppets.cpp) 26 | target_link_libraries(shadow_puppets PRIVATE raytracer)# OpenMP::OpenMP_CXX) 27 | 28 | add_executable(reflect_refract scenes/reflect_refract.cpp) 29 | target_link_libraries(reflect_refract PRIVATE raytracer)# OpenMP::OpenMP_CXX) 30 | 31 | add_executable(uv_mapping scenes/uv_mapping.cpp) 32 | target_link_libraries(uv_mapping PRIVATE raytracer)# OpenMP::OpenMP_CXX) 33 | 34 | add_executable(table scenes/table.cpp) 35 | target_link_libraries(table PRIVATE raytracer)# OpenMP::OpenMP_CXX) 36 | 37 | add_executable(cylinders scenes/cylinders.cpp) 38 | target_link_libraries(cylinders PRIVATE raytracer)# OpenMP::OpenMP_CXX) 39 | 40 | add_executable(cover scenes/cover.cpp) 41 | target_link_libraries(cover PRIVATE raytracer)# OpenMP::OpenMP_CXX) -------------------------------------------------------------------------------- /apps/cannon/cannon.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cannon.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "projectile.h" 12 | #include 13 | #include "worldconditions.h" 14 | 15 | using namespace raytracer; 16 | 17 | int main() { 18 | WorldConditions w{make_vector(0, -0.1, 0), make_vector(-0.01, 0, 0)}; 19 | Projectile p{make_point(0, 1, 0), make_vector(1, 1, 0).normalize()}; 20 | 21 | std::cout << "In " << w << "\n\tbeginning with " << p << "\n\n"; 22 | 23 | size_t ticks = 0; 24 | std::cout << "Time" << std::setw(34) << "Velocity" << std::setw(42) << "Position" << '\n'; 25 | for (; p.inAir(); p = p.tick(w), ++ticks) { 26 | const Tuple &position = p.getPosition(); 27 | const Tuple &velocity = p.getVelocity(); 28 | 29 | std::cout << std::fixed 30 | << std::setw( 4) << ticks 31 | << std::setw(20) << '(' << std::setprecision(5) << velocity[tuple_constants::x] << ", " 32 | << std::setw( 8) << std::setprecision(5) << velocity[tuple_constants::y] << ", " 33 | << std::setw( 0) << std::setprecision(0) << velocity[tuple_constants::z] << ')' 34 | << std::setw(20) << '(' << std::setprecision(5) << std::setw(8) << position[tuple_constants::x] << ", " 35 | << std::setw( 7) << position[tuple_constants::y] << ", " 36 | << std::setw( 1) << std::setprecision(0) << position[tuple_constants::z] << ")\n"; 37 | } 38 | 39 | std::cout << "\nTotal time to hit ground: " << ticks << " ticks." << std::endl; 40 | } -------------------------------------------------------------------------------- /apps/cannon/cannon_ppm.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cannon_ppm.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "projectile.h" 16 | #include "worldconditions.h" 17 | 18 | using namespace raytracer; 19 | 20 | int main() { 21 | constexpr auto width = 900; 22 | constexpr auto height = 550; 23 | WorldConditions w{make_vector(0, -0.1, 0), make_vector(-0.01, 0, 0)}; 24 | const auto colour = make_colour(1, 1, 0); 25 | 26 | Canvas c{width, height}; 27 | for (Projectile p{make_point(0, 1, 0), make_vector(1, 1.8, 0).normalize() * 11.25}; p.inAir(); p = p.tick(w)) { 28 | const auto pos = p.getPosition(); 29 | const auto x = (int) pos[tuple_constants::x]; 30 | const auto y = height - (int) pos[tuple_constants::y]; 31 | 32 | assert(x >= 0 && x < width); 33 | assert(y >= 0 && y < height); 34 | 35 | c[x][y] = colour; 36 | } 37 | 38 | std::ofstream out("cannon.ppm"); 39 | out << c; 40 | out.close(); 41 | } -------------------------------------------------------------------------------- /apps/cannon/projectile.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * projectile.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "projectile.h" 10 | 11 | namespace raytracer { 12 | 13 | std::ostream &operator<<(std::ostream &os, const Projectile &projectile) { 14 | return os << "projectile(position: " << projectile.position << ", velocity: " << projectile.velocity << ')'; 15 | } 16 | } -------------------------------------------------------------------------------- /apps/cannon/projectile.h: -------------------------------------------------------------------------------- 1 | /** 2 | * projectile.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "vec.h" 15 | #include "worldconditions.h" 16 | 17 | namespace raytracer { 18 | class Projectile { 19 | private: 20 | Tuple position; 21 | Tuple velocity; 22 | 23 | public: 24 | constexpr Projectile(const Tuple &position, const Tuple &velocity): position{position}, velocity{velocity} { 25 | if (!position.isPoint()) 26 | throw std::invalid_argument("projectile requires a position for the first argument"); 27 | if (!velocity.isVector()) 28 | throw std::invalid_argument("projectile requires a vector for the second argument"); 29 | } 30 | 31 | constexpr Projectile(const Projectile &other) = default; 32 | 33 | Projectile &operator=(const Projectile&) = default; 34 | 35 | Projectile tick(const WorldConditions w) const { 36 | return Projectile{position + velocity, velocity + w.gravity + w.wind}; 37 | } 38 | 39 | constexpr const Tuple &getPosition() const { 40 | return position; 41 | } 42 | 43 | constexpr const Tuple &getVelocity() const { 44 | return velocity; 45 | } 46 | 47 | constexpr bool inAir() const { 48 | return position[tuple_constants::y] >= 0; 49 | } 50 | 51 | friend std::ostream &operator<<(std::ostream &os, const Projectile &projectile); 52 | }; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /apps/cannon/worldconditions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * worldconditions.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "worldconditions.h" 10 | 11 | namespace raytracer { 12 | 13 | std::ostream &operator<<(std::ostream &os, const WorldConditions &world) { 14 | return os << "world(gravity: " << world.gravity << ", wind: " << world.wind << ')'; 15 | } 16 | } -------------------------------------------------------------------------------- /apps/cannon/worldconditions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * worldconditions.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | #include "vec.h" 13 | 14 | namespace raytracer { 15 | class Projectile; 16 | 17 | class WorldConditions { 18 | private: 19 | const Tuple gravity; 20 | const Tuple wind; 21 | 22 | public: 23 | constexpr WorldConditions(Tuple gravity, Tuple wind): gravity{gravity}, wind{wind} { 24 | if (!gravity.isVector()) 25 | throw std::invalid_argument("world requires a vector for the first argument"); 26 | if (!wind.isVector()) 27 | throw std::invalid_argument("world requires a vector for the second argument"); 28 | } 29 | 30 | friend std::ostream &operator<<(std::ostream &os, const WorldConditions &world); 31 | friend class Projectile; 32 | }; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /apps/clock/clock.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * clock.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | * 6 | * Draw a simplistic clock with pixels representing the hour markers. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace raytracer; 21 | 22 | int main() { 23 | constexpr int size = 300; 24 | constexpr int border = 50; 25 | 26 | Colour colour{1, 1, 0}; 27 | Canvas c{size, size}; 28 | 29 | // Calculate the rotation needed to advance one hour. 30 | constexpr auto rot = math_constants::pi<> / 6; 31 | 32 | // Scale to the desired height. 33 | constexpr auto sz = (size - 2 * border) / 2; 34 | constexpr auto sc = scale(sz, sz, 0); 35 | 36 | // Calculate the translation to get to the middle of the canvas. 37 | constexpr auto tr = translation(size / 2, size / 2, 0); 38 | 39 | // Start with a point pointing straight up to 12:00. 40 | constexpr auto p = make_point(0, -1, 0); 41 | 42 | // // The first two techniques build up rotations. 43 | // // In the first, we use pointers, since Matrix is immutable. 44 | // // In the second, we use a recursive lambda. 45 | // auto transform = std::unique_ptr{new Transformation{rot.andThen(sc).andThen(tr)}}; 46 | // for (size_t i = 0; i < 12; ++i) { 47 | // auto newp = *transform * p; 48 | // c[newp[tuple_constants::x]][newp[tuple_constants::y]] = colour_ptr; 49 | // transform = std::unique_ptr{new Transformation{rot.andThen(*transform)}}; 50 | // } 51 | 52 | // // Using a recursive lambda. 53 | // std::function runner = [&] (size_t iterations, Transformation transformation){ 54 | // if (iterations == 0) 55 | // return transformation; 56 | // auto newp = transformation * p; 57 | // c[newp[tuple_constants::x]][newp[tuple_constants::y]] = colour_ptr; 58 | // return runner(iterations - 1, rot.andThen(transformation)); 59 | // }; 60 | // runner(12, sc.andThen(tr)); 61 | 62 | 63 | for (size_t i = 0; i < 12; ++i) { 64 | auto newp = rotation_z(i * rot).andThen(sc).andThen(tr) * p; 65 | c[newp[tuple_constants::x]][newp[tuple_constants::y]] = colour; 66 | } 67 | 68 | // Let's add hands to show the current time. 69 | auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 70 | auto now2 = std::localtime(&now); 71 | int hour = now2->tm_hour % 12; 72 | int min = now2->tm_min; 73 | 74 | // Colour of the hour and minute hand. 75 | const auto hour_colour = predefined_colours::red; 76 | const auto min_colour = make_colour(0.5, 0.5, 1); 77 | 78 | // Angle per minute. 79 | constexpr auto minuteangle = math_constants::pi<> / 30; 80 | 81 | // Draw the hour hand. 82 | for (int i = -10; i < sz - 20; ++i) { 83 | auto newp = rotation_z((hour + min / 60.0) * rot) 84 | .andThen(scale(i, i, i)) 85 | .andThen(tr) * p; 86 | c[newp[tuple_constants::x]][newp[tuple_constants::y]] = hour_colour; 87 | } 88 | 89 | // Draw the minute hand. 90 | for (int i = -10; i < sz; ++i) { 91 | auto newp = rotation_z(min * minuteangle) 92 | .andThen(scale(i, i, i)) 93 | .andThen(tr) * p; 94 | c[newp[tuple_constants::x]][newp[tuple_constants::y]] = min_colour; 95 | } 96 | 97 | std::ofstream out("clock.ppm"); 98 | out << c; 99 | out.close(); 100 | } 101 | -------------------------------------------------------------------------------- /apps/scenes/cover.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cover.cpp 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "../timer.h" 23 | 24 | using namespace raytracer; 25 | using namespace raytracer::shapes; 26 | 27 | int main() { 28 | const Camera camera{1000, 1000, 0.785, 29 | view_transform(make_point(-6, 6, -10), make_point(6, 0, 6), make_point(-0.45, 1, 0))}; 30 | const PointLight light{make_point(50, 100, -50), predefined_colours::white}; 31 | 32 | // Materials 33 | auto white_material = std::make_shared(predefined_colours::white); 34 | white_material->setDiffuse(0.7); 35 | white_material->setAmbient(0.1); 36 | white_material->setSpecular(0.0); 37 | white_material->setReflectivity(0.1); 38 | 39 | auto blue_material = std::make_shared(make_colour(0.537, 0.831, 0.914)); 40 | blue_material->setDiffuse(0.7); 41 | blue_material->setAmbient(0.1); 42 | blue_material->setSpecular(0.0); 43 | blue_material->setReflectivity(0.1); 44 | 45 | auto red_material = std::make_shared(make_colour(0.941, 0.322, 0.388)); 46 | red_material->setDiffuse(0.7); 47 | red_material->setAmbient(0.1); 48 | red_material->setSpecular(0.0); 49 | red_material->setReflectivity(0.1); 50 | 51 | auto purple_material = std::make_shared(make_colour(0.373, 0.404, 0.550)); 52 | purple_material->setDiffuse(0.7); 53 | purple_material->setAmbient(0.1); 54 | purple_material->setSpecular(0.0); 55 | purple_material->setReflectivity(0.1); 56 | 57 | const auto standard_transform = scale(0.5, 0.5, 0.5) * translation(1, -1, 1); 58 | const auto large_object = scale(3.5, 3.5, 3.5) * standard_transform; 59 | const auto medium_object = scale(3, 3, 3) * standard_transform; 60 | const auto small_object = scale(2, 2, 2) * standard_transform; 61 | 62 | // White backdrop for the scene. 63 | auto plane = Plane::createPlane(); 64 | auto plane_material = std::make_shared(predefined_colours::white); 65 | plane_material->setAmbient(1.0); 66 | plane_material->setDiffuse(0); 67 | plane_material->setSpecular(0); 68 | plane->setMaterial(plane_material); 69 | plane->setTransformation(translation(0, 0, 500) * rotation_x(math_constants::pi_by_two<>)); 70 | 71 | // Elements of the scene. 72 | auto sphere = Sphere::createSphere(); 73 | auto sphere_material = std::make_shared(make_colour(0.373, 0.404, 0.550)); 74 | sphere_material->setDiffuse(0.2); 75 | sphere_material->setAmbient(0); 76 | sphere_material->setSpecular(1); 77 | sphere_material->setShininess(200); 78 | sphere_material->setReflectivity(0.7); 79 | sphere_material->setTransparency(0.7); 80 | sphere_material->setRefractiveIndex(1.5); 81 | sphere->setMaterial(sphere_material); 82 | sphere->setTransformation(large_object); 83 | 84 | auto cube1 = Cube::createCube(); 85 | cube1->setMaterial(white_material); 86 | cube1->setTransformation(translation(4, 0, 0) * medium_object); 87 | 88 | auto cube2 = Cube::createCube(); 89 | cube2->setMaterial(blue_material); 90 | cube2->setTransformation(translation(8.5, 1.5, -0.5) * large_object); 91 | 92 | auto cube3 = Cube::createCube(); 93 | cube3->setMaterial(red_material); 94 | cube3->setTransformation(translation(0, 0, 4) * large_object); 95 | 96 | auto cube4 = Cube::createCube(); 97 | cube4->setMaterial(white_material); 98 | cube4->setTransformation(translation(4, 0, 4) * small_object); 99 | 100 | auto cube5 = Cube::createCube(); 101 | cube5->setMaterial(purple_material); 102 | cube5->setTransformation(translation(7.5, 0.5, 4) * medium_object); 103 | 104 | auto cube6 = Cube::createCube(); 105 | cube6->setMaterial(white_material); 106 | cube6->setTransformation(translation(-0.25, 0.25, 8) * medium_object); 107 | 108 | auto cube7 = Cube::createCube(); 109 | cube7->setMaterial(blue_material); 110 | cube7->setTransformation(translation(4, 1, 7.5) * large_object); 111 | 112 | auto cube8 = Cube::createCube(); 113 | cube8->setMaterial(red_material); 114 | cube8->setTransformation(translation(10, 2, 7.5) * medium_object); 115 | 116 | auto cube9 = Cube::createCube(); 117 | cube9->setMaterial(white_material); 118 | cube9->setTransformation(translation(8, 2, 12) * small_object); 119 | 120 | auto cube10 = Cube::createCube(); 121 | cube10->setMaterial(white_material); 122 | cube10->setTransformation(translation(20, 1, 9) * small_object); 123 | 124 | auto cube11 = Cube::createCube(); 125 | cube11->setMaterial(blue_material); 126 | cube11->setTransformation(translation(-0.5, -5, 0.25) * large_object); 127 | 128 | auto cube12 = Cube::createCube(); 129 | cube12->setMaterial(red_material); 130 | cube12->setTransformation(translation(4, -4, 0) * large_object); 131 | 132 | auto cube13 = Cube::createCube(); 133 | cube13->setMaterial(white_material); 134 | cube13->setTransformation(translation(8.5, -4, 0) * large_object); 135 | 136 | auto cube14 = Cube::createCube(); 137 | cube14->setMaterial(white_material); 138 | cube14->setTransformation(translation(0, -4, 4) * large_object); 139 | 140 | auto cube15 = Cube::createCube(); 141 | cube15->setMaterial(purple_material); 142 | cube15->setTransformation(translation(-0.5, -4.5, 8) * large_object); 143 | 144 | auto cube16 = Cube::createCube(); 145 | cube16->setMaterial(white_material); 146 | cube16->setTransformation(translation(0, -8, 4) * large_object); 147 | 148 | auto cube17 = Cube::createCube(); 149 | cube17->setMaterial(white_material); 150 | cube17->setTransformation(translation(-0.5, -8.5, 8) * large_object); 151 | 152 | const std::vector> shapes = { 153 | plane, sphere, 154 | cube1, cube2, cube3, cube4, cube5, cube6, cube7, cube8, cube9, 155 | cube10, cube11, cube12, cube13, cube14, cube15, cube16, cube17 156 | }; 157 | 158 | const World world{light, shapes}; 159 | 160 | run_timed("cover", [&camera, &world] { 161 | const auto canvas = camera.render(world); 162 | std::ofstream out("cover.ppm"); 163 | out << canvas; 164 | out.close(); 165 | }); 166 | } -------------------------------------------------------------------------------- /apps/scenes/draw_scene.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * draw_scene.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "../timer.h" 21 | 22 | using namespace raytracer; 23 | using namespace raytracer::shapes; 24 | 25 | int main() { 26 | auto wall_material = std::make_shared(std::make_shared(make_colour(1, 0.9, 0.9))); 27 | wall_material->setSpecular(0); 28 | 29 | auto floor = Sphere::createSphere(); 30 | floor->setTransformation(scale(10, 0.01, 10)); 31 | floor->setMaterial(wall_material); 32 | 33 | auto left_wall = Sphere::createSphere(); 34 | left_wall->setTransformation(translation(0, 0, 5) 35 | * rotation_y(-math_constants::pi_by_four<>) 36 | * rotation_x(math_constants::pi_by_two<>) 37 | * scale(10, 0.01, 10)); 38 | left_wall->setMaterial(wall_material); 39 | 40 | auto right_wall = Sphere::createSphere(); 41 | right_wall->setTransformation(translation(0, 0, 5) 42 | * rotation_y(math_constants::pi_by_four<>) 43 | * rotation_x(math_constants::pi_by_two<>) 44 | * scale(10, 0.01, 10)); 45 | right_wall->setMaterial(wall_material); 46 | 47 | auto middle_sphere = Sphere::createSphere(); 48 | middle_sphere->setTransformation(translation(-0.5, 1, -0.5)); 49 | middle_sphere->setMaterial(std::make_shared(make_colour(0.1, 1, 0.5), Material::DEFAULT_AMBIENT, 50 | 0.7, 0.3, Material::DEFAULT_SHININESS)); 51 | 52 | auto left_sphere = Sphere::createSphere(); 53 | left_sphere->setTransformation(translation(-2, 0.33, -0.75) * scale(0.33, 0.33, 0.33)); 54 | left_sphere->setMaterial(std::make_shared(make_colour(1, 0.8, 0.1), Material::DEFAULT_AMBIENT, 55 | 0.7, 0.3, Material::DEFAULT_SHININESS)); 56 | 57 | auto right_sphere = Sphere::createSphere(); 58 | right_sphere->setTransformation(translation(1.5, 0.5, -0.5) * scale(0.5, 0.5, 0.5)); 59 | right_sphere->setMaterial(std::make_shared(make_colour(0.5, 1, 0.1), Material::DEFAULT_AMBIENT, 60 | 0.7, 0.3, Material::DEFAULT_SHININESS)); 61 | 62 | std::vector> shapes = {floor, left_wall, right_wall, left_sphere, right_sphere, middle_sphere}; 63 | 64 | PointLight light{make_point(-10, 10, -10), predefined_colours::white}; 65 | auto world = World{light, shapes}; 66 | auto camera = Camera{1000, 500, math_constants::pi_by_three<>, 67 | view_transform(make_point(0, 1.5, -5), make_point(0, 1, 0), predefined_tuples::y1)}; 68 | 69 | run_timed("draw_scene", [&camera, &world] { 70 | auto canvas = camera.render(world); 71 | std::ofstream out("scene.ppm"); 72 | out << canvas; 73 | out.close(); 74 | }); 75 | } -------------------------------------------------------------------------------- /apps/scenes/draw_scene2.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * draw_scene2.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "../timer.h" 23 | 24 | using namespace raytracer; 25 | using namespace raytracer::shapes; 26 | 27 | int main() { 28 | auto wall_material = std::make_shared(); 29 | std::vector wall_colours{predefined_colours::red, predefined_colours::white}; 30 | wall_material->setPattern(std::make_shared(wall_colours)); 31 | wall_material->setSpecular(0); 32 | 33 | std::vector ball_colours{predefined_colours::white, predefined_colours::blue}; 34 | const auto ball_material = std::make_shared(std::make_shared(ball_colours), 35 | Material::DEFAULT_AMBIENT, 0.7, 0.3, Material::DEFAULT_SHININESS); 36 | 37 | auto floor = Plane::createPlane(); 38 | floor->setTransformation(scale(10, 0.01, 10)); 39 | floor->setMaterial(wall_material); 40 | 41 | auto left_wall = Plane::createPlane(); 42 | left_wall->setTransformation(translation(0, 0, 5) 43 | * rotation_y(-math_constants::pi_by_four<>) 44 | * rotation_x(math_constants::pi_by_two<>) 45 | * scale(10, 0.01, 10)); 46 | left_wall->setMaterial(wall_material); 47 | 48 | auto right_wall = Plane::createPlane(); 49 | right_wall->setTransformation(translation(0, 0, 5) 50 | * rotation_y(math_constants::pi_by_four<>) 51 | * rotation_x(math_constants::pi_by_two<>) 52 | * scale(10, 0.01, 10)); 53 | right_wall->setMaterial(wall_material); 54 | 55 | auto middle_sphere = Sphere::createSphere(); 56 | middle_sphere->setTransformation(translation(-0.5, 1, -0.5) * rotation_z(0.7)); 57 | middle_sphere->setMaterial(ball_material); 58 | 59 | auto left_sphere = Sphere::createSphere(); 60 | left_sphere->setTransformation(translation(-2, 0.33, -0.75) * scale(0.33, 0.33, 0.33)); 61 | left_sphere->setMaterial(ball_material); 62 | 63 | auto right_sphere = Sphere::createSphere(); 64 | right_sphere->setTransformation(translation(1.5, 0.5, -0.5) * scale(0.5, 0.5, 0.5)); 65 | right_sphere->setMaterial(ball_material); 66 | 67 | std::vector> shapes = {floor, left_wall, right_wall, left_sphere, right_sphere, 68 | middle_sphere}; 69 | 70 | PointLight light{make_point(-10, 10, -10), predefined_colours::white}; 71 | auto world = World{light, shapes}; 72 | auto camera = Camera{1000, 500, math_constants::pi_by_three<>, 73 | view_transform(make_point(0, 1.5, -5), make_point(0, 1, 0), predefined_tuples::y1)}; 74 | 75 | run_timed("draw_scene2", [&camera, &world] { 76 | auto canvas = camera.render(world); 77 | std::ofstream out("scene2.ppm"); 78 | out << canvas; 79 | out.close(); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /apps/scenes/draw_scene3.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * draw_scene3.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../timer.h" 24 | 25 | using namespace raytracer; 26 | using namespace raytracer::shapes; 27 | 28 | int main() { 29 | auto wall_material = std::make_shared(); 30 | std::vector wall_colours{predefined_colours::red, predefined_colours::white}; 31 | wall_material->setPattern(std::make_shared(wall_colours)); 32 | wall_material->setSpecular(0); 33 | 34 | std::vector ball_colours{predefined_colours::white, predefined_colours::blue}; 35 | auto ball_material = std::make_shared(std::make_shared(ball_colours), 36 | Material::DEFAULT_AMBIENT, 0.7, 0.3, Material::DEFAULT_SHININESS); 37 | 38 | auto floor = Plane::createPlane(); 39 | floor->setTransformation(scale(10, 0.01, 10)); 40 | floor->setMaterial(wall_material); 41 | 42 | auto left_wall = Sphere::createSphere(); 43 | left_wall->setTransformation(translation(0, 0, 5) 44 | * rotation_y(-math_constants::pi_by_four<>) 45 | * rotation_x(math_constants::pi_by_two<>) 46 | * scale(10, 0.01, 10)); 47 | left_wall->setMaterial(wall_material); 48 | 49 | auto right_wall = Sphere::createSphere(); 50 | right_wall->setTransformation(translation(0, 0, 5) 51 | * rotation_y(math_constants::pi_by_four<>) 52 | * rotation_x(math_constants::pi_by_two<>) 53 | * scale(10, 0.01, 10)); 54 | right_wall->setMaterial(wall_material); 55 | 56 | auto middle_ball_material = std::make_shared(); 57 | middle_ball_material->setReflectivity(1); 58 | auto middle_sphere = Sphere::createSphere(); 59 | middle_sphere->setTransformation(translation(-0.5, 1, -0.5)); 60 | middle_sphere->setMaterial(middle_ball_material); 61 | 62 | auto left_sphere = Sphere::createSphere(); 63 | left_sphere->setTransformation(translation(-2, 0.33, -0.75) * scale(0.33, 0.33, 0.33)); 64 | left_sphere->setMaterial(ball_material); 65 | 66 | auto right_sphere = Sphere::createSphere(); 67 | right_sphere->setTransformation(translation(1.5, 0.5, -0.5) * scale(0.5, 0.5, 0.5)); 68 | right_sphere->setMaterial(ball_material); 69 | 70 | std::vector> shapes = {floor, left_wall, right_wall, 71 | left_sphere, right_sphere, middle_sphere}; 72 | 73 | PointLight light{make_point(-10, 10, -10), predefined_colours::white}; 74 | auto world = World{light, shapes}; 75 | auto camera = Camera{1000, 500, math_constants::pi_by_three<>, 76 | view_transform(make_point(0, 1.5, -5), make_point(0, 1, 0), predefined_tuples::y1)}; 77 | 78 | run_timed("draw_scene3", [&camera, &world] { 79 | auto canvas = camera.render(world); 80 | std::ofstream out("scene3.ppm"); 81 | out << canvas; 82 | out.close(); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /apps/scenes/draw_sphere.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * draw_sphere.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace raytracer; 21 | using namespace raytracer::shapes; 22 | 23 | int main() { 24 | constexpr int canvas_pixels = 1000; 25 | 26 | // Start the ray at z = -5. 27 | const auto ray_origin = make_point(0, 0, -5); 28 | 29 | // Put the wall at z = 10. 30 | constexpr double wall_z = 10; 31 | constexpr double wall_size = 7; 32 | 33 | // Determine the pixel size. 34 | constexpr double pixel_size = wall_size / canvas_pixels; 35 | 36 | // As sphere is centered at origin, this variable describes min / max 37 | // x and y values. 38 | constexpr double half = wall_size / 2; 39 | 40 | Canvas c{canvas_pixels, canvas_pixels}; 41 | 42 | // Give the sphere a purple-ish colour. 43 | auto s = Sphere::createSphere(); 44 | const auto m = std::make_shared(std::make_shared(make_colour(1, 0.2, 1))); 45 | s->setMaterial(m); 46 | s->setTransformation(scale(0.5, 1, 1).andThen(rotation_z(M_PI_4))); 47 | 48 | // Add a light source: 49 | const PointLight light{make_point(-10, 10, -10), predefined_colours::white}; 50 | 51 | // For each row of pixels in the canvas: 52 | for (auto y = 0; y < canvas_pixels; ++y) { 53 | // Compute the world y coordinate (top = +half, bottom = -half). 54 | const auto world_y = half - pixel_size * y; 55 | 56 | // For each pixel in the row: 57 | for (auto x = 0; x < canvas_pixels; ++x) { 58 | // Compute the world x coordinate (left = -half, right = +half). 59 | const auto world_x = -half + pixel_size * x; 60 | 61 | // Describe the point on the wall that the ray will target. 62 | const auto position = make_point(world_x, world_y, wall_z); 63 | 64 | const impl::Ray r{ray_origin, (position - ray_origin).normalize()}; 65 | const auto xs = s->intersect(r); 66 | const auto hit = impl::Intersection::hit(xs); 67 | if (hit.has_value()) { 68 | const auto &intersection = hit.value(); 69 | const auto point = r.position(intersection.getT()); 70 | const auto normal = intersection.getObject()->normalAt(point); 71 | const auto eye = -r.getDirection(); 72 | c[x][y] = intersection.getObject()->getMaterial()->lighting(light, s, point, eye, normal, false); 73 | } 74 | } 75 | } 76 | 77 | std::ofstream out("sphere.ppm"); 78 | out << c; 79 | out.close(); 80 | } -------------------------------------------------------------------------------- /apps/scenes/shadow_puppets.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * shadow_puppets.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | * 6 | * From: http://forum.raytracerchallenge.com/thread/2/shadow-puppets-scene-description 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "../timer.h" 24 | 25 | using namespace raytracer; 26 | using namespace raytracer::shapes; 27 | 28 | int main() { 29 | Camera camera{800, 400, math_constants::pi_by_six<>, 30 | view_transform(make_point(40, 0, -70), make_point(0, 0, -5), predefined_tuples::y1)}; 31 | PointLight light{make_point(0, 0, -100), predefined_colours::white}; 32 | 33 | auto sphere_material = std::make_shared(); 34 | sphere_material->setAmbient(0.2); 35 | sphere_material->setDiffuse(0.8); 36 | sphere_material->setSpecular(0.3); 37 | sphere_material->setShininess(200); 38 | 39 | auto wrist_material = std::make_shared(*sphere_material); 40 | wrist_material->setPattern(std::make_shared(make_colour(0.1, 1, 1))); 41 | 42 | auto thumb_and_palm_material = std::make_shared(*sphere_material); 43 | thumb_and_palm_material->setPattern(std::make_shared(make_colour(0.1, 0.1, 1))); 44 | 45 | auto index_material = std::make_shared(*sphere_material); 46 | index_material->setPattern(std::make_shared(make_colour(1, 1, 0.1))); 47 | 48 | auto middle_material = std::make_shared(*sphere_material); 49 | middle_material->setPattern(std::make_shared(make_colour(0.1, 1, 0.5))); 50 | 51 | auto ring_material = std::make_shared(*sphere_material); 52 | ring_material->setPattern(std::make_shared(make_colour(0.1, 1, 0.1))); 53 | 54 | auto pinky_material = std::make_shared(*sphere_material); 55 | pinky_material->setPattern(std::make_shared(make_colour(0.1, 0.5, 1))); 56 | 57 | // Backdrop onto which to cast the shadow. 58 | auto backdrop = Sphere::createSphere(); 59 | auto backdrop_material = std::make_shared(); 60 | backdrop_material->setPattern(std::make_shared(predefined_colours::white)); 61 | backdrop_material->setAmbient(0); 62 | backdrop_material->setDiffuse(0.5); 63 | backdrop_material->setSpecular(0); 64 | backdrop->setMaterial(backdrop_material); 65 | backdrop->setTransformation(translation(0, 0, 20) * scale(200, 200, 0.01)); 66 | 67 | // Wrist 68 | auto wrist = Sphere::createSphere(); 69 | wrist->setMaterial(wrist_material); 70 | wrist->setTransformation(rotation_z(math_constants::pi_by_four<>) * translation(-4, 0, -21) * scale(3, 3, 3)); 71 | 72 | // Palm 73 | auto palm = Sphere::createSphere(); 74 | palm->setMaterial(thumb_and_palm_material); 75 | palm->setTransformation(translation(0, 0, -15) * scale(4, 3, 3)); 76 | 77 | // Thumb 78 | auto thumb = Sphere::createSphere(); 79 | thumb->setMaterial(thumb_and_palm_material); 80 | thumb->setTransformation(translation(-2, 2, -16) * scale(1, 3, 1)); 81 | 82 | // Index finger 83 | auto index = Sphere::createSphere(); 84 | index->setMaterial(index_material); 85 | index->setTransformation(translation(3, 2, -22) * scale(3, 0.75, 0.75)); 86 | 87 | // Middle finger 88 | auto middle = Sphere::createSphere(); 89 | middle->setMaterial(middle_material); 90 | middle->setTransformation(translation(4, 1, -19) * scale(3, 0.75, 0.75)); 91 | 92 | // Ring finger 93 | auto ring = Sphere::createSphere(); 94 | ring->setMaterial(ring_material); 95 | ring->setTransformation(translation(4, 0, -18) * scale(3, 0.75, 0.75)); 96 | 97 | // Pinky finger 98 | auto pinky = Sphere::createSphere(); 99 | pinky->setMaterial(pinky_material); 100 | pinky->setTransformation(translation(3, -1.5, -20) * rotation_z(-math_constants::pi<> / 10.0) 101 | * translation(1, 0, 0) * scale(2.5, 0.6, 0.6)); 102 | 103 | std::vector> shapes = {backdrop, wrist, palm, thumb, index, middle, ring, pinky}; 104 | World world{light, shapes}; 105 | 106 | run_timed("shadow_puppets", [&camera, &world] { 107 | auto canvas = camera.render(world); 108 | std::ofstream out("shadow_puppets.ppm"); 109 | out << canvas; 110 | out.close(); 111 | }); 112 | } -------------------------------------------------------------------------------- /apps/scenes/uv_mapping.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * uv_mapping.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "../timer.h" 22 | 23 | using namespace raytracer; 24 | using namespace raytracer::shapes; 25 | 26 | int main() { 27 | // Camera camera{800, 400, math_constants::pi_by_six<>, 28 | // view_transform(make_point(40, 0, -70), make_point(0, 0, -5), predefined_tuples::y1)}; 29 | Camera camera{400, 400, math_constants::pi<> / 10, 30 | view_transform(make_point(0, 0, 0), make_point(0, 0, 1), predefined_tuples::y1)}; 31 | 32 | PointLight light{make_point(0, 10, -10), predefined_colours::white}; 33 | 34 | // Ball 1 material 35 | auto ball1_pattern = std::make_shared(make_colour(0.3, 1.0, 0.3), 36 | make_colour(1.0, 1.0, 0.3)); 37 | ball1_pattern->setTransformation(rotation_y(math_constants::pi_by_three<>) * scale(0.25, 0.25, 0.25)); 38 | auto ball1_materail = std::make_shared(ball1_pattern); 39 | auto ball1 = Sphere::createSphere(); 40 | ball1->setMaterial(ball1_materail); 41 | ball1->setTransformation(scale(5, 5, 5) * translation(0, 0, 10)); 42 | 43 | std::vector> shapes = { ball1 }; 44 | World world{light, shapes}; 45 | 46 | run_timed("uv_mapping", [&camera, &world] { 47 | auto canvas = camera.render(world); 48 | std::ofstream out("uv_mapping.ppm"); 49 | out << canvas; 50 | out.close(); 51 | }); 52 | } -------------------------------------------------------------------------------- /apps/timer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * timer.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | /// Run a lambda inside of a timer, and print the time in ms. 14 | template 15 | void run_timed(const std::string &name, f func, std::ostream &out = std::cout) { 16 | out << "Beginning " << name << '\n'; 17 | const auto start = std::chrono::system_clock::now(); 18 | func(); 19 | const auto stop = std::chrono::system_clock::now(); 20 | out << "Completed: " << (stop - start).count() / 1e9 << " seconds.\n"; 21 | } 22 | -------------------------------------------------------------------------------- /output/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sraaphorst/raytracer-cpp/166074c8986d58a6f1237aa68b93eccbff0eb677/output/.DS_Store -------------------------------------------------------------------------------- /output/cylinders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sraaphorst/raytracer-cpp/166074c8986d58a6f1237aa68b93eccbff0eb677/output/cylinders.png -------------------------------------------------------------------------------- /output/reflect_refract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sraaphorst/raytracer-cpp/166074c8986d58a6f1237aa68b93eccbff0eb677/output/reflect_refract.png -------------------------------------------------------------------------------- /output/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sraaphorst/raytracer-cpp/166074c8986d58a6f1237aa68b93eccbff0eb677/output/table.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt 2 | # By Sebastian Raaphorst, 2018. 3 | 4 | project(raytracer VERSION 0.1.0 LANGUAGES CXX) 5 | 6 | include_directories( 7 | impl 8 | shapes 9 | patterns 10 | ) 11 | 12 | add_library(raytracer SHARED 13 | camera.h 14 | camera.cpp 15 | canvas.h 16 | canvas.cpp 17 | common.h 18 | constmath.h 19 | material.h 20 | material.cpp 21 | matrix.h 22 | pointlight.h 23 | transformers.h 24 | vec.h 25 | world.h 26 | world.cpp 27 | 28 | impl/bounding_box.h 29 | impl/bounding_box.cpp 30 | impl/cube_functions.h 31 | impl/cube_functions.cpp 32 | impl/hit.cpp 33 | impl/instance_manager.cpp 34 | impl/intersection.cpp 35 | impl/ray.cpp 36 | 37 | shapes/cone.cpp 38 | shapes/cube.cpp 39 | shapes/cylinder.cpp 40 | shapes/group.cpp 41 | shapes/plane.cpp 42 | shapes/shape.cpp 43 | shapes/sphere.cpp 44 | 45 | patterns/checkerpattern.h 46 | patterns/checkerpattern.cpp 47 | patterns/gradientpattern.h 48 | patterns/gradientpattern.cpp 49 | patterns/pattern.h 50 | patterns/pattern.cpp 51 | patterns/ringpattern.h 52 | patterns/ringpattern.cpp 53 | patterns/solidpattern.h 54 | patterns/solidpattern.cpp 55 | patterns/stripepattern.h 56 | patterns/stripepattern.cpp 57 | ) 58 | -------------------------------------------------------------------------------- /src/affine_transform.h: -------------------------------------------------------------------------------- 1 | /** 2 | * affine_transform.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "matrix.h" 12 | #include "vec.h" 13 | 14 | namespace raytracer { 15 | using Transformation = SquareMatrix<4>; 16 | 17 | constexpr Transformation notransform() { 18 | return predefined_matrices::I; 19 | } 20 | 21 | constexpr Transformation translation(double x, double y, double z) { 22 | std::array point{x, y, z}; 23 | 24 | Transformation::matrix_type m{}; 25 | for (size_t i = 0; i < 4; ++i) 26 | for (size_t j = 0; j < 4; ++j) 27 | if (i == j) 28 | m[i][j] = 1; 29 | else if (j == 3) 30 | m[i][3] = point[i]; 31 | else 32 | m[i][j] = 0; 33 | return Transformation{m}; 34 | } 35 | 36 | constexpr Transformation scale(double x, double y, double z) { 37 | Transformation::matrix_type m{}; 38 | for (size_t i = 0; i < 4; ++i) 39 | for (size_t j = 0; j < 4; ++j) 40 | m[i][j] = 0; 41 | m[0][0] = x; 42 | m[1][1] = y; 43 | m[2][2] = z; 44 | m[3][3] = 1; 45 | return Transformation{m}; 46 | } 47 | 48 | constexpr Transformation rotation_x(double theta) { 49 | Transformation::matrix_type m{}; 50 | for (size_t i = 0; i < 4; ++i) 51 | for (size_t j = 0; j < 4; ++j) 52 | if ((i == 1 && j == 1) || (i == 2 && j == 2)) 53 | m[i][j] = const_cos(theta); 54 | else if (i == 1 && j == 2) 55 | m[1][2] = -const_sin(theta); 56 | else if (i == 2 && j == 1) 57 | m[2][1] = const_sin(theta); 58 | else if (i == j) 59 | m[i][j] = 1; 60 | else 61 | m[i][j] = 0; 62 | return Transformation{m}; 63 | } 64 | 65 | constexpr Transformation rotation_y(double theta) { 66 | Transformation::matrix_type m{}; 67 | for (size_t i = 0; i < 4; ++i) 68 | for (size_t j = 0; j < 4; ++j) 69 | if ((i == 0 && j == 0) || (i == 2 && j == 2)) 70 | m[i][j] = const_cos(theta); 71 | else if (i == 0 && j == 2) 72 | m[i][j] = const_sin(theta); 73 | else if (i == 2 && j == 0) 74 | m[i][j] = -const_sin(theta); 75 | else if (i == j) 76 | m[i][j] = 1; 77 | else m[i][j] = 0; 78 | return Transformation{m}; 79 | } 80 | 81 | constexpr Transformation rotation_z(double theta) { 82 | Transformation::matrix_type m{}; 83 | for (size_t i = 0; i < 4; ++i) 84 | for (size_t j = 0; j < 4; ++j) 85 | if ((i == 0 && j == 0) || (i == 1 && j == 1)) 86 | m[i][j] = const_cos(theta); 87 | else if (i == 0 && j == 1) 88 | m[i][j] = -const_sin(theta); 89 | else if (i == 1 && j == 0) 90 | m[i][j] = const_sin(theta); 91 | else if (i == j) 92 | m[i][j] = 1; 93 | else m[i][j] = 0; 94 | return Transformation{m}; 95 | } 96 | 97 | constexpr Transformation skew(double x_y, double x_z, double y_x, double y_z, double z_x, double z_y) { 98 | Transformation::matrix_type m{}; 99 | for (size_t i = 0; i < 4; ++i) 100 | for (size_t j = 0; j < 4; ++j) 101 | if (i == 0 && j == 1) 102 | m[i][j] = x_y; 103 | else if (i == 0 && j == 2) 104 | m[i][j] = x_z; 105 | else if (i == 1 && j == 0) 106 | m[i][j] = y_x; 107 | else if (i == 1 && j == 2) 108 | m[i][j] = y_z; 109 | else if (i == 2 && j == 0) 110 | m[i][j] = z_x; 111 | else if (i == 2 && j == 1) 112 | m[i][j] = z_y; 113 | else if (i == j) 114 | m[i][j] = 1; 115 | else m[i][j] = 0; 116 | return Transformation{m}; 117 | } 118 | 119 | constexpr Transformation view_transform(const Tuple &from, const Tuple &to, const Tuple &upv) { 120 | Transformation::matrix_type m{}; 121 | 122 | const auto forward = (to - from).normalize(); 123 | const auto upn = upv.normalize(); 124 | const auto left = forward.cross_product(upn); 125 | const auto true_up = left.cross_product(forward); 126 | 127 | // Set the rightmost column and bottom-most row to (0, 0, 0, 1). 128 | for (size_t i = 0; i < 3; ++i) { 129 | m[i][3] = 0; 130 | m[3][i] = 0; 131 | } 132 | m[3][3] = 1; 133 | 134 | // Set the rest of the matrix. 135 | for (size_t i = 0; i < 3; ++i) { 136 | m[0][i] = left[i]; 137 | m[1][i] = true_up[i]; 138 | m[2][i] = -forward[i]; 139 | } 140 | 141 | const auto trans = translation(-from[0], -from[1], -from[2]); 142 | const auto transm = Transformation{m}; 143 | const auto result = transm * trans; 144 | return Transformation{m} * translation(-from[0], -from[1], -from[2]); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/camera.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * camera.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | //#include 8 | 9 | #include "camera.h" 10 | #include "canvas.h" 11 | #include "common.h" 12 | #include "constmath.h" 13 | #include "ray.h" 14 | #include "vec.h" 15 | #include "world.h" 16 | 17 | using namespace raytracer::impl; 18 | 19 | namespace raytracer { 20 | Camera::Camera(size_t hsize, size_t vsize, double fov, const Transformation &transformation) : 21 | hsize{hsize}, vsize{vsize}, fov{fov}, transformation{transformation} { 22 | const auto half_view = const_tan(fov / 2); 23 | const auto aspect = (double) hsize / vsize; 24 | if (aspect >= 1) { 25 | half_width = half_view; 26 | half_height = half_view / aspect; 27 | } else { 28 | half_width = half_view * aspect; 29 | half_height = half_view; 30 | } 31 | 32 | pixel_size = (half_width * 2) / hsize; 33 | } 34 | 35 | bool Camera::operator==(const Camera &other) const noexcept { 36 | return hsize == other.hsize 37 | && vsize == other.vsize 38 | && ALMOST_EQUALS(fov, other.fov) 39 | && transformation == other.transformation; 40 | } 41 | 42 | size_t Camera::getHSize() const noexcept { 43 | return hsize; 44 | } 45 | 46 | size_t Camera::getVSize() const noexcept { 47 | return vsize; 48 | } 49 | 50 | double Camera::getFOV() const noexcept { 51 | return fov; 52 | } 53 | 54 | const Transformation &Camera::getTransformation() const noexcept { 55 | return transformation; 56 | } 57 | 58 | double Camera::getHalfWidth() const noexcept { 59 | return half_width; 60 | } 61 | 62 | double Camera::getHalfHeight() const noexcept { 63 | return half_height; 64 | } 65 | 66 | double Camera::getPixelSize() const noexcept { 67 | return pixel_size; 68 | } 69 | 70 | const Ray Camera::ray_for_pixel(double px, double py) const noexcept { 71 | const auto xoffset = (px + 0.5) * pixel_size; 72 | const auto yoffset = (py + 0.5) * pixel_size; 73 | 74 | const auto world_x = half_width - xoffset; 75 | const auto world_y = half_height - yoffset; 76 | 77 | const auto inv = transformation.invert(); 78 | const auto pixel = inv * make_point(world_x, world_y, -1); 79 | const auto origin = inv * make_point(0, 0, 0); 80 | 81 | const auto direction = (pixel - origin).normalize(); 82 | return Ray{origin, direction}; 83 | } 84 | 85 | const Canvas Camera::render(const World &w) const noexcept { 86 | auto image = Canvas{hsize, vsize}; 87 | 88 | //#pragma omp parallel for 89 | for (size_t y = 0; y < vsize; ++y) { 90 | //#pragma omp parallel for 91 | for (size_t x = 0; x < hsize; ++x) { 92 | const auto ray = ray_for_pixel(x, y); 93 | const auto colour = w.colourAt(ray); 94 | image[x][y] = colour; 95 | } 96 | } 97 | return image; 98 | } 99 | } -------------------------------------------------------------------------------- /src/camera.h: -------------------------------------------------------------------------------- 1 | /** 2 | * camera.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "affine_transform.h" 10 | 11 | namespace raytracer::impl { 12 | class Ray; 13 | } 14 | 15 | namespace raytracer { 16 | class Canvas; 17 | class World; 18 | 19 | class Camera final { 20 | size_t hsize; 21 | size_t vsize; 22 | double fov; 23 | Transformation transformation; 24 | double half_width; 25 | double half_height; 26 | double pixel_size; 27 | 28 | public: 29 | Camera(size_t hsize, size_t vsize, double fov, 30 | const Transformation &transformation = predefined_matrices::I); 31 | Camera(const Camera&) = default; 32 | Camera(Camera&&) = default; 33 | Camera &operator=(const Camera&) = default; 34 | 35 | bool operator==(const Camera&) const noexcept; 36 | 37 | size_t getHSize() const noexcept; 38 | size_t getVSize() const noexcept; 39 | double getFOV() const noexcept; 40 | const Transformation &getTransformation() const noexcept; 41 | 42 | double getHalfWidth() const noexcept; 43 | double getHalfHeight() const noexcept; 44 | double getPixelSize() const noexcept; 45 | 46 | const impl::Ray ray_for_pixel(double px, double py) const noexcept; 47 | 48 | const Canvas render(const World &w) const noexcept; 49 | }; 50 | } 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/canvas.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * canvas.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include "canvas.h" 8 | 9 | namespace raytracer { 10 | Canvas::Canvas(size_t width, size_t height): width{width}, height{height}, 11 | pixels{width, std::vector{height, predefined_colours::black}} {} 12 | } -------------------------------------------------------------------------------- /src/canvas.h: -------------------------------------------------------------------------------- 1 | /** 2 | * canvas.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "constmath.h" 16 | #include "vec.h" 17 | 18 | namespace raytracer { 19 | class Canvas final { 20 | const size_t width; 21 | const size_t height; 22 | 23 | using col = std::vector; 24 | using grid = std::vector; 25 | 26 | /// Use shared_ptrs so that we can reuse colours, like black on initialization. 27 | grid pixels; 28 | 29 | public: 30 | Canvas(size_t width, size_t height); 31 | 32 | /// This is mutable, so allow access to contents through indexing. 33 | col& operator[](const int idx) { 34 | return pixels[idx]; 35 | } 36 | 37 | const col& operator[](const int idx) const { 38 | return pixels[idx]; 39 | } 40 | 41 | size_t getWidth() const noexcept { 42 | return width; 43 | } 44 | 45 | size_t getHeight() const noexcept { 46 | return height; 47 | } 48 | 49 | /// Create a stream representing this as a PPM file. 50 | friend std::ostream &operator<<(std::ostream &ostr, const Canvas &c) { 51 | ostr << "P3\n" << c.width << ' ' << c.height << '\n' << colour_constants::maxvalue << '\n'; 52 | 53 | int linewidth = 0; 54 | for (size_t j=0; j < c.height; ++j) { 55 | bool first = true; 56 | 57 | for (size_t i=0; i < c.width; ++i) { 58 | for (auto rgb = 0; rgb < 3; ++rgb) { 59 | auto cval = lround(c[i][j][rgb] * colour_constants::maxvalue); 60 | auto val = std::max(0, std::min(cval, colour_constants::maxvalue)); 61 | 62 | // Constrain lines to 70 characters as per PPM specifications. 63 | auto valwidth = const_numDigits(val); 64 | if (linewidth + 1 + valwidth > 70) { 65 | ostr << '\n'; 66 | linewidth = 0; 67 | first = true; 68 | } 69 | 70 | // Separate by spaces. 71 | if (first) 72 | first = false; 73 | else { 74 | ostr << ' '; 75 | ++linewidth; 76 | } 77 | 78 | ostr << val; 79 | linewidth += valwidth; 80 | } 81 | } 82 | 83 | // We always want a newline at the end of the output as per PPM specification. 84 | ostr << '\n'; 85 | linewidth = 0; 86 | } 87 | return ostr; 88 | } 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * common.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "constmath.h" 14 | 15 | #define EPSILON 1e-4 16 | 17 | namespace raytracer { 18 | template 19 | constexpr typename std::enable_if_t || std::is_floating_point_v, bool> 20 | ALMOST_EQUALS(S x, T y) { 21 | // We need special treatment for infinities here. 22 | if (x == std::numeric_limits::infinity() && y == std::numeric_limits::infinity()) 23 | return true; 24 | if (x == -std::numeric_limits::infinity() && y == -std::numeric_limits::infinity()) 25 | return true; 26 | return (const_absd(x - y) < EPSILON); 27 | } 28 | 29 | template 30 | constexpr typename std::enable_if_t || std::is_floating_point_v, bool> 31 | GREATER_THAN(S x, T y) { 32 | return x >= y - EPSILON; 33 | } 34 | 35 | template 36 | constexpr typename std::enable_if_t || std::is_floating_point_v, bool> 37 | LESS_THAN(S x, T y) { 38 | 39 | return x <= y + EPSILON; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/constmath.h: -------------------------------------------------------------------------------- 1 | /** 2 | * constmath.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace raytracer { 13 | namespace math_constants { 14 | /// Trigonometric types. 15 | template constexpr T pi = 3.14159265358979323846264338327L; 16 | template constexpr T two_pi = 2 * pi; 17 | template constexpr T pi_by_two = pi / 2; 18 | template constexpr T pi_by_three = pi / 3; 19 | template constexpr T pi_by_four = pi / 4; 20 | template constexpr T pi_by_six = pi / 6; 21 | template constexpr T inf = std::numeric_limits::infinity(); 22 | template constexpr T ninf = -std::numeric_limits::infinity(); 23 | } 24 | 25 | namespace math_details { 26 | /// Calculates factorials. 27 | constexpr size_t factorial_helper(size_t sz, size_t accum) { 28 | if (sz == 0) 29 | return accum; 30 | else 31 | return factorial_helper(sz-1, sz * accum); 32 | } 33 | 34 | /// max factorial methods: could not figure out how to make this C++14 constexpr with loops, so had to revert 35 | /// to C++11 struct technique. 36 | template 37 | struct MaxFactorial { 38 | static constexpr size_t value() { 39 | if (i > 1 && val - i * val < val) return i - 1; 40 | else return MaxFactorial::value(); 41 | } 42 | }; 43 | template 44 | struct MaxFactorial { 45 | static constexpr size_t value() { return 2000; } 46 | }; 47 | 48 | /// sqrt methods. 49 | double constexpr sqrtNewtonRaphson(double x, double curr, double prev) { 50 | return curr == prev 51 | ? curr 52 | : sqrtNewtonRaphson(x, 0.5 * (curr + x / curr), curr); 53 | } 54 | } 55 | 56 | /// constexpr sqrt of a double. 57 | double constexpr const_sqrtd(double x) { 58 | return x >= 0 && x < std::numeric_limits::infinity() 59 | ? math_details::sqrtNewtonRaphson(x, x, 0) 60 | : std::numeric_limits::quiet_NaN(); 61 | } 62 | 63 | /// constexpr absolute value of a double. 64 | constexpr double const_absd(const double d) { 65 | return d >= 0 ? d : -d; 66 | } 67 | 68 | /// max value 69 | constexpr double const_maxd(const double d1, const double d2) { 70 | return d1 > d2 ? d1 : d2; 71 | } 72 | 73 | /// min value 74 | constexpr double const_mind(const double d1, const double d2) { 75 | return d1 < d2 ? d1 : d2; 76 | } 77 | 78 | /// constexpr calculate the number of digits. 79 | template 80 | constexpr std::enable_if_t, T> 81 | const_numDigits(T number) 82 | { 83 | int digits = 0; 84 | if (number < 0) digits = 1; 85 | while (number) { 86 | number /= 10; 87 | digits++; 88 | } 89 | return digits; 90 | } 91 | 92 | /// Calculate n! 93 | constexpr inline long double const_factorial(size_t const &n) { 94 | return math_details::factorial_helper(n, 1); 95 | } 96 | 97 | /// Return the largest factorial we can calculate. 98 | constexpr size_t const_max_factorial() { 99 | return math_details::MaxFactorial<1,1>::value(); 100 | } 101 | 102 | template 103 | constexpr double const_normalize_radians(T val) { 104 | while (val < -math_constants::pi) val += math_constants::two_pi; 105 | while (val > math_constants::pi) val -= math_constants::two_pi; 106 | return val; 107 | } 108 | 109 | /// Taylor polynomial approximation of normalized value in [-pi, pi] with 8 terms. 110 | template 111 | constexpr std::enable_if_t, T> 112 | const_sin(T value) { 113 | const T x = const_normalize_radians(value); 114 | 115 | T numerator = x; 116 | T result = 0; 117 | for (size_t i = 0; i < 8; ++i) { 118 | result += numerator / const_factorial(2 * i + 1); 119 | numerator *= -1 * x * x; 120 | } 121 | return result; 122 | } 123 | 124 | template 125 | constexpr std::enable_if_t, T> 126 | const_cos(T value) { 127 | const T x = const_normalize_radians(value); 128 | 129 | T numerator = 1; 130 | T result = 0; 131 | for (size_t i = 0; i < 8; ++i) { 132 | result += numerator / const_factorial(2 * i); 133 | numerator *= -1 * x * x; 134 | } 135 | return result; 136 | } 137 | 138 | template 139 | constexpr std::enable_if_t, T> 140 | const_tan(T value) { 141 | return const_sin(value) / const_cos(value); 142 | } 143 | 144 | template 145 | constexpr std::enable_if_t, T> 146 | const_deg2rad(T deg) { 147 | return const_normalize_radians((deg * math_constants::pi) / 180); 148 | } 149 | 150 | namespace math_constants { 151 | constexpr double sqrt2 = const_sqrtd(2.0); 152 | constexpr double sqrt2_by_2 = sqrt2 / 2; 153 | } 154 | } -------------------------------------------------------------------------------- /src/impl/bounding_box.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * bounding_box.cpp 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #include 8 | 9 | #include "affine_transform.h" 10 | #include "cube_functions.h" 11 | #include "ray.h" 12 | #include "vec.h" 13 | #include "bounding_box.h" 14 | 15 | namespace raytracer::impl { 16 | void BoundingBox::addPoint(const Tuple &point) noexcept { 17 | const auto minx = point[tuple_constants::x] < min_point[tuple_constants::x] 18 | ? point[tuple_constants::x] : min_point[tuple_constants::x]; 19 | const auto miny = point[tuple_constants::y] < min_point[tuple_constants::y] 20 | ? point[tuple_constants::y] : min_point[tuple_constants::y]; 21 | const auto minz = point[tuple_constants::z] < min_point[tuple_constants::z] 22 | ? point[tuple_constants::z] : min_point[tuple_constants::z]; 23 | 24 | const auto maxx = point[tuple_constants::x] > max_point[tuple_constants::x] 25 | ? point[tuple_constants::x] : max_point[tuple_constants::x]; 26 | const auto maxy = point[tuple_constants::y] > max_point[tuple_constants::y] 27 | ? point[tuple_constants::y] : max_point[tuple_constants::y]; 28 | const auto maxz = point[tuple_constants::z] > max_point[tuple_constants::z] 29 | ? point[tuple_constants::z] : max_point[tuple_constants::z]; 30 | 31 | min_point = make_point(minx, miny, minz); 32 | max_point = make_point(maxx, maxy, maxz); 33 | } 34 | 35 | void BoundingBox::addBox(const BoundingBox &box) noexcept { 36 | addPoint(box.getMinPoint()); 37 | addPoint(box.getMaxPoint()); 38 | } 39 | 40 | bool BoundingBox::containsPoint(const Tuple &point) const noexcept { 41 | const auto x = point[tuple_constants::x]; 42 | const auto y = point[tuple_constants::y]; 43 | const auto z = point[tuple_constants::z]; 44 | return 45 | min_point[tuple_constants::x] <= x && x <= max_point[tuple_constants::x] && 46 | min_point[tuple_constants::y] <= y && y <= max_point[tuple_constants::y] && 47 | min_point[tuple_constants::z] <= z && z <= max_point[tuple_constants::z]; 48 | 49 | } 50 | 51 | bool BoundingBox::containsBox(const BoundingBox &other) const noexcept { 52 | return containsPoint(other.getMinPoint()) && containsPoint(other.getMaxPoint()); 53 | } 54 | 55 | BoundingBox BoundingBox::transform(const Transformation &trans) const noexcept { 56 | BoundingBox box{}; 57 | box.addPoint(trans * min_point); 58 | box.addPoint(trans * make_point(min_point[tuple_constants::x], min_point[tuple_constants::y], max_point[tuple_constants::z])); 59 | box.addPoint(trans * make_point(min_point[tuple_constants::x], max_point[tuple_constants::y], min_point[tuple_constants::z])); 60 | box.addPoint(trans * make_point(min_point[tuple_constants::x], max_point[tuple_constants::y], max_point[tuple_constants::z])); 61 | box.addPoint(trans * make_point(max_point[tuple_constants::x], min_point[tuple_constants::y], min_point[tuple_constants::z])); 62 | box.addPoint(trans * make_point(max_point[tuple_constants::x], min_point[tuple_constants::y], max_point[tuple_constants::z])); 63 | box.addPoint(trans * make_point(max_point[tuple_constants::x], max_point[tuple_constants::y], min_point[tuple_constants::z])); 64 | box.addPoint(trans * max_point); 65 | 66 | return box; 67 | } 68 | 69 | bool BoundingBox::intersects(const Ray &ray) const noexcept { 70 | std::vector mins; 71 | std::vector maxs; 72 | 73 | for (size_t i = 0; i < 3; ++i) { 74 | const auto [vmin, vmax] = checkAxis(ray.getOrigin()[i], ray.getDirection()[i], min_point[i], max_point[i]); 75 | mins.emplace_back(vmin); 76 | maxs.emplace_back(vmax); 77 | } 78 | 79 | const auto tmin = *std::max_element(std::cbegin(mins), std::cend(mins)); 80 | const auto tmax = *std::min_element(std::cbegin(maxs), std::cend(maxs)); 81 | return tmin <= tmax; 82 | } 83 | } -------------------------------------------------------------------------------- /src/impl/bounding_box.h: -------------------------------------------------------------------------------- 1 | /** 2 | * bounding_box.h 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "affine_transform.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer::impl { 13 | class Ray; 14 | 15 | class BoundingBox final { 16 | Tuple min_point; 17 | Tuple max_point; 18 | 19 | public: 20 | /** 21 | * The initial bounding box is empty. 22 | */ 23 | constexpr BoundingBox() noexcept: 24 | min_point{predefined_tuples::min_point}, max_point{predefined_tuples::max_point} {}; 25 | 26 | constexpr BoundingBox(Tuple min_point, Tuple max_point) noexcept: 27 | min_point{min_point}, max_point{max_point} {} 28 | 29 | constexpr BoundingBox(const BoundingBox&) = default; 30 | constexpr BoundingBox(BoundingBox&&) = default; 31 | 32 | constexpr bool operator==(const BoundingBox &o) const noexcept { 33 | return min_point == o.min_point && max_point == o.max_point; 34 | } 35 | 36 | constexpr auto getMinPoint() const noexcept { 37 | return min_point; 38 | } 39 | constexpr auto getMaxPoint() const noexcept { 40 | return max_point; 41 | } 42 | 43 | /// Adds a point to a bounding box. 44 | void addPoint(const Tuple&) noexcept; 45 | 46 | /// Merges the given bounding box to the original. 47 | void addBox(const BoundingBox&) noexcept; 48 | 49 | /// Determines if the bounding box contains a point. 50 | bool containsPoint(const Tuple&) const noexcept; 51 | 52 | /// Determines if the bounding box contains another. 53 | bool containsBox(const BoundingBox&) const noexcept; 54 | 55 | /** 56 | * Apply a transformation to this bounding box to get another bounding box 57 | * that contains all eight transformed points of the original box. 58 | */ 59 | BoundingBox transform(const Transformation&) const noexcept; 60 | 61 | /// Determine if a ray intersects the box. 62 | bool intersects(const Ray&) const noexcept; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/impl/cube_functions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cube_functions.cpp 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #include 8 | 9 | #include "common.h" 10 | #include "constmath.h" 11 | #include "cube_functions.h" 12 | 13 | namespace raytracer::impl { 14 | std::pair checkAxis(double origin, double direction, double min_extent, double max_extent) { 15 | const auto tmin_numerator = min_extent - origin; 16 | const auto tmax_numerator = max_extent - origin; 17 | 18 | // If the direction is close to 0, assume parallel, in which case, no intersection. 19 | if (ALMOST_EQUALS(direction, 0)) 20 | return {tmin_numerator * math_constants::inf<>, 21 | tmax_numerator * math_constants::inf<>}; 22 | 23 | auto tmin = tmin_numerator / direction; 24 | auto tmax = tmax_numerator / direction; 25 | if (tmin > tmax) 26 | std::swap(tmin, tmax); 27 | return {tmin, tmax}; 28 | } 29 | } -------------------------------------------------------------------------------- /src/impl/cube_functions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * cube_functions.h 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace raytracer::impl { 12 | std::pair checkAxis(double origin, double direction, double min_extent, double max_extent); 13 | } 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/impl/hit.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * hit.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "intersection.h" 12 | #include "hit.h" 13 | 14 | using namespace raytracer; 15 | 16 | namespace raytracer::impl { 17 | Hit::Hit(const Intersection &i, 18 | const Tuple &point, 19 | const Tuple &under_point, 20 | const Tuple &eyev, 21 | const Tuple &normalv, 22 | const Tuple &reflectv, 23 | bool inside, double n1, double n2): 24 | Intersection{i}, 25 | point{point}, 26 | under_point{under_point}, 27 | eyev{eyev}, 28 | normalv{normalv}, 29 | reflectv{reflectv}, 30 | inside{inside}, n1{n1}, n2{n2} {} 31 | 32 | const Tuple &Hit::getPoint() const noexcept { 33 | return point; 34 | } 35 | 36 | const Tuple &Hit::getUnderPoint() const noexcept { 37 | return under_point; 38 | } 39 | 40 | const Tuple &Hit::getEyeVector() const noexcept { 41 | return eyev; 42 | } 43 | 44 | const Tuple &Hit::getNormalVector() const noexcept { 45 | return normalv; 46 | } 47 | 48 | const Tuple &Hit::getReflectVector() const noexcept { 49 | return reflectv; 50 | } 51 | 52 | double Hit::getN1() const noexcept { 53 | return n1; 54 | } 55 | 56 | double Hit::getN2() const noexcept { 57 | return n2; 58 | } 59 | 60 | bool Hit::isInside() const noexcept { 61 | return inside; 62 | } 63 | 64 | double Hit::schlick() const noexcept { 65 | auto cos = eyev.dot_product(normalv); 66 | 67 | // Total internal reflection can only occur if n1 > n2. 68 | if (n1 > n2) { 69 | const auto n = n1 / n2; 70 | const auto sin2_t = n * n * (1 - cos * cos); 71 | if (sin2_t > 1) 72 | return 1; 73 | 74 | // Use cost(theta_t) instead. 75 | cos = std::sqrt(1 - sin2_t); 76 | } 77 | 78 | const auto rcalc = (n1 - n2) / (n1 + n2); 79 | const auto r0 = rcalc * rcalc; 80 | const auto mcos = 1 - cos; 81 | const auto mcos2 = mcos * mcos; 82 | 83 | // Yes, really: mcos^5. 84 | return r0 + (1 - r0) * mcos2 * mcos2 * mcos; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/impl/hit.h: -------------------------------------------------------------------------------- 1 | /** 2 | * hit.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include "intersection.h" 11 | 12 | namespace raytracer::impl { 13 | /** 14 | * A Hit is a subclass of \see{Intersection} that contains additional information: 15 | * 1. The point in world-space where the intersection occurred, adjusted slightly to prevent acne. 16 | * 2. The "under point", i.e. the point adjusted slightly in the opposite direction for reflection. 17 | * 3. The eye vector (pointing back to the camera / eye). 18 | * 4. The normal vector of the \see{Shape} at the point. 19 | * 5. A vector indicating the reflection effect. 20 | * 6. Whether or not the intersection occurs inside the object (in which case, the normal is inverted to 21 | * illuminate the surface properly). 22 | */ 23 | class Hit final: public Intersection { 24 | const Tuple point; 25 | const Tuple under_point; 26 | const Tuple eyev; 27 | const Tuple normalv; 28 | const Tuple reflectv; 29 | const bool inside; 30 | const double n1; 31 | const double n2; 32 | 33 | public: 34 | Hit() = delete; 35 | 36 | Hit(const Intersection &i, 37 | const Tuple &point, 38 | const Tuple &under_point, 39 | const Tuple &eyev, 40 | const Tuple &normalv, 41 | const Tuple &reflectv, 42 | bool inside, double n1, double n2); 43 | 44 | Hit(const Hit&) = default; 45 | Hit(Hit&&) = default; 46 | 47 | const Tuple &getPoint() const noexcept; 48 | const Tuple &getUnderPoint() const noexcept; 49 | const Tuple &getEyeVector() const noexcept; 50 | const Tuple &getNormalVector() const noexcept; 51 | const Tuple &getReflectVector() const noexcept; 52 | double getN1() const noexcept; 53 | double getN2() const noexcept; 54 | bool isInside() const noexcept; 55 | 56 | /** 57 | * Calculate the Fresnel effect using the Schlick approximation. 58 | * This returns the reflectance, a number in [0,1] that represents the fraction of light that should be 59 | * reflected, given the surface information. 60 | */ 61 | double schlick() const noexcept; 62 | }; 63 | } -------------------------------------------------------------------------------- /src/impl/instance_manager.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * InstanceManager.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "instance_manager.h" 11 | 12 | namespace raytracer::impl { 13 | std::vector> InstanceManager::ptrs; 14 | 15 | InstanceManager::InstanceManager(dummy) {} 16 | } -------------------------------------------------------------------------------- /src/impl/instance_manager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * instance_manager.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace raytracer::impl { 13 | /** 14 | * This is a bit of a ghastly hack of class that manages a static list of shared_ptrs so that classes can 15 | * inherit from InstanceManager and then use factory methods to create shared_ptrs of themselves and register 16 | * them. Since calls to std::make_shared require public classes, we include a private dummy struct here that 17 | * constructors require so that they can be public but uninstantiable outside of the factory method. 18 | * 19 | * This is also so that they can inherit from std::enable_shared_from_this, and then create shared_ptrs with 20 | * shared_from_this. 21 | * 22 | * For an example of this, see Shape. 23 | */ 24 | class InstanceManager { 25 | private: 26 | static std::vector> ptrs; 27 | 28 | protected: 29 | struct dummy {}; 30 | 31 | template 32 | static void registerInstance(std::shared_ptr &ptr) { 33 | ptrs.emplace_back(std::dynamic_pointer_cast(ptr)); 34 | } 35 | 36 | public: 37 | explicit InstanceManager(dummy); 38 | }; 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/impl/intersection.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * intersection.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "hit.h" 11 | #include "intersection.h" 12 | #include "ray.h" 13 | #include "shape.h" 14 | 15 | using namespace raytracer::shapes; 16 | 17 | namespace raytracer::impl { 18 | Intersection::Intersection(const double t, const std::shared_ptr &o) noexcept: t{t}, o{o} {} 19 | 20 | bool Intersection::operator==(const Intersection &other) const noexcept { 21 | return typeid(*this) == typeid(other) 22 | && ALMOST_EQUALS(t, other.t) && *o == *other.o 23 | && doCompare(other); 24 | } 25 | 26 | bool Intersection::operator!=(const Intersection &other) const noexcept { 27 | return !(*this == other); 28 | } 29 | 30 | double Intersection::getT() const noexcept { 31 | return t; 32 | } 33 | 34 | const std::shared_ptr &Intersection::getObject() const noexcept { 35 | return o; 36 | } 37 | 38 | const std::optional Intersection::hit(const std::vector &ints) noexcept { 39 | if (ints.empty()) 40 | return std::nullopt; 41 | 42 | Intersection const *curr = nullptr; 43 | for (const Intersection &i: ints) { 44 | if (i.getT() > 0 && (curr == nullptr || i.getT() < curr->getT())) 45 | curr = &i; 46 | } 47 | 48 | if (curr == nullptr) return std::nullopt; 49 | return {*curr}; 50 | } 51 | 52 | const std::optional Intersection::prepareHit(const std::optional &hit, 53 | const Ray &ray, 54 | const std::vector &xs) noexcept { 55 | if (!hit.has_value()) 56 | return {}; 57 | return prepareHit(hit.value(), ray, xs); 58 | } 59 | 60 | const Hit Intersection::prepareHit(const Intersection &hit, 61 | const Ray &ray, 62 | const std::vector &xs) noexcept { 63 | const auto point = ray.position(hit.getT()); 64 | const auto eyev = -ray.getDirection(); 65 | const auto normalv = hit.getObject()->normalAt(point); 66 | const auto reflectv = ray.getDirection().reflect(normalv); 67 | const bool inside = normalv.dot_product(eyev) < 0; 68 | const auto adj_normalv = inside ? -normalv : normalv; /// 69 | 70 | // Slightly offset the point from the expected value to prevent acne. 71 | // 1e-4 is arbitrary and depends on the scale of the scene; it is sufficient unless dealing with very 72 | // small distances. 73 | const auto adjusted_point = point + adj_normalv * 1e-4; 74 | 75 | // This is where the refracted rays will originate. 76 | // As with adjusted_point, we have to slightly offset this, but in the opposite direction. 77 | const auto under_point = point - adj_normalv * 1e-4; 78 | 79 | double n1 = 0; 80 | double n2 = 0; 81 | std::vector> containers; 82 | for (const auto &x: xs) { 83 | if (x == hit) 84 | n1 = containers.empty() ? 1 : containers.back().get()->getMaterial()->getRefractiveIndex(); 85 | 86 | auto iter = std::find_if(std::begin(containers), std::end(containers), 87 | [&x](const auto &c) { return *c.get() == *x.getObject(); }); 88 | if (iter != std::end(containers)) 89 | containers.erase(iter); 90 | else 91 | containers.emplace_back(x.getObject()); 92 | 93 | if (x == hit) { 94 | n2 = containers.empty() ? 1 : containers.back().get()->getMaterial()->getRefractiveIndex(); 95 | break; 96 | } 97 | } 98 | 99 | return Hit{hit, adjusted_point, under_point, eyev, adj_normalv, reflectv, inside, n1, n2}; 100 | } 101 | 102 | bool Intersection::doCompare(const Intersection&) const noexcept { 103 | return true; 104 | } 105 | } -------------------------------------------------------------------------------- /src/impl/intersection.h: -------------------------------------------------------------------------------- 1 | /** 2 | * intersection.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "shape.h" 16 | #include "transformers.h" 17 | 18 | namespace raytracer::shapes { 19 | class Shape; 20 | } 21 | 22 | namespace raytracer::impl { 23 | class Hit; 24 | class Ray; 25 | 26 | /** 27 | * Defines the concept of an intersection, which is a \see{Ray} striking a \see{Shape}. 28 | * A \see{Ray} that strikes a \see{Shape} is called a \see{Hit}, which contains additional information about 29 | * the intersection. 30 | */ 31 | class Intersection { 32 | private: 33 | double t; 34 | 35 | // Intersections should not be allowed to modify their shapes. 36 | std::shared_ptr o; 37 | 38 | public: 39 | Intersection() = delete; 40 | 41 | /// Define an Intersection at time t with a \see{Shape}. 42 | Intersection(double t, const std::shared_ptr &o) noexcept; 43 | 44 | Intersection(const Intersection&) noexcept = default; 45 | Intersection(Intersection&&) noexcept = default; 46 | Intersection &operator=(const Intersection &other) noexcept = default; 47 | 48 | bool operator==(const Intersection &other) const noexcept; 49 | bool operator!=(const Intersection &other) const noexcept; 50 | 51 | double getT() const noexcept; 52 | const std::shared_ptr &getObject() const noexcept; 53 | 54 | /** 55 | * Static hit method. This takes a list of intersections, and determines the intersection that is visible, 56 | * i.e. the intersection with the lowest positive time value. 57 | * 58 | * @param xs the list of intersections 59 | * @return the intersection, or nullopt if no such intersection exists 60 | */ 61 | static const std::optional hit(const std::vector &xs) noexcept; 62 | 63 | /** 64 | * Given an optional intersection, create a fully-fledged \see{Hit} from it. 65 | * @param hit an optional intersection 66 | * @param ray the ray that generated the intersection 67 | * @param xs the list of all intersections for the ray 68 | * @return a Hit if the intersection is defined, and nullopt otherwise 69 | */ 70 | [[nodiscard]] static const std::optional prepareHit(const std::optional &hit, 71 | const Ray &ray, 72 | const std::vector &xs) noexcept; 73 | 74 | 75 | /** 76 | * Given an intersection, create a fully-fledged \see{Hit} from it. 77 | * @param hit the intersection 78 | * @param ray the ray that generated the intersection 79 | * @param xs the list of all intersections for the ray 80 | * @return a fully defined Hit 81 | */ 82 | [[nodiscard]] static const Hit prepareHit(const Intersection &hit, 83 | const Ray &ray, 84 | const std::vector &xs) noexcept; 85 | 86 | protected: 87 | /// Any additional implementation for equality comparison in subclasses should be implemented here. 88 | virtual bool doCompare(const Intersection &other) const noexcept; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/impl/ray.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ray.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ray.h" 14 | 15 | using namespace raytracer; 16 | using namespace raytracer::impl; 17 | 18 | namespace raytracer::impl { 19 | Ray::Ray() noexcept: origin{predefined_tuples::zero_point}, direction{predefined_tuples::zero_vector} {} 20 | 21 | Ray::Ray(const Tuple &origin, const Tuple &direction): origin{origin}, direction{direction} { 22 | if (!origin.isPoint()) 23 | throw std::invalid_argument("Ray requires a position for the first argument"); 24 | if (!direction.isVector()) 25 | throw std::invalid_argument("Ray requires a vector for the second argument"); 26 | } 27 | 28 | bool Ray::operator==(const Ray &other) const noexcept { 29 | return typeid(*this) == typeid(other) 30 | && origin == other.origin && direction == other.direction 31 | && doCompare(other); 32 | } 33 | 34 | bool Ray::operator!=(const Ray &other) const noexcept { 35 | return !(*this == other); 36 | } 37 | 38 | Tuple Ray::position(const double t) const noexcept { 39 | return origin + t * direction; 40 | } 41 | 42 | const Tuple &Ray::getOrigin() const noexcept { 43 | return origin; 44 | } 45 | 46 | const Tuple &Ray::getDirection() const noexcept { 47 | return direction; 48 | } 49 | 50 | const Ray Ray::transform(const Transformation &t) const { 51 | return Ray{t * origin, t * direction}; 52 | } 53 | 54 | bool Ray::doCompare(const Ray&) const noexcept { 55 | return true; 56 | } 57 | } -------------------------------------------------------------------------------- /src/impl/ray.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ray.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | #include "transformers.h" 13 | #include "vec.h" 14 | #include "affine_transform.h" 15 | 16 | namespace raytracer::impl { 17 | /// Defines the concept of a ray, which has a point of origin and a direction associated with it. 18 | class Ray final { 19 | private: 20 | Tuple origin; 21 | Tuple direction; 22 | 23 | public: 24 | /** 25 | * Create a Ray at the origin with no direction. 26 | */ 27 | Ray() noexcept; 28 | 29 | /** 30 | * Create a Ray at the specified origin with the given direction. 31 | * @param origin the origin of the ray 32 | * @param direction the direction of the ray 33 | */ 34 | Ray(const Tuple &origin, const Tuple &direction); 35 | 36 | Ray(const Ray&) noexcept = default; 37 | Ray(Ray&&) noexcept = default; 38 | Ray &operator=(const Ray&) = default; 39 | 40 | /// Determine if two rays are the same. 41 | bool operator==(const Ray&) const noexcept; 42 | 43 | /// Determine if two rays are different. 44 | bool operator!=(const Ray&) const noexcept; 45 | 46 | /// Determine the position of this ray at time t. 47 | Tuple position(double) const noexcept; 48 | 49 | const Tuple &getOrigin() const noexcept; 50 | const Tuple &getDirection() const noexcept; 51 | 52 | /// Create a new Ray by applying a transformation to this one. 53 | const Ray transform(const Transformation&) const; 54 | 55 | protected: 56 | /// Subclass comparison implementations should override this method. 57 | virtual bool doCompare(const Ray &other) const noexcept; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/material.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * material.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "material.h" 10 | #include "patterns/pattern.h" 11 | #include "pointlight.h" 12 | #include "shape.h" 13 | #include "patterns/solidpattern.h" 14 | #include "vec.h" 15 | 16 | using namespace raytracer::shapes; 17 | 18 | namespace raytracer { 19 | Material::Material() noexcept: 20 | pattern{std::make_shared(DEFAULT_COLOUR)}, 21 | ambient{DEFAULT_AMBIENT}, 22 | diffuse{DEFAULT_DIFFUSE}, 23 | specular{DEFAULT_SPECULAR}, 24 | shininess{DEFAULT_SHININESS}, 25 | reflectivity{DEFAULT_REFLECTIVITY}, 26 | transparency{DEFAULT_TRANSPARENCY}, 27 | refractive_index{DEFAULT_REFRACTIVE_INDEX} {} 28 | 29 | Material::Material(const Colour &colour, 30 | double ambient, 31 | double diffuse, 32 | double specular, 33 | double shininess, 34 | double reflectivity, 35 | double transparency, 36 | double refractive_index) noexcept: 37 | pattern{std::make_shared(colour)}, 38 | ambient{ambient}, 39 | diffuse{diffuse}, 40 | specular{specular}, 41 | shininess{shininess}, 42 | reflectivity{reflectivity}, 43 | transparency{transparency}, 44 | refractive_index{refractive_index} {} 45 | 46 | Material::Material(const std::shared_ptr &pattern, 47 | double ambient, 48 | double diffuse, 49 | double specular, 50 | double shininess, 51 | double reflectivity, 52 | double transparency, 53 | double refractive_index) noexcept: 54 | pattern{pattern}, 55 | ambient{ambient}, 56 | diffuse{diffuse}, 57 | specular{specular}, 58 | shininess{shininess}, 59 | reflectivity{reflectivity}, 60 | transparency{transparency}, 61 | refractive_index{refractive_index} {} 62 | 63 | bool Material::operator==(const Material &other) const { 64 | return *pattern == *other.pattern 65 | && ALMOST_EQUALS(ambient, other.ambient) 66 | && ALMOST_EQUALS(diffuse, other.diffuse) 67 | && ALMOST_EQUALS(specular, other.specular) 68 | && ALMOST_EQUALS(shininess, other.shininess) 69 | && ALMOST_EQUALS(reflectivity, other.reflectivity) 70 | && ALMOST_EQUALS(transparency, other.transparency) 71 | && ALMOST_EQUALS(refractive_index, other.refractive_index); 72 | } 73 | 74 | bool Material::operator!=(const Material &other) const noexcept { 75 | return !(*this == other); 76 | } 77 | 78 | const std::shared_ptr &Material::getPattern() const noexcept { 79 | return pattern; 80 | } 81 | double Material::getAmbient() const noexcept { 82 | return ambient; 83 | } 84 | double Material::getDiffuse() const noexcept { 85 | return diffuse; 86 | } 87 | double Material::getSpecular() const noexcept { 88 | return specular; 89 | } 90 | double Material::getShininess() const noexcept { 91 | return shininess; 92 | } 93 | double Material::getReflectivity() const noexcept { 94 | return reflectivity; 95 | } 96 | double Material::getTransparency() const noexcept { 97 | return transparency; 98 | } 99 | double Material::getRefractiveIndex() const noexcept { 100 | return refractive_index; 101 | } 102 | 103 | void Material::setPattern(const std::shared_ptr &p) noexcept { 104 | pattern = p; 105 | } 106 | void Material::setAmbient(double a) noexcept { 107 | ambient = a; 108 | } 109 | void Material::setDiffuse(double d) noexcept { 110 | diffuse = d; 111 | } 112 | void Material::setSpecular(double s) noexcept { 113 | specular = s; 114 | } 115 | void Material::setShininess(double s) noexcept { 116 | shininess = s; 117 | } 118 | void Material::setReflectivity(double r) noexcept { 119 | reflectivity = r; 120 | } 121 | void Material::setTransparency(double t) noexcept { 122 | transparency = t; 123 | } 124 | void Material::setRefractiveIndex(double r) noexcept { 125 | refractive_index = r; 126 | } 127 | 128 | Colour Material::lighting(const PointLight &light, 129 | const std::shared_ptr &shape, 130 | const Tuple &point, 131 | const Tuple &eyev, const Tuple &normalv, bool in_shadow) const noexcept { 132 | const auto colour = pattern->colourAtObject(shape, point); 133 | const auto effective_colour = colour * light.getIntensity(); 134 | const auto lightv = (light.getPosition() - point).normalize(); 135 | const auto ambient_component = ambient * effective_colour; 136 | 137 | // If the point is in shadow, we only use the ambient component. 138 | if (in_shadow) 139 | return ambient_component; 140 | 141 | const auto light_dot_normal = lightv.dot_product(normalv); 142 | 143 | const auto diffuse_component = (light_dot_normal < 0) 144 | ? predefined_colours::black 145 | : diffuse * effective_colour * light_dot_normal; 146 | 147 | const auto reflectv = -lightv.reflect(normalv); 148 | const auto reflect_dot_eye = std::pow(reflectv.dot_product(eyev), shininess); 149 | 150 | const auto specular_component = (light_dot_normal < 0 || reflect_dot_eye <= 0) 151 | ? predefined_colours::black 152 | : reflect_dot_eye * specular * light.getIntensity(); 153 | 154 | return ambient_component + diffuse_component + specular_component; 155 | } 156 | } -------------------------------------------------------------------------------- /src/material.h: -------------------------------------------------------------------------------- 1 | /** 2 | * material.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | /** 10 | * TODO:constexpr If we want to make this constexpr, we'll need to implement pow as a constexpr function. 11 | * 12 | * Taylor series expansion: 13 | * a^x = e^{x ln a} = 1 + x ln a / 1! + (x ln a)^2 / 2! + (x ln a)^3 / 3! + ... 14 | * 15 | * Wikipedia page on calculating ln: 16 | * https://en.wikipedia.org/wiki/Natural_logarithm#High_precision 17 | * 18 | * Elbeno constexpr math library: 19 | * https://github.com/elbeno/constexpr/blob/master/src/include/cx_math.h 20 | */ 21 | #include 22 | #include 23 | 24 | #include "pointlight.h" 25 | #include "vec.h" 26 | 27 | namespace raytracer { 28 | class Pattern; 29 | 30 | namespace shapes { 31 | class Shape; 32 | } 33 | 34 | class Material final { 35 | private: 36 | std::shared_ptr pattern; 37 | double ambient; 38 | double diffuse; 39 | double specular; 40 | double shininess; 41 | double reflectivity; 42 | double transparency; 43 | double refractive_index; 44 | 45 | public: 46 | /// Default material is just solid white. 47 | Material() noexcept; 48 | 49 | /// Keep this for backward compatibility, and convert the colour into a SolidPattern. 50 | Material(const Colour &colour, 51 | double = DEFAULT_AMBIENT, 52 | double = DEFAULT_DIFFUSE, 53 | double = DEFAULT_SPECULAR, 54 | double = DEFAULT_SHININESS, 55 | double = DEFAULT_REFLECTIVITY, 56 | double = DEFAULT_TRANSPARENCY, 57 | double = DEFAULT_REFRACTIVE_INDEX) noexcept; 58 | 59 | Material(const std::shared_ptr &pattern, 60 | double = DEFAULT_AMBIENT, 61 | double = DEFAULT_DIFFUSE, 62 | double = DEFAULT_SPECULAR, 63 | double = DEFAULT_SHININESS, 64 | double = DEFAULT_REFLECTIVITY, 65 | double = DEFAULT_TRANSPARENCY, 66 | double = DEFAULT_REFRACTIVE_INDEX) noexcept; 67 | 68 | Material(const Material&) = default; 69 | Material(Material&&) = default; 70 | Material &operator=(const Material&) = default; 71 | Material &operator=(Material&&) = default; 72 | 73 | bool operator==(const Material &other) const; 74 | bool operator!=(const Material &other) const noexcept; 75 | 76 | const std::shared_ptr &getPattern() const noexcept; 77 | double getAmbient() const noexcept; 78 | double getDiffuse() const noexcept; 79 | double getSpecular() const noexcept; 80 | double getShininess() const noexcept; 81 | double getReflectivity() const noexcept; 82 | double getTransparency() const noexcept; 83 | double getRefractiveIndex() const noexcept; 84 | 85 | void setPattern(const std::shared_ptr&) noexcept; 86 | void setAmbient(double) noexcept; 87 | void setDiffuse(double) noexcept; 88 | void setSpecular(double) noexcept; 89 | void setShininess(double) noexcept; 90 | void setReflectivity(double) noexcept; 91 | void setTransparency(double) noexcept; 92 | void setRefractiveIndex(double) noexcept; 93 | 94 | 95 | constexpr static Colour DEFAULT_COLOUR = predefined_colours::white; 96 | constexpr static double DEFAULT_AMBIENT = 0.1; 97 | constexpr static double DEFAULT_DIFFUSE = 0.9; 98 | constexpr static double DEFAULT_SPECULAR = 0.9; 99 | constexpr static double DEFAULT_SHININESS = 200; 100 | constexpr static double DEFAULT_REFLECTIVITY = 0; 101 | constexpr static double DEFAULT_TRANSPARENCY = 0; 102 | constexpr static double DEFAULT_REFRACTIVE_INDEX = 1; 103 | 104 | // Calculate the lighting of this material. 105 | Colour lighting(const PointLight &light, 106 | const std::shared_ptr &shape, 107 | const Tuple &point, 108 | const Tuple &eyev, 109 | const Tuple &normalv, 110 | bool in_shadow) const noexcept; 111 | }; 112 | } -------------------------------------------------------------------------------- /src/patterns/checkerpattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * checkerpattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "checkerpattern.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer { 13 | CheckerPattern::CheckerPattern() noexcept: 14 | colour1{predefined_colours::white}, 15 | colour2{predefined_colours::black} {} 16 | 17 | // TODO: In order to do this with UV mapping for a sphere, we'll need to get the normal 18 | // at point, and then switch directions to get a vector to the origin of the sphere. 19 | // We will probably need some calculation here to get the vector v to the right length. 20 | // Then we use the formulae on Wikipedia at: 21 | // https://en.wikipedia.org/wiki/UV_mapping 22 | // u = 0.5 + arctan2(d_z, d_x) / 2pi 23 | // v = 0.5 = arcsin(d_y) / pi 24 | // Then we use the (u, v), coordinates to determine the colour in the equivalent 25 | // 2D manner as the 3D calculation below. 26 | const Colour CheckerPattern::colourAt(const Tuple &point) const noexcept { 27 | const auto idx = 28 | (static_cast(floor(point[tuple_constants::x])) + 29 | static_cast(floor(point[tuple_constants::y])) + 30 | static_cast(floor(point[tuple_constants::z]))) % 2; 31 | return idx == 0 ? colour1 : colour2; 32 | 33 | } 34 | 35 | bool CheckerPattern::doCompare(const Pattern &other) const noexcept { 36 | const auto othercp = dynamic_cast(&other); 37 | return othercp != nullptr && colour1 == othercp->colour1 && colour2 == othercp->colour2; 38 | } 39 | } -------------------------------------------------------------------------------- /src/patterns/checkerpattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * checkerpattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pattern.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer { 13 | class CheckerPattern final: public Pattern { 14 | Colour colour1; 15 | Colour colour2; 16 | 17 | public: 18 | CheckerPattern() noexcept; 19 | CheckerPattern(const CheckerPattern&) noexcept = default; 20 | CheckerPattern(CheckerPattern&&) noexcept = default; 21 | 22 | template 23 | CheckerPattern(T&& c1, R&& c2): colour1{c1}, colour2{c2} {} 24 | 25 | CheckerPattern &operator=(const CheckerPattern&) noexcept = default; 26 | CheckerPattern &operator=(CheckerPattern&&) noexcept = default; 27 | 28 | const Colour colourAt(const Tuple&) const noexcept override; 29 | 30 | private: 31 | bool doCompare(const Pattern &other) const noexcept; 32 | 33 | }; 34 | } 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/patterns/gradientpattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * gradientpattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "gradientpattern.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer { 13 | GradientPattern::GradientPattern() noexcept: 14 | colour1{predefined_colours::white}, 15 | colour2{predefined_colours::black} {} 16 | 17 | const Colour GradientPattern::colourAt(const Tuple &point) const noexcept { 18 | return colour1 + (colour2 - colour1) * (point[tuple_constants::x] - std::floor(point[tuple_constants::x])); 19 | } 20 | 21 | bool GradientPattern::doCompare(const Pattern &other) const noexcept { 22 | const auto othergp = dynamic_cast(&other); 23 | return othergp != nullptr && colour1 == othergp->colour1 && colour2 == othergp->colour2; 24 | } 25 | } -------------------------------------------------------------------------------- /src/patterns/gradientpattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * gradientpattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pattern.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer { 13 | class GradientPattern final: public Pattern { 14 | Colour colour1; 15 | Colour colour2; 16 | 17 | public: 18 | GradientPattern() noexcept; 19 | GradientPattern(const GradientPattern&) noexcept = default; 20 | GradientPattern(GradientPattern&&) noexcept = default; 21 | 22 | template 23 | GradientPattern(T&& c1, R&& c2): colour1{c1}, colour2{c2} {} 24 | 25 | GradientPattern &operator=(const GradientPattern&) noexcept = default; 26 | GradientPattern &operator=(GradientPattern&&) noexcept = default; 27 | 28 | const Colour colourAt(const Tuple&) const noexcept override; 29 | 30 | private: 31 | bool doCompare(const Pattern &other) const noexcept; 32 | }; 33 | } 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/patterns/pattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * pattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include "affine_transform.h" 8 | #include "matrix.h" 9 | #include "pattern.h" 10 | #include "shape.h" 11 | 12 | using namespace raytracer::shapes; 13 | 14 | namespace raytracer { 15 | Pattern::Pattern(): 16 | transformation{predefined_matrices::I<>}, 17 | transformationInverse{predefined_matrices::I<>} {} 18 | Pattern::Pattern(const Transformation &t): 19 | transformation{t}, 20 | transformationInverse{predefined_matrices::I<>} {} 21 | Pattern::Pattern(Transformation &&t): 22 | transformation{t}, 23 | transformationInverse{predefined_matrices::I<>} {} 24 | 25 | const Transformation &Pattern::getTransformation() const noexcept { 26 | return transformation; 27 | } 28 | void Pattern::setTransformation(const Transformation &t) noexcept { 29 | transformation = t; 30 | transformationInverse = transformation.invert(); 31 | } 32 | void Pattern::setTransformation(Transformation &&t) noexcept { 33 | transformation = std::move(t); 34 | transformationInverse = transformation.invert(); 35 | } 36 | 37 | bool Pattern::operator==(const Pattern &other) const noexcept { 38 | return typeid(*this) == typeid(other) && doCompare(other); 39 | } 40 | 41 | bool Pattern::operator!=(const Pattern &other) const noexcept { 42 | return !(*this == other); 43 | } 44 | 45 | const Colour Pattern::colourAtObject(const std::shared_ptr &shape, 46 | const Tuple &world_point) const noexcept { 47 | const auto object_point = shape->worldToObject(world_point); 48 | const auto pattern_point = transformationInverse * object_point; 49 | return colourAt(pattern_point); 50 | } 51 | 52 | bool Pattern::doCompare(const Pattern&) const noexcept { 53 | return true; 54 | } 55 | } -------------------------------------------------------------------------------- /src/patterns/pattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * pattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "affine_transform.h" 12 | #include "vec.h" 13 | 14 | namespace raytracer { 15 | namespace shapes { 16 | class Shape; 17 | } 18 | 19 | class Pattern { 20 | Transformation transformation; 21 | Transformation transformationInverse; 22 | 23 | public: 24 | Pattern(); 25 | Pattern(const Transformation&); 26 | Pattern(Transformation&&); 27 | Pattern(const Pattern&) = default; 28 | Pattern(Pattern&&) = default; 29 | Pattern &operator=(const Pattern&) = default; 30 | Pattern &operator=(Pattern&&) = default; 31 | 32 | const Transformation &getTransformation() const noexcept; 33 | void setTransformation(const Transformation&) noexcept; 34 | void setTransformation(Transformation&&) noexcept; 35 | 36 | /** 37 | * The comparison operator compares types and then defers to doCompare for a more concrete implementation, 38 | * which should be - if necessary - implemented in subclasses. The default is a doCompare that returns true. 39 | */ 40 | bool operator==(const Pattern &other) const noexcept; 41 | bool operator!=(const Pattern &other) const noexcept; 42 | 43 | /// Return the colour at a given point for this pattern. 44 | virtual const Colour colourAt(const Tuple&) const noexcept = 0; 45 | 46 | /// Computes the colour for a specific object at a given world point. 47 | const Colour colourAtObject(const std::shared_ptr&, const Tuple&) const noexcept; 48 | 49 | protected: 50 | /// Subclass comparison implementations should override this method. 51 | virtual bool doCompare(const Pattern &other) const noexcept; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/patterns/perlin.h: -------------------------------------------------------------------------------- 1 | /** 2 | * perlin.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | * 6 | * Perlin noise implementation adapted from Ken Perlin's implementation at: 7 | * https://mrl.nyu.edu/~perlin/noise/ 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace raytracer::impl { 18 | class PerlinNoise final { 19 | using namespace raytracer; 20 | PerlinNoise() = delete; 21 | 22 | public: 23 | static double noise(const Tuple &tup) noexcept { 24 | double x = tup[tuple_constants::x]; 25 | double y = tup[tuple_constants::y]; 26 | double z = tup[tuple_constants::z]; 27 | 28 | // Find the unit cube that contains tup. 29 | const int X = floori(x) % 255; 30 | const int Y = floori(y) % 255; 31 | const int Z = floori(z) % 255; 32 | 33 | // Find the point relative to tup in that unit cube. 34 | x -= floord(x); 35 | y -= floord(y); 36 | z -= floord(z); 37 | 38 | const double u = fade(x); 39 | const double v = fade(y); 40 | const double w = fade(z); 41 | 42 | int A = p[X] + Y; 43 | int AA = p[A] + z; 44 | int AB = p[A + 1] + Z; 45 | int B = p[X + 1] + Y; 46 | int BA = p[B] + Z; 47 | int BB = p[B + 1] + Z; 48 | 49 | // Add blended results from the eight corners of the cube. 50 | return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), 51 | grad(p[BA ], x-1, y , z )), 52 | lerp(u, grad(p[AB ], x , y-1, z ), 53 | grad(p[BB ], x-1, y-1, z ))), 54 | lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), 55 | grad(p[BA+1], x-1, y , z-1 )), 56 | lerp(u, grad(p[AB+1], x , y-1, z-1 ), 57 | grad(p[BB+1], x-1, y-1, z-1 )))); 58 | } 59 | 60 | private: 61 | constexpr static double fade(double t) noexcept { 62 | return t * t * t * (t * (t * 6 - 15) + 10); 63 | } 64 | 65 | constexpr static double lerp(double t, double a, double b) noexcept { 66 | return a + t * (b - a); 67 | } 68 | 69 | constexpr static double grad(int hash, double x, double y, double z) noexcept { 70 | int h = hash & 15; 71 | double u = h < 8 ? x : y; 72 | double v = h < 4 ? y : (h == 12 || h == 14 ? x : z); 73 | return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); 74 | } 75 | 76 | constexpr static std::array p{{ 77 | 151,160,137,91,90,15, 78 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 79 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 80 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 81 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 82 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 83 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 84 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 85 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 86 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 87 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 88 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 89 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, 90 | 151,160,137,91,90,15, 91 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 92 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 93 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 94 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 95 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 96 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 97 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 98 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 99 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 100 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 101 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 102 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 103 | }}; 104 | }; 105 | } 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/patterns/ringpattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * ringpattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "constmath.h" 12 | #include "pattern.h" 13 | #include "ringpattern.h" 14 | #include "vec.h" 15 | 16 | namespace raytracer { 17 | RingPattern::RingPattern() noexcept: colours{predefined_colours::white, predefined_colours::black} {} 18 | 19 | RingPattern::RingPattern(const std::vector &cs): colours{cs} { 20 | if (colours.empty()) 21 | throw std::invalid_argument("RingPattern requires at least one colour."); 22 | } 23 | RingPattern::RingPattern(std::vector &&cs): colours{std::move(cs)} { 24 | if (colours.empty()) 25 | throw std::invalid_argument("StripePattern requires at least one colour."); 26 | } 27 | 28 | const Colour RingPattern::colourAt(const Tuple &point) const noexcept { 29 | const auto px = point[tuple_constants::x]; 30 | const auto pz = point[tuple_constants::z]; 31 | const auto idx = static_cast(std::floor(const_sqrtd(px * px + pz * pz))); 32 | return colours[((idx % colours.size()) + colours.size()) % colours.size()]; 33 | } 34 | 35 | const std::vector RingPattern::getColours() const noexcept { 36 | return colours; 37 | } 38 | 39 | void RingPattern::setColours(const std::vector &cs) noexcept { 40 | colours = cs; 41 | } 42 | 43 | bool RingPattern::doCompare(const Pattern &other) const noexcept { 44 | const auto otherrp = dynamic_cast(&other); 45 | return otherrp != nullptr && colours == otherrp->colours; 46 | } 47 | } -------------------------------------------------------------------------------- /src/patterns/ringpattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * ringpattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "pattern.h" 12 | #include "vec.h" 13 | 14 | namespace raytracer { 15 | /// Depends on x and z to decide which colour to return, working similarly to stripes but using circular stripes. 16 | class RingPattern final: public Pattern { 17 | std::vector colours; 18 | 19 | public: 20 | /// The default stripe pattern is alternating white and black. 21 | RingPattern() noexcept; 22 | explicit RingPattern(const std::vector&); 23 | explicit RingPattern(std::vector&&); 24 | RingPattern(const RingPattern&) = default; 25 | RingPattern(RingPattern&&) noexcept = default; 26 | RingPattern &operator=(const RingPattern&) noexcept = default; 27 | RingPattern &operator=(RingPattern&&) noexcept = default; 28 | 29 | const Colour colourAt(const Tuple &) const noexcept override; 30 | 31 | const std::vector getColours() const noexcept; 32 | void setColours(const std::vector&) noexcept; 33 | private: 34 | bool doCompare(const Pattern &other) const noexcept override; 35 | }; 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/patterns/solidpattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * solidpattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include "pattern.h" 8 | #include "solidpattern.h" 9 | #include "vec.h" 10 | 11 | namespace raytracer { 12 | SolidPattern::SolidPattern() noexcept: colour{predefined_colours::white} {} 13 | 14 | SolidPattern::SolidPattern(const Colour &c) noexcept: colour{c} {} 15 | 16 | SolidPattern::SolidPattern(Colour &&c) noexcept: colour{std::move(c)} {} 17 | 18 | const Colour SolidPattern::getColour() const noexcept { 19 | return colour; 20 | } 21 | 22 | void SolidPattern::setColour(const Colour &c) noexcept { 23 | colour = c; 24 | } 25 | 26 | bool SolidPattern::doCompare(const Pattern &other) const noexcept { 27 | const auto othersp = dynamic_cast(&other); 28 | return othersp != nullptr && colour == othersp->colour; 29 | } 30 | } -------------------------------------------------------------------------------- /src/patterns/solidpattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * solidpattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "pattern.h" 10 | #include "vec.h" 11 | 12 | namespace raytracer { 13 | class SolidPattern final: public Pattern { 14 | Colour colour; 15 | 16 | public: 17 | /// Default colour is white. 18 | SolidPattern() noexcept; 19 | explicit SolidPattern(const Colour&) noexcept; 20 | explicit SolidPattern(Colour&&) noexcept; 21 | SolidPattern(const SolidPattern&) noexcept = default; 22 | SolidPattern(SolidPattern&&) noexcept = default; 23 | SolidPattern &operator=(const SolidPattern&) noexcept = default; 24 | SolidPattern &operator=(SolidPattern&&) noexcept = default; 25 | 26 | inline const Colour colourAt(const Tuple &) const noexcept override { 27 | return colour; 28 | } 29 | 30 | const Colour getColour() const noexcept; 31 | void setColour(const Colour&) noexcept; 32 | private: 33 | bool doCompare(const Pattern &other) const noexcept override; 34 | }; 35 | } 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/patterns/stripepattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * stripepattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "constmath.h" 12 | #include "stripepattern.h" 13 | #include "vec.h" 14 | 15 | namespace raytracer { 16 | StripePattern::StripePattern() noexcept: colours{predefined_colours::white, predefined_colours::black} {} 17 | 18 | StripePattern::StripePattern(const std::vector &cs): colours{cs} { 19 | if (colours.empty()) 20 | throw std::invalid_argument("StripePattern requires at least one colour."); 21 | } 22 | StripePattern::StripePattern(std::vector &&cs): colours{std::move(cs)} { 23 | if (colours.empty()) 24 | throw std::invalid_argument("StripePattern requires at least one colour."); 25 | } 26 | 27 | const Colour StripePattern::colourAt(const Tuple &point) const noexcept { 28 | const auto idx = static_cast(std::floor(point[tuple_constants::x])); 29 | return colours[((idx % colours.size()) + colours.size()) % colours.size()]; 30 | } 31 | 32 | const std::vector StripePattern::getColours() const noexcept { 33 | return colours; 34 | } 35 | 36 | void StripePattern::setColours(const std::vector &cs) noexcept { 37 | colours = cs; 38 | } 39 | 40 | bool StripePattern::doCompare(const Pattern &other) const noexcept { 41 | const auto othersp = dynamic_cast(&other); 42 | return othersp != nullptr && colours == othersp->colours; 43 | } 44 | } -------------------------------------------------------------------------------- /src/patterns/stripepattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * stripepattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "pattern.h" 12 | #include "vec.h" 13 | 14 | namespace raytracer { 15 | class StripePattern final: public Pattern { 16 | std::vector colours; 17 | 18 | public: 19 | /// The default stripe pattern is alternating white and black. 20 | StripePattern() noexcept; 21 | explicit StripePattern(const std::vector&); 22 | explicit StripePattern(std::vector&&); 23 | StripePattern(const StripePattern&) = default; 24 | StripePattern(StripePattern&&) noexcept = default; 25 | StripePattern &operator=(const StripePattern&) noexcept = default; 26 | StripePattern &operator=(StripePattern&&) noexcept = default; 27 | 28 | const Colour colourAt(const Tuple &) const noexcept override; 29 | 30 | const std::vector getColours() const noexcept; 31 | void setColours(const std::vector&) noexcept; 32 | private: 33 | bool doCompare(const Pattern &other) const noexcept override; 34 | }; 35 | } 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/pointlight.h: -------------------------------------------------------------------------------- 1 | /** 2 | * pointlight.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "vec.h" 12 | 13 | namespace raytracer { 14 | class PointLight final { 15 | private: 16 | Tuple position; 17 | Colour intensity; 18 | 19 | public: 20 | template 21 | constexpr PointLight(T &&position, S &&intensity) noexcept: position{position}, intensity{intensity} {} 22 | constexpr PointLight(const PointLight&) noexcept = default; 23 | constexpr PointLight(PointLight&&) noexcept = default; 24 | 25 | PointLight &operator=(const PointLight&) = default; 26 | 27 | constexpr bool operator==(const std::optional &other) const noexcept { 28 | return other.has_value() && (*this == other.value()); 29 | } 30 | 31 | constexpr bool operator==(const PointLight &other) const noexcept { 32 | return position == other.position && intensity == other.intensity; 33 | } 34 | 35 | template 36 | constexpr bool operator!=(T&& other) const noexcept { 37 | return !(*this == other); 38 | } 39 | 40 | constexpr Tuple getPosition() const noexcept { 41 | return position; 42 | } 43 | 44 | constexpr Colour getIntensity() const noexcept { 45 | return intensity; 46 | } 47 | }; 48 | } -------------------------------------------------------------------------------- /src/shapes/cone.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cone.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bounding_box.h" 13 | #include "cone.h" 14 | #include "constmath.h" 15 | #include "intersection.h" 16 | #include "ray.h" 17 | #include "shape.h" 18 | #include "vec.h" 19 | 20 | using namespace raytracer::impl; 21 | 22 | namespace raytracer::shapes { 23 | Cone::Cone(dummy d) noexcept: 24 | Shape{d}, 25 | minY{-std::numeric_limits::infinity()}, 26 | maxY{ std::numeric_limits::infinity()}, 27 | capped{false} {} 28 | 29 | std::shared_ptr Cone::createCone() noexcept { 30 | auto cone = std::make_shared(dummy{}); 31 | registerInstance(cone); 32 | return cone; 33 | } 34 | 35 | double Cone::getMinimumY() const noexcept { 36 | return minY; 37 | } 38 | 39 | void Cone::setMinimumY(double y) noexcept { 40 | minY = y; 41 | } 42 | 43 | double Cone::getMaximumY() const noexcept { 44 | return maxY; 45 | } 46 | 47 | void Cone::setMaximumY(double y) noexcept { 48 | maxY = y; 49 | } 50 | 51 | bool Cone::isCapped() const noexcept { 52 | return capped; 53 | } 54 | 55 | void Cone::setCapped(bool c) noexcept { 56 | capped = c; 57 | } 58 | 59 | bool Cone::checkCap(const impl::Ray &ray, double t, double y) const noexcept { 60 | const auto x = ray.getOrigin()[tuple_constants::x] + t * ray.getDirection()[tuple_constants::x]; 61 | const auto z = ray.getOrigin()[tuple_constants::z] + t * ray.getDirection()[tuple_constants::z]; 62 | return x * x + z * z <= y * y; 63 | } 64 | 65 | BoundingBox Cone::bounds() const { 66 | const auto a = const_absd(minY); 67 | const auto b = const_absd(maxY); 68 | const auto limit = const_maxd(a, b); 69 | return BoundingBox{make_point(-limit, minY, -limit), make_point(limit, maxY, limit)}; 70 | } 71 | 72 | const std::vector Cone::localIntersection(const Ray &ray) const noexcept { 73 | const auto rdx = ray.getDirection()[tuple_constants::x]; 74 | const auto rdy = ray.getDirection()[tuple_constants::y]; 75 | const auto rdz = ray.getDirection()[tuple_constants::z]; 76 | const auto rox = ray.getOrigin()[tuple_constants::x]; 77 | const auto roy = ray.getOrigin()[tuple_constants::y]; 78 | const auto roz = ray.getOrigin()[tuple_constants::z]; 79 | 80 | const auto a = rdx * rdx - rdy * rdy + rdz * rdz; 81 | const auto b = 2 * rox * rdx - 2 * roy * rdy + 2 * roz * rdz; 82 | const auto c = rox * rox + - roy * roy + roz * roz; 83 | 84 | std::vector xs; 85 | 86 | if (ALMOST_EQUALS(a, 0) && !ALMOST_EQUALS(b, 0)) { 87 | const auto t = -c / 2 * b; 88 | xs.emplace_back(Intersection{t, shared_from_this()}); 89 | } 90 | 91 | // Possible intersection with walls if a is nearly zero. 92 | if (!ALMOST_EQUALS(a, 0)) { 93 | const auto discriminant = b * b - 4 * a * c; 94 | if (discriminant >= 0) { 95 | const auto sqrt_discriminant = const_sqrtd(discriminant); 96 | auto t0 = (-b - sqrt_discriminant) / (2 * a); 97 | auto t1 = (-b + sqrt_discriminant) / (2 * a); 98 | if (t0 > t1) 99 | std::swap(t0, t1); 100 | 101 | const auto y0 = ray.getOrigin()[tuple_constants::y] + t0 * ray.getDirection()[tuple_constants::y]; 102 | if (minY < y0 && y0 < maxY) 103 | xs.emplace_back(Intersection{t0, shared_from_this()}); 104 | 105 | const auto y1 = ray.getOrigin()[tuple_constants::y] + t1 * ray.getDirection()[tuple_constants::y]; 106 | if (minY < y1 && y1 < maxY) 107 | xs.emplace_back(Intersection{t1, shared_from_this()}); 108 | } 109 | } 110 | 111 | // Check for intersection with caps. 112 | if (capped && !ALMOST_EQUALS(ray.getDirection()[tuple_constants::y], 0)) { 113 | const auto t0 = (minY - ray.getOrigin()[tuple_constants::y]) / ray.getDirection()[tuple_constants::y]; 114 | if (checkCap(ray, t0, minY)) 115 | xs.emplace_back(Intersection{t0, shared_from_this()}); 116 | const auto t1 = (maxY - ray.getOrigin()[tuple_constants::y]) / ray.getDirection()[tuple_constants::y]; 117 | if (checkCap(ray, t1, maxY)) 118 | xs.emplace_back(t1, shared_from_this()); 119 | } 120 | 121 | return xs; 122 | } 123 | 124 | 125 | const Tuple Cone::localNormalAt(const Tuple &point) const noexcept { 126 | const auto x = point[tuple_constants::x]; 127 | const auto z = point[tuple_constants::z]; 128 | 129 | const auto dist = x * x + z * z; 130 | if (dist < 1 && GREATER_THAN(point[tuple_constants::y], maxY)) 131 | return predefined_tuples::y1; 132 | else if (dist < 1 && LESS_THAN(point[tuple_constants::y], minY)) 133 | return -predefined_tuples::y1; 134 | 135 | auto y = const_sqrtd(x * x + z * z); 136 | if (point[tuple_constants::y] > 0) 137 | y = - y; 138 | return make_vector(x, y, z); 139 | } 140 | } -------------------------------------------------------------------------------- /src/shapes/cone.h: -------------------------------------------------------------------------------- 1 | /** 2 | * cone.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | 15 | namespace raytracer::impl { 16 | class BoundingBox; 17 | class Intersection; 18 | class Ray; 19 | } 20 | 21 | namespace raytracer::shapes { 22 | /** 23 | * A double-napped cone, i.e. a cone with another cone perched above it, tip-to-tip, where the tips meet 24 | * at 0 and the cones extend in y infinitely. 25 | */ 26 | class Cone final: public Shape { 27 | private: 28 | double minY; 29 | double maxY; 30 | bool capped; 31 | 32 | public: 33 | /// Uninstantiable outside of factory method. 34 | explicit Cone(dummy) noexcept; 35 | 36 | /// Factory method to create a cylinder of radius 1 that extends infinitely in y. 37 | static std::shared_ptr createCone() noexcept; 38 | 39 | double getMinimumY() const noexcept; 40 | void setMinimumY(double) noexcept; 41 | double getMaximumY() const noexcept; 42 | void setMaximumY(double) noexcept; 43 | bool isCapped() const noexcept; 44 | void setCapped(bool) noexcept; 45 | 46 | /// Get a bounding box. 47 | impl::BoundingBox bounds() const override; 48 | 49 | private: 50 | bool checkCap(const impl::Ray&, double, double) const noexcept; 51 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 52 | const Tuple localNormalAt(const Tuple&) const noexcept override; 53 | }; 54 | } 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/shapes/cube.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cube.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bounding_box.h" 13 | #include "constmath.h" 14 | #include "cube.h" 15 | #include "cube_functions.h" 16 | #include "intersection.h" 17 | #include "ray.h" 18 | #include "shape.h" 19 | #include "vec.h" 20 | 21 | using namespace raytracer::impl; 22 | 23 | namespace raytracer::shapes { 24 | Cube::Cube(dummy d) noexcept: Shape{d} {} 25 | 26 | std::shared_ptr Cube::createCube() noexcept { 27 | auto cube = std::make_shared(dummy{}); 28 | registerInstance(cube); 29 | return cube; 30 | } 31 | 32 | 33 | BoundingBox Cube::bounds() const { 34 | return BoundingBox{make_point(-1, -1, -1), make_point(1, 1, 1)}; 35 | } 36 | 37 | const std::vector Cube::localIntersection(const Ray &ray) const noexcept { 38 | std::vector mins; 39 | std::vector maxs; 40 | std::vector xs; 41 | 42 | for (size_t i = 0; i < 3; ++i) { 43 | const auto [vmin, vmax] = checkAxis(ray.getOrigin()[i], ray.getDirection()[i], -1, 1); 44 | mins.emplace_back(vmin); 45 | maxs.emplace_back(vmax); 46 | } 47 | 48 | const auto tmin = *std::max_element(std::cbegin(mins), std::cend(mins)); 49 | const auto tmax = *std::min_element(std::cbegin(maxs), std::cend(maxs)); 50 | if (tmin <= tmax) { 51 | xs.emplace_back(Intersection{tmin, shared_from_this()}); 52 | xs.emplace_back(Intersection{tmax, shared_from_this()}); 53 | } 54 | 55 | return xs; 56 | } 57 | 58 | const Tuple Cube::localNormalAt(const Tuple &point) const noexcept { 59 | const auto maxc = std::max(const_absd(point[0]), std::max(const_absd(point[1]), const_absd(point[2]))); 60 | if (maxc == const_absd(point[0])) 61 | return make_vector(point[0], 0, 0); 62 | else if (maxc == const_absd(point[1])) 63 | return make_vector(0, point[1], 0); 64 | else 65 | return make_vector(0, 0, point[2]); 66 | } 67 | } -------------------------------------------------------------------------------- /src/shapes/cube.h: -------------------------------------------------------------------------------- 1 | /** 2 | * cube.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | #include "vec.h" 15 | 16 | namespace raytracer::impl { 17 | class BoundingBox; 18 | class Intersection; 19 | class Ray; 20 | } 21 | 22 | namespace raytracer::shapes { 23 | /// An Axis Aligned Bounding Box cube. 24 | class Cube: public Shape { 25 | public: 26 | /// Uninstantiable outside of factory method. 27 | explicit Cube(dummy d) noexcept; 28 | 29 | /// Factory method to create a cube at the origin extending +/-1 along all axes. 30 | static std::shared_ptr createCube() noexcept; 31 | 32 | /// Get a bounding box. 33 | impl::BoundingBox bounds() const override; 34 | 35 | private: 36 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 37 | const Tuple localNormalAt(const Tuple&) const noexcept override; 38 | }; 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/shapes/cylinder.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * cylinder.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "bounding_box.h" 12 | #include "constmath.h" 13 | #include "cylinder.h" 14 | #include "intersection.h" 15 | #include "ray.h" 16 | #include "shape.h" 17 | #include "vec.h" 18 | 19 | using namespace raytracer::impl; 20 | 21 | namespace raytracer::shapes { 22 | Cylinder::Cylinder(dummy d) noexcept: 23 | Shape{d}, 24 | minY{math_constants::ninf<>}, 25 | maxY{math_constants::inf<>}, 26 | capped{false} {} 27 | 28 | std::shared_ptr Cylinder::createCylinder() noexcept { 29 | auto cylinder = std::make_shared(dummy{}); 30 | registerInstance(cylinder); 31 | return cylinder; 32 | } 33 | 34 | double Cylinder::getMinimumY() const noexcept { 35 | return minY; 36 | } 37 | 38 | void Cylinder::setMinimumY(double y) noexcept { 39 | minY = y; 40 | } 41 | 42 | double Cylinder::getMaximumY() const noexcept { 43 | return maxY; 44 | } 45 | 46 | void Cylinder::setMaximumY(double y) noexcept { 47 | maxY = y; 48 | } 49 | 50 | bool Cylinder::isCapped() const noexcept { 51 | return capped; 52 | } 53 | 54 | void Cylinder::setCapped(bool c) noexcept { 55 | capped = c; 56 | } 57 | 58 | /// Get a bounding box. 59 | BoundingBox Cylinder::bounds() const { 60 | return BoundingBox{make_point(-1, minY, -1), make_point(1, maxY, 1)}; 61 | } 62 | 63 | bool Cylinder::checkCap(const impl::Ray &ray, double t) const noexcept { 64 | const auto x = ray.getOrigin()[tuple_constants::x] + t * ray.getDirection()[tuple_constants::x]; 65 | const auto z = ray.getOrigin()[tuple_constants::z] + t * ray.getDirection()[tuple_constants::z]; 66 | return x * x + z * z <= 1; 67 | } 68 | 69 | const std::vector Cylinder::localIntersection(const Ray &ray) const noexcept { 70 | const auto rdx = ray.getDirection()[tuple_constants::x]; 71 | const auto rdz = ray.getDirection()[tuple_constants::z]; 72 | const auto a = rdx * rdx + rdz * rdz; 73 | 74 | std::vector xs; 75 | 76 | // Possible intersection with walls if a is nearly zero. 77 | if (!ALMOST_EQUALS(a, 0)) { 78 | const auto rox = ray.getOrigin()[tuple_constants::x]; 79 | const auto roz = ray.getOrigin()[tuple_constants::z]; 80 | 81 | const auto b = 2 * rox * rdx + 2 * roz * rdz; 82 | const auto c = rox * rox + roz * roz - 1; 83 | 84 | const auto discriminant = b * b - 4 * a * c; 85 | if (discriminant >= 0) { 86 | const auto sqrt_discriminant = const_sqrtd(discriminant); 87 | auto t0 = (-b - sqrt_discriminant) / (2 * a); 88 | auto t1 = (-b + sqrt_discriminant) / (2 * a); 89 | if (t0 > t1) 90 | std::swap(t0, t1); 91 | 92 | const auto y0 = ray.getOrigin()[tuple_constants::y] + t0 * ray.getDirection()[tuple_constants::y]; 93 | if (minY < y0 && y0 < maxY) 94 | xs.emplace_back(Intersection{t0, shared_from_this()}); 95 | 96 | const auto y1 = ray.getOrigin()[tuple_constants::y] + t1 * ray.getDirection()[tuple_constants::y]; 97 | if (minY < y1 && y1 < maxY) 98 | xs.emplace_back(Intersection{t1, shared_from_this()}); 99 | } 100 | } 101 | 102 | // Check for intersection with caps. 103 | if (capped && !ALMOST_EQUALS(ray.getDirection()[tuple_constants::y], 0)) { 104 | const auto t0 = (minY - ray.getOrigin()[tuple_constants::y]) / ray.getDirection()[tuple_constants::y]; 105 | if (checkCap(ray, t0)) 106 | xs.emplace_back(Intersection{t0, shared_from_this()}); 107 | const auto t1 = (maxY - ray.getOrigin()[tuple_constants::y]) / ray.getDirection()[tuple_constants::y]; 108 | if (checkCap(ray, t1)) 109 | xs.emplace_back(t1, shared_from_this()); 110 | } 111 | 112 | return xs; 113 | } 114 | 115 | 116 | const Tuple Cylinder::localNormalAt(const Tuple &point) const noexcept { 117 | const auto x = point[tuple_constants::x]; 118 | const auto z = point[tuple_constants::z]; 119 | 120 | const auto dist = x * x + z * z; 121 | if (dist < 1 && GREATER_THAN(point[tuple_constants::y], maxY)) 122 | return predefined_tuples::y1; 123 | else if (dist < 1 && LESS_THAN(point[tuple_constants::y], minY)) 124 | return -predefined_tuples::y1; 125 | else 126 | return make_vector(point[tuple_constants::x], 0, point[tuple_constants::z]); 127 | } 128 | } -------------------------------------------------------------------------------- /src/shapes/cylinder.h: -------------------------------------------------------------------------------- 1 | /** 2 | * cylinder.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | 15 | namespace raytracer::impl { 16 | class BoundingBox; 17 | class Intersection; 18 | class Ray; 19 | } 20 | 21 | namespace raytracer::shapes { 22 | class Cylinder final: public Shape { 23 | private: 24 | double minY; 25 | double maxY; 26 | bool capped; 27 | 28 | public: 29 | /// Uninstantiable outside of factory method. 30 | explicit Cylinder(dummy) noexcept; 31 | 32 | /// Factory method to create a cylinder of radius 1 that extends infinitely in y. 33 | static std::shared_ptr createCylinder() noexcept; 34 | 35 | double getMinimumY() const noexcept; 36 | void setMinimumY(double) noexcept; 37 | double getMaximumY() const noexcept; 38 | void setMaximumY(double) noexcept; 39 | bool isCapped() const noexcept; 40 | void setCapped(bool) noexcept; 41 | 42 | /// Get a bounding box. 43 | impl::BoundingBox bounds() const override; 44 | 45 | private: 46 | bool checkCap(const impl::Ray&, double) const noexcept; 47 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 48 | const Tuple localNormalAt(const Tuple&) const noexcept override; 49 | }; 50 | } 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/shapes/group.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * group.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "bounding_box.h" 16 | #include "constmath.h" 17 | #include "group.h" 18 | #include "intersection.h" 19 | #include "ray.h" 20 | #include "shape.h" 21 | #include "vec.h" 22 | 23 | using namespace raytracer; 24 | using namespace raytracer::impl; 25 | 26 | namespace raytracer::shapes { 27 | Group::Group(dummy d) noexcept: Shape{d} {} 28 | 29 | std::shared_ptr Group::createGroup() noexcept { 30 | auto group = std::make_shared(dummy{}); 31 | registerInstance(group); 32 | return group; 33 | } 34 | 35 | const std::vector> Group::getShapes() const noexcept { 36 | return shapes; 37 | } 38 | 39 | void Group::clearShapes() noexcept { 40 | for (auto &shape: shapes) 41 | shape->setParent(nullptr); 42 | shapes.clear(); 43 | } 44 | 45 | BoundingBox Group::bounds() const { 46 | BoundingBox box{}; 47 | for (const auto &shape: shapes) 48 | box.addBox(shape->parentSpaceBounds()); 49 | return box; 50 | } 51 | 52 | const std::vector Group::localIntersection(const Ray &ray) const noexcept { 53 | if (bounds().intersects(ray)) { 54 | std::vector xs{}; 55 | for (auto &shape: shapes) { 56 | auto xss = shape->intersect(ray); 57 | std::copy(xss.begin(), xss.end(), std::back_inserter(xs)); 58 | } 59 | 60 | // Now sort by t. 61 | std::sort(xs.begin(), xs.end(), [](const auto &x1, const auto &x2) { return x1.getT() < x2.getT(); }); 62 | return xs; 63 | } else return {}; 64 | } 65 | 66 | const Tuple Group::localNormalAt(const Tuple&) const { 67 | throw std::domain_error("Group::localNormalAt cannot be called"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/shapes/group.h: -------------------------------------------------------------------------------- 1 | /** 2 | * group.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | #include "vec.h" 15 | 16 | namespace raytracer::impl { 17 | class BoundingBox; 18 | class Intersection; 19 | class Ray; 20 | } 21 | 22 | namespace raytracer::shapes { 23 | /// A collection of other conglomerated shapes into a single object. 24 | class Group: public Shape { 25 | std::vector> shapes; 26 | 27 | public: 28 | /// Uninstantiable outside of factory method. 29 | explicit Group(dummy d) noexcept; 30 | 31 | /// Factory method to create a group. 32 | static std::shared_ptr createGroup() noexcept; 33 | 34 | template 35 | void add(T& t) noexcept { 36 | shapes.emplace_back(t); 37 | t->setParent(shared_from_this()); 38 | } 39 | 40 | template 41 | void addAll(Ts& ... ts) noexcept { 42 | (add(ts),... ); 43 | } 44 | 45 | const std::vector> getShapes() const noexcept; 46 | 47 | /// We need to clear the shapes out of a group in order to not cause memory leaks. 48 | void clearShapes() noexcept; 49 | 50 | /// Get a bounding box. 51 | impl::BoundingBox bounds() const override; 52 | 53 | private: 54 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 55 | const Tuple localNormalAt(const Tuple&) const override; 56 | }; 57 | } 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/shapes/plane.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * plane.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "bounding_box.h" 12 | #include "constmath.h" 13 | #include "intersection.h" 14 | #include "plane.h" 15 | #include "ray.h" 16 | #include "vec.h" 17 | 18 | using namespace raytracer::impl; 19 | 20 | namespace raytracer::shapes { 21 | Plane::Plane(dummy d) noexcept: Shape{d} {} 22 | 23 | std::shared_ptr Plane::createPlane() noexcept { 24 | std::shared_ptr plane = std::make_shared(dummy{}); 25 | registerInstance(plane); 26 | return plane; 27 | } 28 | 29 | BoundingBox Plane::bounds() const { 30 | return BoundingBox{make_point(math_constants::ninf<>, 0, math_constants::ninf<>), 31 | make_point(math_constants::inf<>, 0, math_constants::inf<>)}; 32 | } 33 | 34 | const std::vector Plane::localIntersection(const Ray &ray) const noexcept { 35 | if (const_absd(ray.getDirection()[tuple_constants::y]) < 1e-4) 36 | return {}; 37 | 38 | const auto t = -ray.getOrigin()[tuple_constants::y] / ray.getDirection()[tuple_constants::y]; 39 | return {Intersection{t, shared_from_this()}}; 40 | } 41 | 42 | const Tuple Plane::localNormalAt(const Tuple&) const noexcept { 43 | return predefined_tuples::y1; 44 | } 45 | } -------------------------------------------------------------------------------- /src/shapes/plane.h: -------------------------------------------------------------------------------- 1 | /** 2 | * plane.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | 15 | namespace raytracer::impl { 16 | class BoundingBox; 17 | class Intersection; 18 | class Ray; 19 | } 20 | 21 | namespace raytracer::shapes { 22 | class Plane final: public Shape { 23 | public: 24 | /// Uninstantiable outside of factory method. 25 | explicit Plane(dummy) noexcept; 26 | 27 | /// Factory method to create an x-z plane through the origin. 28 | static std::shared_ptr createPlane() noexcept; 29 | 30 | /// Get a bounding box. 31 | impl::BoundingBox bounds() const override; 32 | 33 | private: 34 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 35 | const Tuple localNormalAt(const Tuple&) const noexcept override; 36 | }; 37 | } 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/shapes/shape.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * shape.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "affine_transform.h" 12 | #include "bounding_box.h" 13 | #include "shape.h" 14 | #include "intersection.h" 15 | #include "material.h" 16 | #include "matrix.h" 17 | #include "ray.h" 18 | 19 | using namespace raytracer; 20 | using namespace raytracer::impl; 21 | 22 | namespace raytracer::shapes { 23 | Shape::Shape(dummy d) noexcept: 24 | InstanceManager{d}, 25 | transformation{predefined_matrices::I}, 26 | transformationInverse{predefined_matrices::I}, 27 | transformationInverseTranspose{predefined_matrices::I}, 28 | material{std::make_shared()}, 29 | parent(nullptr), 30 | casts_shadow{true} {} 31 | 32 | bool Shape::operator==(const Shape &other) const noexcept { 33 | return typeid(*this) == typeid(other) 34 | && transformation == other.transformation 35 | && casts_shadow == other.casts_shadow 36 | && *material == *other.material 37 | && doCompare(other); 38 | } 39 | 40 | bool Shape::operator!=(const Shape &other) const noexcept { 41 | return !(*this == other); 42 | } 43 | 44 | const Transformation &Shape::getTransformation() const noexcept { 45 | return transformation; 46 | } 47 | 48 | const Transformation &Shape::getTransformationInverse() const noexcept { 49 | return transformationInverse; 50 | } 51 | 52 | void Shape::setTransformation(Transformation&& t) noexcept { 53 | transformation = t; 54 | transformationInverse = transformation.invert(); 55 | transformationInverseTranspose = transformationInverse.transpose(); 56 | } 57 | 58 | void Shape::setTransformation(const Transformation &t) noexcept { 59 | transformation = t; 60 | transformationInverse = transformation.invert(); 61 | transformationInverseTranspose = transformationInverse.transpose(); 62 | } 63 | 64 | void Shape::setTransformation(Transformation &t) noexcept { 65 | transformation = t; 66 | transformationInverse = transformation.invert(); 67 | transformationInverseTranspose = transformationInverse.transpose(); 68 | } 69 | 70 | const std::shared_ptr &Shape::getMaterial() const noexcept { 71 | return material; 72 | } 73 | 74 | std::shared_ptr &Shape::getMaterial() noexcept { 75 | return material; 76 | } 77 | 78 | void Shape::setMaterial(std::shared_ptr &&m) noexcept { 79 | material = std::move(m); 80 | } 81 | 82 | void Shape::setMaterial(const std::shared_ptr &m) noexcept { 83 | material = m; 84 | } 85 | 86 | void Shape::setMaterial(std::shared_ptr &m) noexcept { 87 | material = m; 88 | } 89 | 90 | const std::shared_ptr Shape::getParent() const noexcept { 91 | return parent; 92 | } 93 | 94 | void Shape::setParent(std::shared_ptr p) noexcept { 95 | parent = p; 96 | } 97 | 98 | bool Shape::castsShadow() const noexcept { 99 | return casts_shadow; 100 | } 101 | 102 | void Shape::setCastsShadow(bool s) noexcept { 103 | casts_shadow = s; 104 | } 105 | 106 | const std::vector Shape::intersect(const Ray &r0) const noexcept { 107 | // Transform the ray to object space. 108 | const Ray r = r0.transform(transformationInverse); 109 | 110 | // Return the shape-dependent implementation results. 111 | return localIntersection(r); 112 | } 113 | 114 | const Tuple Shape::normalAt(const Tuple &world_point) const noexcept { 115 | const auto local_point = worldToObject(world_point); 116 | const auto local_normal = localNormalAt(local_point); 117 | return normalToWorld(local_normal); 118 | 119 | } 120 | 121 | const Tuple Shape::worldToObject(const Tuple &point) const noexcept { 122 | return transformationInverse * (parent == nullptr ? point : parent->worldToObject(point)); 123 | } 124 | 125 | const Tuple Shape::normalToWorld(const Tuple &normal) const noexcept { 126 | const auto n1 = transformationInverseTranspose * normal; 127 | const auto n2 = make_vector(n1[tuple_constants::x], 128 | n1[tuple_constants::y], 129 | n1[tuple_constants::z]).normalize(); 130 | return (parent == nullptr) ? n2 : parent->normalToWorld(n2); 131 | } 132 | 133 | BoundingBox Shape::parentSpaceBounds() const { 134 | return bounds().transform(transformation); 135 | } 136 | } -------------------------------------------------------------------------------- /src/shapes/shape.h: -------------------------------------------------------------------------------- 1 | /** 2 | * shape.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "affine_transform.h" 15 | #include "instance_manager.h" 16 | #include "material.h" 17 | #include "vec.h" 18 | 19 | namespace raytracer::impl { 20 | class BoundingBox; 21 | class Intersection; 22 | class Ray; 23 | } 24 | 25 | namespace raytracer::shapes { 26 | class Group; 27 | 28 | /** 29 | * Note that there is some ghastliness in how the intersections are created right now, since they need a 30 | * shared_ptr to this. We could use std::enabled_shared_from_this, and then use make_shared_from_this(), 31 | * but then we need to have a shared_ptr to this stored somewhere as a prerequisite to doing so. 32 | * 33 | * So right now, we duplicate the objects, which is horrible. 34 | * TODO: Fix this. All Shapes should be factory managed and use shared_ptr. 35 | */ 36 | class Shape: public impl::InstanceManager, public std::enable_shared_from_this { 37 | protected: 38 | Transformation transformation; 39 | Transformation transformationInverse; 40 | Transformation transformationInverseTranspose; 41 | std::shared_ptr material; 42 | std::shared_ptr parent; 43 | bool casts_shadow; 44 | 45 | public: 46 | explicit Shape(dummy d) noexcept; 47 | 48 | /** 49 | * Compare type compatibility, transformation and material, and then invoke the concrete implementation, 50 | * doCompare, which is subclass-dependent. Override if necessary. 51 | */ 52 | bool operator==(const Shape&) const noexcept; 53 | bool operator!=(const Shape&) const noexcept; 54 | 55 | const Transformation &getTransformation() const noexcept; 56 | const Transformation &getTransformationInverse() const noexcept; 57 | void setTransformation(Transformation&&) noexcept; 58 | void setTransformation(const Transformation&) noexcept; 59 | void setTransformation(Transformation&) noexcept; 60 | 61 | const std::shared_ptr &getMaterial() const noexcept; 62 | std::shared_ptr &getMaterial() noexcept; 63 | void setMaterial(std::shared_ptr&&) noexcept; 64 | void setMaterial(const std::shared_ptr&) noexcept; 65 | void setMaterial(std::shared_ptr&) noexcept; 66 | 67 | const std::shared_ptr getParent() const noexcept; 68 | void setParent(std::shared_ptr) noexcept; 69 | 70 | bool castsShadow() const noexcept; 71 | void setCastsShadow(bool s) noexcept; 72 | 73 | /** 74 | * Convert the ray to object space and then pass it to the concrete implementation of local_intersect, 75 | * which is subclass-dependent. 76 | */ 77 | const std::vector intersect(const impl::Ray&) const noexcept; 78 | 79 | /** 80 | * Takes a world point and transforms it to object space. It is then passed to localNormalAt, which is 81 | * subclass-dependent. The normal vector is then translated back to world space and returned. 82 | */ 83 | const Tuple normalAt(const Tuple &p) const noexcept; 84 | 85 | /** 86 | * Takes a point and transforms it from world coordinates to object coordinates, replying on the parent 87 | * object if one exists. 88 | */ 89 | const Tuple worldToObject(const Tuple&) const noexcept; 90 | 91 | /** 92 | * Takes a normal and converts it to world coordinates. 93 | */ 94 | const Tuple normalToWorld(const Tuple&) const noexcept; 95 | 96 | /** 97 | * Create a bounding box for a shape. 98 | */ 99 | virtual impl::BoundingBox bounds() const = 0; 100 | 101 | /** 102 | * Report a shape's bounds in the space of the shape's parent. 103 | * This is used for groups and CSG. 104 | */ 105 | impl::BoundingBox parentSpaceBounds() const; 106 | 107 | protected: 108 | /** 109 | * Any additional implementation for equality comparison in subclasses, should be implemented here. 110 | */ 111 | virtual bool doCompare(const Shape&) const noexcept { 112 | return true; 113 | } 114 | 115 | /** 116 | * The intersect method transforms the ray to object space and passes it to this method, which 117 | * should comprise the concrete implementation of calculating the intersections with the implemented Shape. 118 | */ 119 | virtual const std::vector localIntersection(const impl::Ray &r) 120 | const noexcept = 0; 121 | 122 | /** 123 | * The normalAt method transforms the point to object space and passes it to this method, which 124 | * should comprise the concrete implementation of calculating the normal vector at the point for the 125 | * implemented Shape. The normalAt method then translates it back to world space. 126 | * 127 | * Note: Groups should never have this method called on them, and will throw a domain_error if it is attempted. 128 | */ 129 | virtual const Tuple localNormalAt(const Tuple&) const = 0; 130 | 131 | friend class Group; 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /src/shapes/sphere.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * sphere.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bounding_box.h" 13 | #include "constmath.h" 14 | #include "intersection.h" 15 | #include "ray.h" 16 | #include "shape.h" 17 | #include "sphere.h" 18 | #include "vec.h" 19 | 20 | using namespace raytracer::impl; 21 | 22 | namespace raytracer::shapes { 23 | Sphere::Sphere(InstanceManager::dummy d) noexcept: Shape{d} {} 24 | 25 | std::shared_ptr Sphere::createSphere() noexcept { 26 | std::shared_ptr sphere = std::make_shared(dummy{}); 27 | registerInstance(sphere); 28 | return sphere; 29 | } 30 | 31 | std::shared_ptr Sphere::createGlassSphere() noexcept { 32 | std::shared_ptr sphere = createSphere(); 33 | sphere->getMaterial()->setTransparency(1); 34 | sphere->getMaterial()->setRefractiveIndex(1.5); 35 | return sphere; 36 | } 37 | 38 | BoundingBox Sphere::bounds() const { 39 | return BoundingBox{make_point(-1, -1, -1), make_point(1, 1, 1)}; 40 | } 41 | 42 | const std::vector Sphere::localIntersection(const Ray &r) const noexcept { 43 | const auto sphere_to_ray = r.getOrigin() - predefined_tuples::zero_point; 44 | const auto &direction = r.getDirection(); 45 | 46 | const auto a = direction.dot_product(direction); 47 | const auto b = 2 * direction.dot_product(sphere_to_ray); 48 | const auto c = sphere_to_ray.dot_product(sphere_to_ray) - 1; 49 | 50 | const auto discriminant = b * b - 4 * a * c; 51 | if (discriminant < 0) 52 | return {}; 53 | 54 | const auto sqrt_discriminant = const_sqrtd(discriminant); 55 | auto t0 = (-b - sqrt_discriminant) / (2 * a); 56 | auto t1 = (-b + sqrt_discriminant) / (2 * a); 57 | if (t0 > t1) 58 | std::swap(t0, t1); 59 | return {Intersection{t0, shared_from_this()}, Intersection{t1, shared_from_this()}}; 60 | } 61 | 62 | const Tuple Sphere::localNormalAt(const Tuple &point) const noexcept { 63 | return point - predefined_tuples::zero_point; 64 | } 65 | } -------------------------------------------------------------------------------- /src/shapes/sphere.h: -------------------------------------------------------------------------------- 1 | /** 2 | * sphere.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shape.h" 14 | #include "vec.h" 15 | 16 | namespace raytracer::impl { 17 | class BoundingBox; 18 | class Intersection; 19 | class Ray; 20 | } 21 | 22 | namespace raytracer::shapes { 23 | class Sphere final: public Shape { 24 | public: 25 | /// Uninstantiable outside of factory method. 26 | explicit Sphere(dummy) noexcept; 27 | 28 | /// Factory method to create a radius 1 sphere at the origin. 29 | static std::shared_ptr createSphere() noexcept; 30 | 31 | // TODO: MOVE OR DELETE 32 | /// Factory method to create a radius 1 glassy sphere at the origin. 33 | static std::shared_ptr createGlassSphere() noexcept; 34 | 35 | /// Get a bounding box. 36 | impl::BoundingBox bounds() const override; 37 | 38 | private: 39 | const std::vector localIntersection(const impl::Ray&) const noexcept override; 40 | const Tuple localNormalAt(const Tuple&) const noexcept override; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/world.h: -------------------------------------------------------------------------------- 1 | /** 2 | * world.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "pointlight.h" 16 | #include "shape.h" 17 | #include "vec.h" 18 | 19 | namespace raytracer { 20 | namespace impl { 21 | class Intersection; 22 | class Hit; 23 | class Ray; 24 | } 25 | 26 | class World final { 27 | std::optional light; 28 | std::vector> shapes; 29 | 30 | public: 31 | World() noexcept = default; 32 | World(const World&) = default; 33 | World(World&&) noexcept = default; 34 | 35 | template 36 | World(const L &light, const std::vector &shapes) noexcept : light{light}, shapes{shapes} {} 37 | 38 | World &operator=(const World &other) = default; 39 | 40 | bool operator==(const World &other) const noexcept; 41 | bool operator!=(const World &other) const noexcept; 42 | 43 | const std::optional getLightSource() const noexcept; 44 | void setLightSource(const PointLight&) noexcept; 45 | void clearLightSource() noexcept; 46 | 47 | std::vector> &getObjects() noexcept; 48 | const std::vector> &getObjects() const noexcept; 49 | 50 | bool contains(const std::shared_ptr &sptr) const noexcept; 51 | bool contains(const shapes::Shape &s) const noexcept; 52 | 53 | /// If shadowing is true, we are looking for shadows; skip intersections with non-shadowing objects. 54 | const std::vector intersect(const impl::Ray &ray, bool shadowing = false) const noexcept; 55 | const std::optional shadeHit(const std::optional&, 56 | int remaining = MAX_RECURSIONS) const noexcept; 57 | const Colour colourAt(const impl::Ray &ray, int remaining = MAX_RECURSIONS) const noexcept; 58 | 59 | /// Determine if a point is in shadow, i.e. there is something between this point and the light source. 60 | bool isShadowed(const Tuple &point) const noexcept; 61 | 62 | /// Get the reflected colour for a hit. Limit the recursion. 63 | const Colour reflectedColour(const impl::Hit&, int remaining = MAX_RECURSIONS) const noexcept; 64 | 65 | /// Get the refracted colour for a hit. Limit the recursion. 66 | const Colour refractedColour(const impl::Hit&, int remaining = MAX_RECURSIONS) const noexcept; 67 | 68 | static World getDefaultWorld() noexcept; 69 | 70 | constexpr static int MAX_RECURSIONS = 5; 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /test/TestBounds.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestBounds.cpp 3 | * 4 | * By Sebastian Raaphorst, 2019. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "affine_transform.h" 12 | #include "common.h" 13 | #include "ray.h" 14 | #include "vec.h" 15 | #include "bounding_box.h" 16 | 17 | using namespace raytracer; 18 | using namespace raytracer::impl; 19 | 20 | TEST_CASE("BoundingBox: Creating an empty BoundingBox") { 21 | constexpr BoundingBox box{}; 22 | REQUIRE(box.getMinPoint() == predefined_tuples::min_point); 23 | REQUIRE(box.getMaxPoint() == predefined_tuples::max_point); 24 | } 25 | 26 | TEST_CASE("BoundingBox: Create a BoundingBow with volume") { 27 | constexpr BoundingBox box{make_point(-1, -2, -3), make_point(3, 2, 1)}; 28 | REQUIRE(box.getMinPoint() == make_point(-1, -2, -3)); 29 | REQUIRE(box.getMaxPoint() == make_point(3, 2, 1)); 30 | } 31 | 32 | TEST_CASE("BoundingBox: Adding points to an empty BoundingBox") { 33 | BoundingBox box{}; 34 | box.addPoint(make_point(-5, 2, 0)); 35 | box.addPoint(make_point(7, 0, -3)); 36 | REQUIRE(box.getMinPoint() == make_point(-5, 0, -3)); 37 | REQUIRE(box.getMaxPoint() == make_point(7, 2, 0)); 38 | } 39 | 40 | TEST_CASE("BoundingBox:: Adding one bounding box to another") { 41 | BoundingBox box1{make_point(-5, -2, 0), make_point(7, 4, 4)}; 42 | const BoundingBox box2{make_point(8, -7, -2), make_point(14, 2, 8)}; 43 | box1.addBox(box2); 44 | REQUIRE(box1.getMinPoint() == make_point(-5, -7, -2)); 45 | REQUIRE(box1.getMaxPoint() == make_point(14, 4, 8)); 46 | } 47 | 48 | TEST_CASE("BoundingBox: Checking to see if a box contains a given point") { 49 | BoundingBox box{make_point(5, -2, 0), make_point(11, 4, 7)}; 50 | std::array points {{ 51 | make_point( 5, -2, 0), 52 | make_point(11, 4, 7), 53 | make_point( 8, 1, 3), 54 | make_point( 3, 0, 3), 55 | make_point( 8, -4, 3), 56 | make_point( 8, 1, -1), 57 | make_point(13, 1, 3), 58 | make_point( 8, 5, 3), 59 | make_point( 8, 1, 8) 60 | }}; 61 | std::array incidence {{ 62 | true, true, true, 63 | false, false, false, 64 | false, false, false 65 | }}; 66 | 67 | for (size_t i = 0; i < 9; ++i) 68 | REQUIRE(box.containsPoint(points[i]) == incidence[i]); 69 | } 70 | 71 | TEST_CASE("BoundingBox: Checking to see if a box contains a given box") { 72 | BoundingBox box{make_point(5, -2, 0), make_point(11, 4, 7)}; 73 | std::array boxes {{ 74 | BoundingBox{make_point(5, -2, 0), make_point(11, 4, 7)}, 75 | BoundingBox{make_point(6, -1, 1), make_point(10, 3, 6)}, 76 | BoundingBox{make_point(4, -3, -1), make_point(11, 4, 7)}, 77 | BoundingBox{make_point(6, -1, 1), make_point(12, 5, 8)} 78 | }}; 79 | std::array incidence {{ 80 | true, true, false, false 81 | }}; 82 | 83 | for (size_t i = 0; i < 4; ++i) 84 | REQUIRE(box.containsBox(boxes[i]) == incidence[i]); 85 | } 86 | 87 | TEST_CASE("BoundingBox: Transforming a bounding box") { 88 | const BoundingBox box{make_point(-1, -1, -1), make_point(1, 1, 1)}; 89 | const auto trans = rotation_x(math_constants::pi_by_four<>) * rotation_y(math_constants::pi_by_four<>); 90 | const auto box2 = box.transform(trans); 91 | REQUIRE(box2.getMinPoint() == make_point(-1.4142, -1.7071, -1.7071)); 92 | REQUIRE(box2.getMaxPoint() == make_point( 1.4142, 1.7071, 1.7071)); 93 | } 94 | 95 | TEST_CASE("BoundingBox: Intersecting a ray with a bounding box at the origin") { 96 | const BoundingBox box{make_point(-1, -1, -1), make_point(1, 1, 1)}; 97 | 98 | std::array origins {{ 99 | make_point( 5, 0.5, 0), 100 | make_point(-5, 0.5, 0), 101 | make_point( 0.5, 5, 0), 102 | make_point( 0.5, -5, 0), 103 | make_point( 0.5, 0, 5), 104 | make_point( 0.5, 0, -5), 105 | make_point( 0, 0.5, 0), 106 | make_point(-2, 0, 0), 107 | make_point( 0, -2, 0), 108 | make_point( 0, 0, -2), 109 | make_point( 2, 0, 2), 110 | make_point( 0, 2, 2), 111 | make_point( 2, 2, 0) 112 | }}; 113 | 114 | std::array directions {{ 115 | make_vector(-1, 0, 0), 116 | make_vector( 1, 0, 0), 117 | make_vector( 0, -1, 0), 118 | make_vector( 0, 1, 0), 119 | make_vector( 0, 0, -1), 120 | make_vector( 0, 0, 1), 121 | make_vector( 0, 0, 1), 122 | make_vector( 2, 4, 6), 123 | make_vector( 6, 2, 4), 124 | make_vector( 4, 6, 2), 125 | make_vector( 0, 0, -1), 126 | make_vector( 0, -1, 0), 127 | make_vector(-1, 0, 0) 128 | }}; 129 | 130 | std::array results {{ 131 | true, true, true, true, true, true, true, 132 | false, false, false, false, false, false 133 | }}; 134 | 135 | for (size_t i = 0; i < 13; ++i) { 136 | const Ray r{origins[i], directions[i].normalize()}; 137 | REQUIRE(box.intersects(r) == results[i]); 138 | } 139 | } 140 | 141 | TEST_CASE("BoundingBox: Intersecting a ray with a non-cubic bounding box") { 142 | const BoundingBox box{make_point(5, -2, 0), make_point(11, 4, 7)}; 143 | 144 | std::array origins {{ 145 | make_point(15, 1, 2), 146 | make_point(-5, -1, 4), 147 | make_point( 7, 6, 5), 148 | make_point( 9, -5, 6), 149 | make_point( 8, 2, 12), 150 | make_point( 6, 0, -5), 151 | make_point( 8, 1, 3.5), 152 | make_point( 9, -1, -8), 153 | make_point( 8, 3, -4), 154 | make_point( 9, -1, -2), 155 | make_point( 4, 0, 9), 156 | make_point( 8, 6, -1), 157 | make_point(12, 5, 4) 158 | }}; 159 | 160 | std::array directions {{ 161 | make_vector(-1, 0, 0), 162 | make_vector( 1, 0, 0), 163 | make_vector( 0, -1, 0), 164 | make_vector( 0, 1, 0), 165 | make_vector( 0, 0, -1), 166 | make_vector( 0, 0, 1), 167 | make_vector( 0, 0, 1), 168 | make_vector( 2, 4, 6), 169 | make_vector( 6, 2, 4), 170 | make_vector( 4, 6, 2), 171 | make_vector( 0, 0, -1), 172 | make_vector( 0, -1, 0), 173 | make_vector(-1, 0, 0) 174 | }}; 175 | 176 | std::array results {{ 177 | true, true, true, true, true, true, true, 178 | false, false, false, false, false, false 179 | }}; 180 | 181 | for (size_t i = 0; i < 13; ++i) { 182 | const Ray r{origins[i], directions[i].normalize()}; 183 | REQUIRE(box.intersects(r) == results[i]); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /test/TestCamera.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestCamera.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "affine_transform.h" 10 | #include "camera.h" 11 | #include "canvas.h" 12 | #include "common.h" 13 | #include "constmath.h" 14 | #include "matrix.h" 15 | #include "impl/ray.h" 16 | #include "vec.h" 17 | #include "world.h" 18 | 19 | using namespace raytracer; 20 | 21 | TEST_CASE("Camera: Constructing a camera") { 22 | const Camera c{160, 120, math_constants::pi_by_two}; 23 | REQUIRE(c.getHSize() == 160); 24 | REQUIRE(c.getVSize() == 120); 25 | REQUIRE(ALMOST_EQUALS(c.getFOV(), math_constants::pi_by_two<>)); 26 | REQUIRE(c.getTransformation() == predefined_matrices::I<>); 27 | } 28 | 29 | TEST_CASE("Camera: The pixel size for a horizontal canvas") { 30 | const Camera c{200, 125, math_constants::pi_by_two<>}; 31 | REQUIRE(ALMOST_EQUALS(c.getPixelSize(), 0.01)); 32 | } 33 | 34 | TEST_CASE("Camera: The pixel size for a vertical canvas") { 35 | const Camera c{125, 200, math_constants::pi_by_two<>}; 36 | REQUIRE(ALMOST_EQUALS(c.getPixelSize(), 0.01)); 37 | } 38 | 39 | TEST_CASE("Camera: Construct a ray through the centre of the canvas") { 40 | const Camera c{201, 101, math_constants::pi_by_two<>}; 41 | const auto r = c.ray_for_pixel(100, 50); 42 | REQUIRE(r.getOrigin() == predefined_tuples::zero_point); 43 | REQUIRE(r.getDirection() == make_vector(0, 0, -1)); 44 | } 45 | 46 | TEST_CASE("Camera: Construct a ray through a corner of the canvas") { 47 | const Camera c{201, 101, math_constants::pi_by_two<>}; 48 | const auto r = c.ray_for_pixel(0, 0); 49 | REQUIRE(r.getOrigin() == predefined_tuples::zero_point); 50 | REQUIRE(r.getDirection() == make_vector(0.66519, 0.33259, -0.66851)); 51 | } 52 | 53 | TEST_CASE("Camera: Construct a ray when the camera is transformed") { 54 | const Camera c{201, 101, math_constants::pi_by_two<>, 55 | rotation_y(math_constants::pi_by_four<>) * translation(0, -2, 5)}; 56 | const auto r = c.ray_for_pixel(100, 50); 57 | const auto sqrt2_by_2 = const_sqrtd(2) / 2; 58 | REQUIRE(r.getOrigin() == make_point(0, 2, -5)); 59 | REQUIRE(r.getDirection() == make_vector(sqrt2_by_2, 0, -sqrt2_by_2)); 60 | } 61 | 62 | TEST_CASE("Camera: Rendering a world with a camera") { 63 | const auto w = World::getDefaultWorld(); 64 | const auto from = make_point(0, 0, -5); 65 | const auto to = predefined_tuples::zero_point; 66 | const auto up = predefined_tuples::y1; 67 | const Camera c{11, 11, math_constants::pi_by_two<>, view_transform(from, to, up)}; 68 | const auto image = c.render(w); 69 | REQUIRE(image[5][5] == make_colour(0.38066, 0.47583, 0.2855)); 70 | 71 | } -------------------------------------------------------------------------------- /test/TestCanvas.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestCanvas.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "canvas.h" 14 | #include "vec.h" 15 | 16 | using namespace raytracer; 17 | 18 | constexpr int width = 10; 19 | constexpr int height = 10; 20 | 21 | TEST_CASE("Canvas: Canvas initializes to black") { 22 | Canvas c{width, height}; 23 | for (auto i=0; i < width; ++i) 24 | for (auto j=0; j < height; ++j) 25 | REQUIRE(c[i][j] == predefined_colours::black); 26 | } 27 | 28 | TEST_CASE("Canvas: Canvas can be written to") { 29 | Canvas c{width, height}; 30 | 31 | constexpr std::array colours{ 32 | predefined_colours::red, 33 | predefined_colours::green, 34 | predefined_colours::blue 35 | }; 36 | 37 | for (auto i=0; i < width; ++i) 38 | for (auto j=0; j < width; ++j) 39 | c[i][j] = colours[(i + j) % 3]; 40 | 41 | for (auto i=0; i < width; ++i) 42 | for (auto j=0; j < height; ++j) 43 | REQUIRE((c[i][j]) == colours[(i + j) % 3]); 44 | } 45 | 46 | TEST_CASE("Canvas: Canvas outputs as PPM") { 47 | Canvas c{5, 3}; 48 | c[0][0] = make_colour( 1.5, 0 , 0); 49 | c[2][1] = make_colour( 0 , 0.5, 0); 50 | c[4][2] = make_colour(-0.5, 0 , 1); 51 | 52 | auto s = "P3\n5 3\n" + std::to_string(colour_constants::maxvalue) + '\n' + 53 | "255 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" 54 | "0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n" 55 | "0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n"; 56 | 57 | std::stringstream ostr; 58 | ostr << c; 59 | REQUIRE(s == ostr.str()); 60 | } 61 | 62 | TEST_CASE("Canvas: Canvas PPM file truncates at 70 characters") { 63 | Canvas c{10, 2}; 64 | const auto colour = make_colour(1, 0.8, 0.6); 65 | for (auto i=0; i < 10; ++i) 66 | for (auto j=0; j < 2; ++j) 67 | c[i][j] = colour; 68 | 69 | auto s = "P3\n10 2\n" + std::to_string(colour_constants::maxvalue) + '\n' + 70 | "255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n" 71 | "153 255 204 153 255 204 153 255 204 153 255 204 153\n" 72 | "255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n" 73 | "153 255 204 153 255 204 153 255 204 153 255 204 153\n"; 74 | 75 | std::stringstream ostr; 76 | ostr << c; 77 | REQUIRE(s == ostr.str()); 78 | } -------------------------------------------------------------------------------- /test/TestColour.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestColour.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "common.h" 10 | #include "vec.h" 11 | 12 | using namespace raytracer; 13 | 14 | TEST_CASE("Colour: Colours are RGB tuples") { 15 | constexpr Colour rgb{-0.5, 0.4, 1.7}; 16 | REQUIRE(ALMOST_EQUALS(rgb[colour_constants::r], -0.5)); 17 | REQUIRE(ALMOST_EQUALS(rgb[colour_constants::g], 0.4)); 18 | REQUIRE(ALMOST_EQUALS(rgb[colour_constants::b], 1.7)); 19 | } 20 | 21 | TEST_CASE("Colour: Colours can be added") { 22 | constexpr Colour c1{0.9, 0.6, 0.75}; 23 | constexpr Colour c2{0.7, 0.1, 0.25}; 24 | [[maybe_unused]] constexpr auto res1 = c1 + c2; 25 | [[maybe_unused]] constexpr auto res2 = c1 + c2 == Colour{1.6, 0.7, 1.0}; 26 | REQUIRE(c1 + c2 == Colour{1.6, 0.7, 1.0}); 27 | } 28 | 29 | TEST_CASE("Colour: Colours can be subtracted") { 30 | constexpr Colour c1{0.9, 0.6, 0.75}; 31 | constexpr Colour c2{0.7, 0.1, 0.25}; 32 | [[maybe_unused]] constexpr auto res1 = c1 - c2; 33 | [[maybe_unused]] constexpr auto res2 = c1 - c2 == Colour{0.2, 0.5, 0.5}; 34 | REQUIRE(c1 - c2 == Colour{0.2, 0.5, 0.5}); 35 | } 36 | 37 | TEST_CASE("Colour: Colours can be multiplied by a scalar") { 38 | constexpr Colour c{0.2, 0.3, 0.4}; 39 | [[maybe_unused]] constexpr auto res1 = 2 * c; 40 | [[maybe_unused]] constexpr auto res2 = 2 * c == Colour{0.4, 0.6, 0.8}; 41 | REQUIRE(2 * c == Colour{0.4, 0.6, 0.8}); 42 | } 43 | 44 | TEST_CASE("Colour: Colours can be multiplied by the hadamard product") { 45 | constexpr Colour c1{1 , 0.2, 0.4}; 46 | constexpr Colour c2{0.9, 1, 0.1}; 47 | [[maybe_unused]] constexpr auto res1 = c1 * c2; 48 | [[maybe_unused]] constexpr auto res2 = c1 * c2 == Colour{0.9, 0.2, 0.04}; 49 | REQUIRE(c1 * c2 == Colour{0.9, 0.2, 0.04}); 50 | } 51 | 52 | TEST_CASE("Colour: Colours should be able to determine when they are valid") { 53 | constexpr Colour c{0.5, 0, 1}; 54 | [[maybe_unused]] constexpr auto res = c.isValidColour(); 55 | REQUIRE(c.isValidColour()); 56 | } 57 | 58 | TEST_CASE("Colour: Colours should be able to determine when they are invalid") { 59 | constexpr Colour c{0.5, 0, 1.1}; 60 | [[maybe_unused]] constexpr auto res = c.isValidColour(); 61 | REQUIRE_FALSE(c.isValidColour()); 62 | } -------------------------------------------------------------------------------- /test/TestCone.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestCone.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "bounding_box.h" 14 | #include "intersection.h" 15 | #include "ray.h" 16 | #include "vec.h" 17 | 18 | // A necessary evil to test the private members. 19 | #define private public 20 | #include "cone.h" 21 | 22 | using namespace raytracer; 23 | using namespace raytracer::impl; 24 | using namespace raytracer::shapes; 25 | 26 | TEST_CASE("Cone: A ray intersecting a cone") { 27 | const auto c = Cone::createCone(); 28 | 29 | std::array origins{make_point(0, 0, -5), 30 | make_point(0, 0, -5), 31 | make_point(1, 1, -5)}; 32 | 33 | std::array directions{predefined_tuples::z1, 34 | make_vector(1, 1, 1), 35 | make_vector(-0.5, -1, 1)}; 36 | 37 | std::array t0s{5, 8.66025, 4.55006}; 38 | std::array t1s{5, 8.66025, 49.44994}; 39 | 40 | for (size_t i = 0; i < 3; ++i) { 41 | const auto &origin = origins[i]; 42 | const auto direction = directions[i].normalize(); 43 | const auto t0 = t0s[i]; 44 | const auto t1 = t1s[i]; 45 | 46 | const Ray ray{origin, direction}; 47 | const auto xs = c->localIntersection(ray); 48 | REQUIRE(xs.size() == 2); 49 | REQUIRE(ALMOST_EQUALS(xs[0].getT(), t0)); 50 | REQUIRE(ALMOST_EQUALS(xs[1].getT(), t1)); 51 | } 52 | } 53 | 54 | TEST_CASE("Cone: The normal vector on a cone") { 55 | const auto c = Cone::createCone(); 56 | 57 | constexpr std::array origins{make_point(0, 0, 0), 58 | make_point(1, 1, 1), 59 | make_point(-1, -1, 0)}; 60 | constexpr std::array normals{make_vector(0, 0, 0), 61 | make_vector(1, -math_constants::sqrt2, 1), 62 | make_vector(-1, 1, 0)}; 63 | 64 | for (size_t i = 0; i < 3; ++i) { 65 | const auto &origin = origins[i]; 66 | const auto &normal = c->localNormalAt(origin); 67 | REQUIRE(normal == normals[i]); 68 | } 69 | } 70 | 71 | TEST_CASE("Cone: The default minimum and maximum for a cone") { 72 | const auto c = Cone::createCone(); 73 | REQUIRE(c->getMinimumY() == -std::numeric_limits::infinity()); 74 | REQUIRE(c->getMaximumY() == std::numeric_limits::infinity()); 75 | } 76 | 77 | TEST_CASE("Cone: Intersecting a constrained cone") { 78 | auto c = Cone::createCone(); 79 | c->setMinimumY(1); 80 | c->setMaximumY(2); 81 | 82 | constexpr std::array points{make_point(0, 1.5, 0), 83 | make_point(0, 3, -5), 84 | make_point(0, 0, -5), 85 | make_point(0, 2, -5), 86 | make_point(0, 1, -5), 87 | make_point(0, 1.5, 2)}; 88 | constexpr std::array directions{make_vector(0.1, 1, 0), 89 | predefined_tuples::z1, 90 | predefined_tuples::z1, 91 | predefined_tuples::z1, 92 | predefined_tuples::z1, 93 | predefined_tuples::z1}; 94 | 95 | constexpr std::array count{0, 0, 0, 0, 0, 2}; 96 | 97 | for (size_t i = 0; i < 6; ++i) { 98 | const auto direction = directions[i].normalize(); 99 | const Ray ray{points[i], direction}; 100 | const auto xs = c->localIntersection(ray); 101 | REQUIRE(xs.size() == count[i]); 102 | } 103 | } 104 | 105 | TEST_CASE("Cone: The default capped value for a cone") { 106 | const auto c = Cone::createCone(); 107 | REQUIRE(!c->isCapped()); 108 | } 109 | 110 | TEST_CASE("Cone: Intersecting the caps of a closed cone") { 111 | auto c = Cone::createCone(); 112 | c->setMinimumY(-0.5); 113 | c->setMaximumY( 0.5); 114 | c->setCapped(true); 115 | 116 | constexpr std::array points{make_point(0, 0, -5), 117 | make_point(0, 0, -0.25), 118 | make_point(0, 0, -0.25)}; 119 | constexpr std::array directions{predefined_tuples::y1, 120 | make_vector(0, 1, 1), 121 | predefined_tuples::y1}; 122 | 123 | constexpr std::array count{0, 2, 4}; 124 | 125 | for (size_t i = 0; i < 3; ++i) { 126 | const auto direction = directions[i].normalize(); 127 | const Ray ray{points[i], direction}; 128 | const auto xs = c->localIntersection(ray); 129 | REQUIRE(xs.size() == count[i]); 130 | } 131 | } 132 | 133 | TEST_CASE("Cone: The normal vector on a cone's end caps") { 134 | auto c = Cone::createCone(); 135 | c->setMinimumY(1); 136 | c->setMaximumY(2); 137 | c->setCapped(true); 138 | 139 | constexpr std::array points{make_point(0, 1, 0), 140 | make_point(0.5, 1, 0), 141 | make_point(0, 1, 0.5), 142 | make_point(0, 2, 0), 143 | make_point(0.5, 2, 0), 144 | make_point(0, 2, 0.5)}; 145 | constexpr std::array normals{make_vector(0, -1, 0), 146 | make_vector(0, -1, 0), 147 | make_vector(0, -1, 0), 148 | predefined_tuples::y1, 149 | predefined_tuples::y1, 150 | predefined_tuples::y1}; 151 | 152 | for (size_t i = 0; i < 6; ++i) 153 | REQUIRE(c->localNormalAt(points[i]) == normals[i]); 154 | } 155 | 156 | TEST_CASE("Cone: A bounded cone has a bounding box") { 157 | auto c = Cone::createCone(); 158 | c->setMinimumY(-5); 159 | c->setMaximumY(3); 160 | 161 | const auto box = c->bounds(); 162 | REQUIRE(box.getMinPoint() == make_point(-5, -5, -5)); 163 | REQUIRE(box.getMaxPoint() == make_point(5, 3, 5)); 164 | } -------------------------------------------------------------------------------- /test/TestCube.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestCube.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "bounding_box.h" 13 | #include "intersection.h" 14 | #include "ray.h" 15 | #include "vec.h" 16 | 17 | // A necessary evil to test the private members. 18 | #define private public 19 | #include "cube.h" 20 | 21 | using namespace raytracer; 22 | using namespace raytracer::impl; 23 | using namespace raytracer::shapes; 24 | 25 | TEST_CASE("Cube: A ray intersecting a cube") { 26 | const auto c = Cube::createCube(); 27 | 28 | /** 29 | * Cases: 30 | * 0. +x 31 | * 1. -x 32 | * 2. +y 33 | * 3. -y 34 | * 4. +z 35 | * 5. -z 36 | * 6. inside 37 | */ 38 | 39 | constexpr std::array origins{make_point( 5, 0.5, 0), 40 | make_point(-5, 0.5, 0), 41 | make_point( 0.5, 5, 0), 42 | make_point( 0.5, -5, 0), 43 | make_point( 0.5, 0, 5), 44 | make_point( 0.5, 0, -5), 45 | make_point( 0, 0.5, 0)}; 46 | constexpr std::array directions{make_vector(-1, 0, 0), 47 | make_vector( 1, 0, 0), 48 | make_vector( 0, -1, 0), 49 | make_vector( 0, 1, 0), 50 | make_vector( 0, 0, -1), 51 | make_vector( 0, 0, 1), 52 | make_vector( 0, 0, 1)}; 53 | std::array t0s{4, 4, 4, 4, 4, 4, -1}; 54 | std::array t1s{6, 6, 6, 6, 6, 6, 1}; 55 | 56 | for (size_t i = 0; i < 7; ++i) { 57 | const auto &origin = origins[i]; 58 | const auto &direction = directions[i]; 59 | const auto t0 = t0s[i]; 60 | const auto t1 = t1s[i]; 61 | 62 | const Ray ray{origin, direction}; 63 | const auto xs = c->localIntersection(ray); 64 | REQUIRE(xs.size() == 2); 65 | REQUIRE(ALMOST_EQUALS(xs[0].getT(), t0)); 66 | REQUIRE(ALMOST_EQUALS(xs[1].getT(), t1)); 67 | } 68 | } 69 | 70 | TEST_CASE("Cube: A ray misses a cube") { 71 | const auto c = Cube::createCube(); 72 | 73 | constexpr std::array origins{make_point(-2, 0, 0), 74 | make_point( 0, -2, 0), 75 | make_point( 0, 0, -2), 76 | make_point( 2, 0, 2), 77 | make_point( 0, 2, 2), 78 | make_point( 2, 2, 0)}; 79 | constexpr std::array directions{make_vector(0.2673, 0.5345, 0.8018), 80 | make_vector(0.8018, 0.2673, 0.5345), 81 | make_vector(0.5345, 0.8018, 0.2673), 82 | make_vector( 0, 0, -1), 83 | make_vector( 0, -1, 0), 84 | make_vector(-1, 0, 0)}; 85 | 86 | for (size_t i = 0; i < 6; ++i) { 87 | const auto &origin = origins[i]; 88 | const auto &direction = directions[i]; 89 | 90 | const Ray ray{origin, direction}; 91 | const auto xs = c->localIntersection(ray); 92 | REQUIRE(xs.empty()); 93 | } 94 | } 95 | 96 | TEST_CASE("Cube: The normal on the surface of a cube") { 97 | const auto c = Cube::createCube(); 98 | 99 | constexpr std::array origins{make_point( 1, 0.5, -0.8), 100 | make_point(-1, -0.2, 0.9), 101 | make_point(-0.4, 1, -0.1), 102 | make_point( 0.3, -1, -0.7), 103 | make_point(-0.6, 0.3, 1), 104 | make_point( 0.4, 0.4, -1), 105 | make_point( 1, 1, 1), 106 | make_point(-1, -1, -1)}; 107 | constexpr std::array normals{make_vector( 1, 0, 0), 108 | make_vector(-1, 0, 0), 109 | make_vector( 0, 1, 0), 110 | make_vector( 0, -1, 0), 111 | make_vector( 0, 0, 1), 112 | make_vector( 0, 0, -1), 113 | make_vector( 1, 0, 0), 114 | make_vector(-1, 0, 0)}; 115 | 116 | for (size_t i = 0; i < 8; ++i) { 117 | const auto &origin = origins[i]; 118 | const auto &normal = c->localNormalAt(origin); 119 | REQUIRE(normal == normals[i]); 120 | } 121 | } 122 | 123 | TEST_CASE("Cube: A cube has a bounding box") { 124 | const auto c = Cube::createCube(); 125 | const auto box = c->bounds(); 126 | REQUIRE(box.getMinPoint() == make_point(-1, -1, -1)); 127 | REQUIRE(box.getMaxPoint() == make_point(1, 1, 1)); 128 | } -------------------------------------------------------------------------------- /test/TestGroup.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestGroup.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "cylinder.h" 16 | #include "intersection.h" 17 | #include "ray.h" 18 | #include "sphere.h" 19 | #include "vec.h" 20 | 21 | // A necessary evil to test the private members. 22 | #define private public 23 | #include "group.h" 24 | 25 | using namespace raytracer; 26 | using namespace raytracer::impl; 27 | using namespace raytracer::shapes; 28 | 29 | TEST_CASE("Group: Creating a new group") { 30 | const auto g = Group::createGroup(); 31 | REQUIRE(g->getShapes().empty()); 32 | REQUIRE(g->getTransformation() == predefined_matrices::I); 33 | } 34 | 35 | TEST_CASE("Group: Adding a child to a group") { 36 | auto g = Group::createGroup(); 37 | const auto s = TestShape::createTestShape(); 38 | g->addAll(s); 39 | REQUIRE(g->getShapes().size() == 1); 40 | REQUIRE(g->getShapes()[0] == s); 41 | REQUIRE(s->getParent() == g); 42 | g->clearShapes(); 43 | } 44 | 45 | TEST_CASE("Group: Intersecting a ray with an empty group") { 46 | const auto g = Group::createGroup(); 47 | Ray ray{predefined_tuples::zero_point, predefined_tuples::z1}; 48 | const auto xs = g->localIntersection(ray); 49 | REQUIRE(xs.empty()); 50 | } 51 | 52 | TEST_CASE("Group: Intersecting a ray with a non-empty group") { 53 | auto g = Group::createGroup(); 54 | const auto s1 = Sphere::createSphere(); 55 | auto s2 = Sphere::createSphere(); 56 | s2->setTransformation(translation(0, 0, -3)); 57 | auto s3 = Sphere::createSphere(); 58 | s3->setTransformation(translation(5, 0, 0)); 59 | g->addAll(s1, s2, s3); 60 | 61 | const Ray ray{make_point(0, 0, -5), predefined_tuples::z1}; 62 | const auto xs = g->localIntersection(ray); 63 | 64 | REQUIRE(xs.size() == 4); 65 | REQUIRE(xs[0].getObject() == s2); 66 | REQUIRE(xs[1].getObject() == s2); 67 | REQUIRE(xs[2].getObject() == s1); 68 | REQUIRE(xs[3].getObject() == s1); 69 | } 70 | 71 | TEST_CASE("Group: Intersecting a transformed group") { 72 | auto g = Group::createGroup(); 73 | g->setTransformation(scale(2, 2, 2)); 74 | auto s = Sphere::createSphere(); 75 | s->setTransformation(translation(5, 0, 0)); 76 | g->add(s); 77 | 78 | const Ray ray{make_point(10, 0, -10), predefined_tuples::z1}; 79 | const auto xs = g->intersect(ray); 80 | REQUIRE(xs.size() == 2); 81 | } 82 | 83 | TEST_CASE("Group: localNormalAt not supported") { 84 | REQUIRE_THROWS(Group::createGroup()->localNormalAt(predefined_tuples::zero_point)); 85 | } 86 | 87 | TEST_CASE("Group: A group has a bounding box that contains its children") { 88 | auto s = Sphere::createSphere(); 89 | s->setTransformation(translation(2, 5, -3) * scale(2, 2, 2)); 90 | 91 | auto c = Cylinder::createCylinder(); 92 | c->setMinimumY(-2); 93 | c->setMaximumY(2); 94 | c->setTransformation(translation(-4, -1, 4) * scale(0.5, 1, 0.5)); 95 | 96 | auto g = Group::createGroup(); 97 | g->addAll(s, c); 98 | 99 | const auto box = g->bounds(); 100 | REQUIRE(box.getMinPoint() == make_point(-4.5, -3, -5)); 101 | REQUIRE(box.getMaxPoint() == make_point(4, 7, 4.5)); 102 | } 103 | 104 | TEST_CASE("Group: Intersecting ray and group doesn't test children if box is missed") { 105 | const auto c = TestShape::createTestShape(); 106 | auto g = Group::createGroup(); 107 | g->add(c); 108 | 109 | const Ray ray{make_point(0, 0, -5), make_vector(0, 1, 0)}; 110 | g->intersect(ray); 111 | 112 | // The TestShape saves any ray that intersects it, so if it didn't intersect it as we expect, then 113 | // the saved ray would be the default ray. 114 | REQUIRE(c->saved_ray == Ray{}); 115 | } 116 | 117 | TEST_CASE("Group: Intersecting ray and group tests children if box is hit") { 118 | const auto c = TestShape::createTestShape(); 119 | auto g = Group::createGroup(); 120 | g->add(c); 121 | 122 | const Ray ray{make_point(0, 0, -5), make_vector(0, 0, 1)}; 123 | g->intersect(ray); 124 | 125 | // The TestShape saves any ray that intersects it, so if it intersected it as we expect it to, then 126 | // it would be the saved ray. 127 | REQUIRE(c->saved_ray == ray); 128 | } -------------------------------------------------------------------------------- /test/TestLights.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestLights.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "vec.h" 10 | #include "pointlight.h" 11 | 12 | using namespace raytracer; 13 | 14 | TEST_CASE("Lights: A point light has a position and intensity") { 15 | constexpr auto intensity = make_colour(1, 1, 1); 16 | constexpr auto position = make_point(0, 0, 0); 17 | constexpr auto light = PointLight(position, intensity); 18 | REQUIRE(light.getPosition() == position); 19 | REQUIRE(light.getIntensity() == intensity); 20 | } 21 | -------------------------------------------------------------------------------- /test/TestMaterial.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestMaterial.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "constmath.h" 12 | #include "material.h" 13 | #include "patterns/pattern.h" 14 | #include "pointlight.h" 15 | #include "patterns/solidpattern.h" 16 | #include "shapes/sphere.h" 17 | #include "patterns/stripepattern.h" 18 | 19 | using namespace raytracer; 20 | using namespace raytracer::shapes; 21 | 22 | TEST_CASE("Material: The default material") { 23 | const Material m; 24 | REQUIRE(*m.getPattern() == SolidPattern{predefined_colours::white}); 25 | REQUIRE(ALMOST_EQUALS(m.getAmbient(), 0.1)); 26 | REQUIRE(ALMOST_EQUALS(m.getDiffuse(), 0.9)); 27 | REQUIRE(ALMOST_EQUALS(m.getSpecular(), 0.9)); 28 | REQUIRE(m.getShininess() == 200); 29 | } 30 | 31 | TEST_CASE("Material: Lighting with the eye between the light and the surface") { 32 | const Material m; 33 | const auto position = predefined_tuples::zero_point; 34 | const auto eyev = make_vector(0, 0, -1); 35 | const auto normalv = make_vector(0, 0, -1); 36 | const PointLight light{make_point(0, 0, -10), predefined_colours::white}; 37 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, false); 38 | REQUIRE(result == make_colour(1.9, 1.9, 1.9)); 39 | } 40 | 41 | TEST_CASE("Material: Lighting with the eye between light and surface, eye offset 45 deg") { 42 | const Material m; 43 | const auto position = predefined_tuples::zero_point; 44 | const auto sqrt2by2 = const_sqrtd(2) / 2; 45 | const auto eyev = make_vector(0, sqrt2by2, -sqrt2by2); 46 | const auto normalv = make_vector(0, 0, -1); 47 | const PointLight light{make_point(0, 0, -10), predefined_colours::white}; 48 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, false); 49 | REQUIRE(result == predefined_colours::white); 50 | } 51 | 52 | TEST_CASE("Material: Lighting with eye opposite surface, light offset 45 deg") { 53 | const Material m; 54 | const auto position = predefined_tuples::zero_point; 55 | const auto eyev = make_vector(0, 0, -1); 56 | const auto normalv = make_vector(0, 0, -1); 57 | const PointLight light{make_point(0, 10, -10), predefined_colours::white}; 58 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, false); 59 | REQUIRE(result == make_colour(0.7364, 0.7364, 0.7364)); 60 | } 61 | 62 | TEST_CASE("Material: Lighting with eye in the path of the reflection vector") { 63 | const Material m; 64 | const auto position = predefined_tuples::zero_point; 65 | const auto sqrt2by2 = const_sqrtd(2) / 2; 66 | const auto eyev = make_vector(0, -sqrt2by2, -sqrt2by2); 67 | const auto normalv = make_vector(0, 0, -1); 68 | const PointLight light{make_point(0, 10, -10), predefined_colours::white}; 69 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, false); 70 | REQUIRE(result == make_colour(1.6364, 1.6364, 1.6364)); 71 | } 72 | 73 | TEST_CASE("Material: Lighting with the light behind the surface") { 74 | const Material m; 75 | const auto position = predefined_tuples::zero_point; 76 | const auto eyev = make_vector(0, 0, -1); 77 | const auto normalv = make_vector(0, 0, -1); 78 | const PointLight light{make_point(0, 0, 10), predefined_colours::white}; 79 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, false); 80 | REQUIRE(result == make_colour(0.1, 0.1, 0.1)); 81 | } 82 | 83 | TEST_CASE("Material: Lighting with the surface in shadow") { 84 | const Material m; 85 | const auto position = predefined_tuples::zero_point; 86 | const auto eyev = -predefined_tuples::z1; 87 | const auto normalv = -predefined_tuples::z1; 88 | const PointLight light{make_point(0, 0, -10), predefined_colours::white}; 89 | const bool in_shadow = true; 90 | const auto result = m.lighting(light, Sphere::createSphere(), position, eyev, normalv, in_shadow); 91 | REQUIRE(result == make_colour(0.1, 0.1, 0.1)); 92 | } 93 | 94 | TEST_CASE("Material: Lighting with a pattern applied") { 95 | const Material m{std::make_shared(), 1, 0, 0, Material::DEFAULT_SHININESS}; 96 | const auto eyev = make_vector(0, 0, -1); 97 | const auto normalv = make_vector(0, 0, -1); 98 | const PointLight light{make_point(0, 0, -10), predefined_colours::white}; 99 | const auto c1 = m.lighting(light, Sphere::createSphere(), make_point(0.9, 0, 0), eyev, normalv, false); 100 | const auto c2 = m.lighting(light, Sphere::createSphere(), make_point(1.1, 0, 0), eyev, normalv, false); 101 | REQUIRE(c1 == predefined_colours::white); 102 | REQUIRE(c2 == predefined_colours::black); 103 | } 104 | 105 | TEST_CASE("Material: Reflectivity for the default material") { 106 | REQUIRE(Material{}.getReflectivity() == 0); 107 | } 108 | -------------------------------------------------------------------------------- /test/TestPattern.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestPattern.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "patterns/checkerpattern.h" 13 | #include "patterns/gradientpattern.h" 14 | #include "patterns/pattern.h" 15 | #include "patterns/ringpattern.h" 16 | #include "shapes/sphere.h" 17 | #include "patterns/stripepattern.h" 18 | #include "vec.h" 19 | #include "TestPattern.h" 20 | 21 | using namespace raytracer; 22 | using namespace raytracer::shapes; 23 | 24 | TEST_CASE("Pattern: Creating default stripe pattern") { 25 | const StripePattern pattern; 26 | REQUIRE(pattern.getColours()[0] == predefined_colours::white); 27 | REQUIRE(pattern.getColours()[1] == predefined_colours::black); 28 | } 29 | 30 | TEST_CASE("Pattern: A stripe pattern is constant in y") { 31 | const StripePattern pattern{{predefined_colours::white, predefined_colours::black}}; 32 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 33 | REQUIRE(pattern.colourAt(make_point(0, 1, 0)) == predefined_colours::white); 34 | REQUIRE(pattern.colourAt(make_point(0, 2, 0)) == predefined_colours::white); 35 | } 36 | 37 | TEST_CASE("Pattern: A stripe pattern is constant in z") { 38 | const StripePattern pattern; 39 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 40 | REQUIRE(pattern.colourAt(make_point(0, 0, 1)) == predefined_colours::white); 41 | REQUIRE(pattern.colourAt(make_point(0, 0, 2)) == predefined_colours::white); 42 | } 43 | 44 | TEST_CASE("Pattern: A stripe pattern alternates in x") { 45 | const StripePattern pattern{{predefined_colours::red, predefined_colours::green, predefined_colours::blue}}; 46 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::red); 47 | REQUIRE(pattern.colourAt(make_point(1, 0, 0)) == predefined_colours::green); 48 | REQUIRE(pattern.colourAt(make_point(2, 0, 0)) == predefined_colours::blue); 49 | REQUIRE(pattern.colourAt(make_point(3, 0, 0)) == predefined_colours::red); 50 | REQUIRE(pattern.colourAt(make_point(4, 0, 0)) == predefined_colours::green); 51 | REQUIRE(pattern.colourAt(make_point(5, 0, 0)) == predefined_colours::blue); 52 | } 53 | 54 | TEST_CASE("Pattern: Pattern with an object transformation") { 55 | auto s = Sphere::createSphere(); 56 | s->setTransformation(scale(2, 2, 2)); 57 | 58 | const auto pattern = std::make_shared(); 59 | auto material = std::make_shared(pattern); 60 | s->setMaterial(material); 61 | 62 | const auto c = pattern->colourAtObject(s, make_point(2, 3, 4)); 63 | REQUIRE(c == make_colour(1, 1.5, 2)); 64 | } 65 | 66 | TEST_CASE("Pattern: Pattern with a pattern transformation") { 67 | std::shared_ptr pattern = std::make_shared(); 68 | pattern->setTransformation(scale(2, 2, 2)); 69 | const auto c = pattern->colourAtObject(Sphere::createSphere(), make_point(2, 3, 4)); 70 | REQUIRE(c == make_colour(1, 1.5, 2)); 71 | 72 | } 73 | 74 | TEST_CASE("Pattern: Pattern with both an object and a pattern transformation") { 75 | auto s = Sphere::createSphere(); 76 | s->setTransformation(scale(2, 2, 2)); 77 | 78 | std::shared_ptr pattern = std::make_shared(); 79 | pattern->setTransformation(translation(0.5, 1, 1.5)); 80 | 81 | const auto c = pattern->colourAtObject(s, make_point(2.5, 3, 3.5)); 82 | REQUIRE(c == make_colour(0.75, 0.5, 0.25)); 83 | } 84 | 85 | TEST_CASE("Pattern: Gradient linearly interpolates between colours") { 86 | GradientPattern pattern; 87 | REQUIRE(pattern.colourAt(make_point(0, 0, 0)) == predefined_colours::white); 88 | REQUIRE(pattern.colourAt(make_point(0.25, 0, 0)) == make_colour(0.75, 0.75, 0.75)); 89 | REQUIRE(pattern.colourAt(make_point(0.5, 0, 0)) == make_colour(0.5, 0.5, 0.5)); 90 | REQUIRE(pattern.colourAt(make_point(0.75, 0, 0)) == make_colour(0.25, 0.25, 0.25)); 91 | } 92 | 93 | TEST_CASE("Pattern: Ring should extend in both x and z") { 94 | RingPattern pattern; 95 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 96 | REQUIRE(pattern.colourAt(make_point(1, 0, 0)) == predefined_colours::black); 97 | REQUIRE(pattern.colourAt(make_point(0, 0, 1)) == predefined_colours::black); 98 | // 0.708 just slightly more than sqrt(2)/2. 99 | REQUIRE(pattern.colourAt(make_point(0.708, 0, 0.708)) == predefined_colours::black); 100 | } 101 | 102 | TEST_CASE("Pattern: Checkers should repeat in x") { 103 | CheckerPattern pattern; 104 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 105 | REQUIRE(pattern.colourAt(make_point(0.99, 0, 0)) == predefined_colours::white); 106 | REQUIRE(pattern.colourAt(make_point(1.01, 0, 0)) == predefined_colours::black); 107 | } 108 | 109 | TEST_CASE("Pattern: Checkers should repeat in y") { 110 | CheckerPattern pattern; 111 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 112 | REQUIRE(pattern.colourAt(make_point(0, 0.99, 0)) == predefined_colours::white); 113 | REQUIRE(pattern.colourAt(make_point(0, 1.01, 0)) == predefined_colours::black); 114 | } 115 | 116 | TEST_CASE("Pattern: Checkers should repeat in z") { 117 | CheckerPattern pattern; 118 | REQUIRE(pattern.colourAt(predefined_tuples::zero_point) == predefined_colours::white); 119 | REQUIRE(pattern.colourAt(make_point(0, 0, 0.99)) == predefined_colours::white); 120 | REQUIRE(pattern.colourAt(make_point(0, 0, 1.01)) == predefined_colours::black); 121 | } 122 | -------------------------------------------------------------------------------- /test/TestPattern.h: -------------------------------------------------------------------------------- 1 | /** 2 | * TestPattern.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "patterns/pattern.h" 10 | #include "vec.h" 11 | 12 | /** 13 | * This is a class that implements just enough of Pattern to be a concrete implementation 14 | * so as to be used to isolate and test the common characteristics of all subclasses of Pattern. 15 | */ 16 | struct TestPattern final: raytracer::Pattern { 17 | TestPattern() = default; 18 | 19 | template 20 | explicit TestPattern(T&& t): Pattern(t) {} 21 | 22 | const raytracer::Colour colourAt(const raytracer::Tuple &tuple) const noexcept override { 23 | return raytracer::make_colour(tuple[raytracer::tuple_constants::x], 24 | tuple[raytracer::tuple_constants::y], 25 | tuple[raytracer::tuple_constants::z]); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/TestPlane.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestPlane.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "bounding_box.h" 10 | #include "constmath.h" 11 | #include "intersection.h" 12 | #include "plane.h" 13 | #include "ray.h" 14 | #include "vec.h" 15 | 16 | using namespace raytracer; 17 | using namespace raytracer::impl; 18 | using namespace raytracer::shapes; 19 | 20 | TEST_CASE("Plane: The normal of a plane is constant everywhere") { 21 | const auto p = Plane::createPlane(); 22 | const auto n1 = p->normalAt(predefined_tuples::zero_point); 23 | const auto n2 = p->normalAt(make_point(10, 0, -10)); 24 | const auto n3 = p->normalAt(make_point(-5, 0, 150)); 25 | REQUIRE(n1 == predefined_tuples::y1); 26 | REQUIRE(n2 == predefined_tuples::y1); 27 | REQUIRE(n3 == predefined_tuples::y1); 28 | } 29 | 30 | TEST_CASE("Plane: Intersect with a ray parallel to the plane") { 31 | const auto p = Plane::createPlane(); 32 | const Ray ray{make_point(0, 10, 0), predefined_tuples::z1}; 33 | REQUIRE(p->intersect(ray).empty()); 34 | } 35 | 36 | TEST_CASE("Plane: Intersect with a coplanar ray") { 37 | const auto p = Plane::createPlane(); 38 | const Ray ray{predefined_tuples::zero_point, predefined_tuples::z1}; 39 | REQUIRE(p->intersect(ray).empty()); 40 | } 41 | 42 | TEST_CASE("Plane: A ray intersecting a plane from above") { 43 | const auto p = Plane::createPlane(); 44 | const Ray ray{make_point(0, 1, 0), make_vector(0, -1, 0)}; 45 | const auto xs = p->intersect(ray); 46 | REQUIRE(xs.size() == 1); 47 | REQUIRE(ALMOST_EQUALS(xs[0].getT(), 1)); 48 | REQUIRE(xs[0].getObject() == p); 49 | } 50 | 51 | TEST_CASE("Plane: A ray intersecting a plane from below") { 52 | const auto p = Plane::createPlane(); 53 | const Ray ray{make_point(0, -1, 0), predefined_tuples::y1}; 54 | const auto xs = p->intersect(ray); 55 | REQUIRE(xs.size() == 1); 56 | REQUIRE(ALMOST_EQUALS(xs[0].getT(), 1)); 57 | REQUIRE(xs[0].getObject() == p); 58 | } 59 | 60 | TEST_CASE("Plane: A plane has a bounding box") { 61 | const auto p = Plane::createPlane(); 62 | const auto box = p->bounds(); 63 | REQUIRE(box.getMinPoint() == make_point(math_constants::ninf<>, 0, math_constants::ninf<>)); 64 | REQUIRE(box.getMaxPoint() == make_point(math_constants::inf<>, 0, math_constants::inf<>)); 65 | 66 | } -------------------------------------------------------------------------------- /test/TestRay.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestRay.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "affine_transform.h" 10 | #include "transformers.h" 11 | #include "impl/ray.h" 12 | #include "vec.h" 13 | 14 | using namespace raytracer; 15 | using namespace raytracer::impl; 16 | using namespace raytracer::transformers; 17 | 18 | TEST_CASE("Ray: Ray throws an exception if improper arguments are passed to it") { 19 | REQUIRE_THROWS(Ray{predefined_tuples::zero_vector, predefined_tuples::zero_vector}); 20 | REQUIRE_THROWS(Ray{predefined_tuples::zero_point, predefined_tuples::zero_point}); 21 | } 22 | 23 | TEST_CASE("Ray: Ray can calculate its position at time t") { 24 | Ray ray{make_point(2, 3, 4), make_vector(1, 0, 0)}; 25 | REQUIRE(ray.position( 0 ) == make_point(2, 3, 4)); 26 | REQUIRE(ray.position( 1 ) == make_point(3, 3, 4)); 27 | REQUIRE(ray.position(-1 ) == make_point(1, 3, 4)); 28 | REQUIRE(ray.position(2.5) == make_point(4.5, 3, 4)); 29 | } 30 | 31 | TEST_CASE("Ray: Translating a ray") { 32 | const Ray r{make_point(1, 2, 3), make_vector(0, 1, 0)}; 33 | const auto m = translation(3, 4, 5); 34 | const auto r2 = r.transform(m); 35 | REQUIRE(r2.getOrigin() == make_point(4, 6, 8)); 36 | REQUIRE(r2.getDirection() == make_vector(0, 1, 0)); 37 | } 38 | 39 | TEST_CASE("Ray: Scaling a ray") { 40 | const Ray r{make_point(1, 2, 3), make_vector(0, 1, 0)}; 41 | const auto m = scale(2, 3, 4); 42 | const auto r2 = r.transform(m); 43 | REQUIRE(r2.getOrigin() == make_point(2, 6, 12)); 44 | REQUIRE(r2.getDirection() == make_vector(0, 3, 0)); 45 | } 46 | -------------------------------------------------------------------------------- /test/TestShape.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestShape.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include "TestShape.h" 10 | 11 | #include "affine_transform.h" 12 | #include "constmath.h" 13 | #include "group.h" 14 | #include "material.h" 15 | #include "matrix.h" 16 | #include "sphere.h" 17 | 18 | using namespace raytracer; 19 | using namespace raytracer::math_constants; 20 | using namespace raytracer::impl; 21 | using namespace raytracer::shapes; 22 | 23 | TEST_CASE("Shape: The default transformation") { 24 | const auto s = TestShape::createTestShape(); 25 | REQUIRE(s->getTransformation() == predefined_matrices::I<>); 26 | } 27 | 28 | TEST_CASE("Shape: Assigning a transformation") { 29 | auto s = TestShape::createTestShape(); 30 | s->setTransformation(translation(2, 3, 4)); 31 | REQUIRE(s->getTransformation() == translation(2, 3, 4)); 32 | } 33 | 34 | TEST_CASE("Shape: The default material") { 35 | const auto s = TestShape::createTestShape(); 36 | REQUIRE(*s->getMaterial() == Material{}); 37 | } 38 | 39 | TEST_CASE("Shape: Assigning a material") { 40 | auto s = TestShape::createTestShape(); 41 | auto m = std::make_shared(); 42 | m->setAmbient(1.0); 43 | s->setMaterial(m); 44 | 45 | Material m2; 46 | m2.setAmbient(1.0); 47 | 48 | REQUIRE(*s->getMaterial() == m2); 49 | REQUIRE_FALSE(*s->getMaterial() == Material{}); 50 | } 51 | 52 | TEST_CASE("Shape: Intersecting a scaled shape with a ray") { 53 | auto s = TestShape::createTestShape(); 54 | s->setTransformation(scale(2, 2, 2)); 55 | s->intersect(Ray{make_point(0, 0, -5), predefined_tuples::z1}); 56 | REQUIRE(s->saved_ray.getOrigin() == make_point(0, 0, -2.5)); 57 | REQUIRE(s->saved_ray.getDirection() == make_vector(0, 0, 0.5)); 58 | } 59 | 60 | TEST_CASE("Shape: Intersecting a translated shape with a ray") { 61 | auto s = TestShape::createTestShape(); 62 | s->setTransformation(translation(5, 0, 0)); 63 | s->intersect(Ray{make_point(0, 0, -5), predefined_tuples::z1}); 64 | REQUIRE(s->saved_ray.getOrigin() == make_point(-5, 0, -5)); 65 | REQUIRE(s->saved_ray.getDirection() == predefined_tuples::z1); 66 | } 67 | 68 | TEST_CASE("Shape: Computing the normal on a translated shape") { 69 | auto s = TestShape::createTestShape(); 70 | s->setTransformation(translation(0, 1, 0)); 71 | const auto n = s->normalAt(make_point(0, 1.70711, -0.70711)); 72 | REQUIRE(n == make_vector(0, 0.70711, -0.70711)); 73 | } 74 | 75 | TEST_CASE("Shape: Computing the normal on a scaled shape") { 76 | auto s = TestShape::createTestShape(); 77 | s->setTransformation(scale(1.0, 0.5, 1.0)); 78 | const auto n = s->normalAt(make_point(0, sqrt2_by_2, -sqrt2_by_2)); 79 | REQUIRE(n == make_vector(0, 0.97014, -0.24254)); 80 | } 81 | 82 | TEST_CASE("Shape: A shape has a parent attribute") { 83 | const auto s = TestShape::createTestShape(); 84 | REQUIRE(s->getParent() == nullptr); 85 | } 86 | 87 | TEST_CASE("Shape: Converting a point from world to object space") { 88 | auto g1 = Group::createGroup(); 89 | g1->setTransformation(rotation_y(math_constants::pi_by_two<>)); 90 | 91 | auto g2 = Group::createGroup(); 92 | g2->setTransformation(scale(2, 2, 2)); 93 | g1->add(g2); 94 | 95 | auto s = Sphere::createSphere(); 96 | s->setTransformation(translation(5, 0, 0)); 97 | g2->add(s); 98 | 99 | REQUIRE(s->worldToObject(make_point(-2, 0, -10)) == make_point(0, 0, -1)); 100 | } 101 | 102 | TEST_CASE("Shape: Converting a normal from object to world space") { 103 | auto g1 = Group::createGroup(); 104 | g1->setTransformation(rotation_y(math_constants::pi_by_two<>)); 105 | 106 | auto g2 = Group::createGroup(); 107 | g2->setTransformation(scale(1, 2, 3)); 108 | g1->add(g2); 109 | 110 | auto s = Sphere::createSphere(); 111 | s->setTransformation(translation(5, 0, 0)); 112 | g2->add(s); 113 | 114 | constexpr auto i = const_sqrtd(3)/3; 115 | REQUIRE(s->normalToWorld(make_vector(i, i, i)) == make_vector(0.2857, 0.4286, -0.8571)); 116 | } 117 | 118 | TEST_CASE("Shape: Finding the normal on a child object") { 119 | auto g1 = Group::createGroup(); 120 | g1->setTransformation(rotation_y(math_constants::pi_by_two<>)); 121 | 122 | auto g2 = Group::createGroup(); 123 | g2->setTransformation(scale(1, 2, 3)); 124 | g1->add(g2); 125 | 126 | auto s = Sphere::createSphere(); 127 | s->setTransformation(translation(5, 0, 0)); 128 | g2->add(s); 129 | 130 | REQUIRE(s->normalAt(make_point(1.7321, 1.1547, -5.5774)) == make_vector(0.2857, 0.4286, -0.8571)); 131 | } 132 | 133 | TEST_CASE("Shape: Test shape has arbitrary bounds") { 134 | const auto s = TestShape::createTestShape(); 135 | const auto box = s->bounds(); 136 | REQUIRE(box.getMinPoint() == make_point(-1, -1, -1)); 137 | REQUIRE(box.getMaxPoint() == make_point(1, 1, 1)); 138 | } 139 | 140 | TEST_CASE("Shape: Querying a shape's bounding box in its parent's space") { 141 | auto s = Sphere::createSphere(); 142 | s->setTransformation(translation(1, -3, 5) * scale(0.5, 2, 4)); 143 | const auto box = s->parentSpaceBounds(); 144 | REQUIRE(box.getMinPoint() == make_point(0.5, -5, 1)); 145 | REQUIRE(box.getMaxPoint() == make_point(1.5, -1, 9)); 146 | } -------------------------------------------------------------------------------- /test/TestShape.h: -------------------------------------------------------------------------------- 1 | /** 2 | * TestShape.h 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "bounding_box.h" 11 | #include "affine_transform.h" 12 | #include "intersection.h" 13 | #include "material.h" 14 | #include "matrix.h" 15 | #include "ray.h" 16 | #include "shape.h" 17 | #include "vec.h" 18 | 19 | /** 20 | * This is a class that implements just enough of Shape to be a concrete implementation, 21 | * so as to be used to isolate and test the common characteristics of all subclasses of Shape. 22 | */ 23 | class TestShape final: public raytracer::shapes::Shape { 24 | 25 | public: 26 | // Saves the ray passed to localIntersection to make sure the proper transformation happens. 27 | raytracer::impl::Ray saved_ray; 28 | 29 | public: 30 | explicit TestShape(dummy d): Shape{d} {} 31 | 32 | static std::shared_ptr createTestShape() { 33 | std::shared_ptr ts = std::make_shared(dummy{}); 34 | registerInstance(ts); 35 | return ts; 36 | } 37 | 38 | /// Get a bounding box. 39 | raytracer::impl::BoundingBox bounds() const override { 40 | return raytracer::impl::BoundingBox{raytracer::make_point(-1, -1, -1), raytracer::make_point(1, 1, 1)}; 41 | } 42 | 43 | private: 44 | /// Since this has no actual form, it just saves the ray to make sure it is properly translated to object space. 45 | const std::vector localIntersection(const raytracer::impl::Ray &r) 46 | const noexcept override { 47 | // This is an exceptional situation. 48 | const_cast(this)->saved_ray = r; 49 | return {}; 50 | } 51 | 52 | const raytracer::Tuple localNormalAt(const raytracer::Tuple &point) const noexcept override { 53 | return point - raytracer::predefined_tuples::zero_point; 54 | } 55 | }; -------------------------------------------------------------------------------- /test/TestSphere.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * TestSphere.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "affine_transform.h" 12 | #include "bounding_box.h" 13 | #include "constmath.h" 14 | #include "impl/intersection.h" 15 | #include "impl/ray.h" 16 | #include "shapes/sphere.h" 17 | #include "vec.h" 18 | 19 | using namespace raytracer; 20 | using namespace raytracer::impl; 21 | using namespace raytracer::shapes; 22 | 23 | TEST_CASE("Sphere: Sphere must return a pair of points if ray intersects it") { 24 | const auto s = Sphere::createSphere(); 25 | const Ray r{make_point(0, 0, -5), make_vector(0, 0, 1)}; 26 | const auto xs = s->intersect(r); 27 | REQUIRE(xs.size() == 2); 28 | REQUIRE(xs[0].getT() == 4); 29 | REQUIRE(xs[1].getT() == 6); 30 | } 31 | 32 | TEST_CASE("Sphere: Sphere must return a pair of equal points if ray intersects at a tangent") { 33 | const auto s = Sphere::createSphere(); 34 | const Ray r{make_point(0, 1, -5), make_vector(0, 0, 1)}; 35 | const auto xs = s->intersect(r); 36 | REQUIRE(xs.size() == 2); 37 | REQUIRE(xs[0].getT() == 5); 38 | REQUIRE(xs[1].getT() == 5); 39 | } 40 | 41 | TEST_CASE("Sphere: Sphere must return empty vector if a ray does not intersect it") { 42 | const auto s = Sphere::createSphere(); 43 | const Ray r{make_point(0, 2, -5), make_vector(0, 0, 1)}; 44 | const auto xs = s->intersect(r); 45 | REQUIRE(xs.empty()); 46 | } 47 | 48 | TEST_CASE("Sphere: Sphere should return correct values if ray originates inside it") { 49 | const auto s = Sphere::createSphere(); 50 | const Ray r{make_point(0, 0, 0), make_vector(0, 0, 1)}; 51 | const auto xs = s->intersect(r); 52 | REQUIRE(xs[0].getT() == -1); 53 | REQUIRE(xs[1].getT() == 1); 54 | } 55 | 56 | TEST_CASE("Sphere: Sphere should return correct values if it is behind the ray") { 57 | const auto s = Sphere::createSphere(); 58 | const Ray r{make_point(0, 0, 5), make_vector(0, 0, 1)}; 59 | const auto xs = s->intersect(r); 60 | REQUIRE(xs[0].getT() == -6); 61 | REQUIRE(xs[1].getT() == -4); 62 | } 63 | 64 | TEST_CASE("Sphere: Intersecting a scaled sphere with a ray") { 65 | const Ray r{make_point(0, 0, -5), make_vector(0, 0, 1)}; 66 | auto s = Sphere::createSphere(); 67 | s->setTransformation(scale(2, 2, 2)); 68 | const auto xs = s->intersect(r); 69 | REQUIRE(xs.size() == 2); 70 | REQUIRE(xs[0].getT() == 3); 71 | REQUIRE(xs[1].getT() == 7); 72 | } 73 | 74 | TEST_CASE("Sphere: Intersecting a translated sphere with a ray") { 75 | const Ray r{make_point(0, 0, -5), make_vector(0, 0, 1)}; 76 | auto s = Sphere::createSphere(); 77 | s->setTransformation(translation(5, 0, 0)); 78 | const auto xs = s->intersect(r); 79 | REQUIRE(xs.size() == 0); 80 | } 81 | 82 | TEST_CASE("Sphere: The normal on a sphere at a point on the x-axis") { 83 | const auto s = Sphere::createSphere(); 84 | const auto n = s->normalAt(make_point(1, 0, 0)); 85 | REQUIRE(n == make_vector(1, 0, 0)); 86 | } 87 | 88 | TEST_CASE("Sphere: The normal on a sphere at a point on the y-axis") { 89 | const auto s = Sphere::createSphere(); 90 | const auto n = s->normalAt(make_point(0, 1, 0)); 91 | REQUIRE(n == make_vector(0, 1, 0)); 92 | } 93 | 94 | TEST_CASE("Sphere: The normal on a sphere at a point on the z-axis") { 95 | const auto s = Sphere::createSphere(); 96 | const auto n = s->normalAt(make_point(0, 0, 1)); 97 | REQUIRE(n == make_vector(0, 0, 1)); 98 | } 99 | 100 | TEST_CASE("Sphere: The normal on a sphere at a non-axial point") { 101 | const auto s = Sphere::createSphere(); 102 | const auto i = const_sqrtd(3) / 3; 103 | const auto n = s->normalAt(make_point(i, i, i)); 104 | REQUIRE(n == make_vector(i, i, i)); 105 | } 106 | 107 | TEST_CASE("Sphere: The normal is a normalized vector") { 108 | const auto s = Sphere::createSphere(); 109 | const auto i = const_sqrtd(3) / 3; 110 | const auto n = s->normalAt(make_point(i, i, i)); 111 | REQUIRE(n == n.normalize()); 112 | } 113 | 114 | TEST_CASE("Sphere: Computing the normal on a translated sphere") { 115 | auto s = Sphere::createSphere(); 116 | s->setTransformation(translation(0, 1, 0)); 117 | auto n = s->normalAt(make_point(0, 1.70711, -0.70711)); 118 | REQUIRE(n == make_vector(0, 0.70711, -0.70711)); 119 | } 120 | 121 | TEST_CASE("Sphere: Computing the normal on a transformed sphere") { 122 | auto s = Sphere::createSphere(); 123 | s->setTransformation(scale(1, 0.5, 1) * rotation_z(math_constants::pi<> / 5)); 124 | auto n = s->normalAt(make_point(0, math_constants::sqrt2_by_2, -math_constants::sqrt2_by_2)); 125 | REQUIRE(n == make_vector(0, 0.97014, -0.24254)); 126 | } 127 | 128 | TEST_CASE("Sphere: A helper for producing a sphere with a glossy material") { 129 | const auto s = Sphere::createGlassSphere(); 130 | REQUIRE(s->getTransformation() == predefined_matrices::I<>); 131 | REQUIRE(s->getMaterial()->getTransparency() == 1); 132 | REQUIRE(s->getMaterial()->getRefractiveIndex() == 1.5); 133 | } 134 | 135 | TEST_CASE("Sphere: Transparency and Refractive Index for the default material") { 136 | const auto s = Sphere::createSphere(); 137 | const Material m{}; 138 | REQUIRE(s->getMaterial()->getTransparency() == 0); 139 | REQUIRE(s->getMaterial()->getRefractiveIndex() == 1.0); 140 | } 141 | 142 | TEST_CASE("Sphere: A sphere has a bounding box") { 143 | const auto s = Sphere::createSphere(); 144 | const auto box = s->bounds(); 145 | REQUIRE(box.getMinPoint() == make_point(-1, -1, -1)); 146 | REQUIRE(box.getMaxPoint() == make_point(1, 1, 1)); 147 | } -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * main.cpp 3 | * 4 | * By Sebastian Raaphorst, 2018. 5 | * 6 | * Create a main function for Catch2 tests, which will link to this file. 7 | */ 8 | 9 | // Define these for Travis, as it has trouble with std::uncaught_exceptions. 10 | #define CATCH_INTERNAL_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 11 | #define CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 12 | #define CATCH_CONFIG_MAIN 13 | #include "catch.hpp" 14 | --------------------------------------------------------------------------------