From e1360466dec41b8eea932ff94bd398cf56315929 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Thu, 4 Jan 2024 12:44:22 +0100 Subject: [PATCH] Ray class now contains some of the hit code Rectangle class added --- src/2dshapes.h | 65 ++++++++++++++++ src/camera.h | 180 +++++++++++++++++++++++++++++-------------- src/main.cc | 57 +++++++++----- src/material.h | 8 +- src/parallelepiped.h | 103 +++++++++++++++++++++++++ src/rand.h | 33 +++++--- src/ray.h | 44 ++++++++++- src/raytracer.h | 12 ++- src/renderer.h | 18 +++-- src/sphere.h | 40 +++++----- src/vec3.h | 30 +++++--- 11 files changed, 455 insertions(+), 135 deletions(-) create mode 100644 src/2dshapes.h create mode 100644 src/parallelepiped.h diff --git a/src/2dshapes.h b/src/2dshapes.h new file mode 100644 index 0000000..e7aab12 --- /dev/null +++ b/src/2dshapes.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + +#include "interval.h" +#include "ray.h" +#include "raytracer.h" +#include "renderobject.h" +#include "vec3.h" + +class Rectangle : public RenderObject { + public: + Rectangle() = default; + + Rectangle(Point3 origin, Vec3 a, Vec3 b, std::shared_ptr mat) + : origin_{origin}, a_{a}, b_{b}, normal_{cross(a, b).normed()}, mat_{std::move(mat)} { + assert(is_zero(dot(a_, b_))); + } + + std::optional hit(const Ray& ray, Interval ts) const override { + auto t_maybe = ray.HitPlane(origin_, normal_); + + if (!t_maybe.has_value()) { + return std::nullopt; + } + + f64 t = t_maybe.value(); + + if (!ts.contains(t)) { + return std::nullopt; + } + + Point3 p = ray.At(t); + + f64 _a = dot(p - origin_, a_); + f64 _b = dot(p - origin_, b_); + + if (_a <= 0.0 || _b <= 0.0 || _a >= a_.norm() || _b >= b_.norm()) { + return std::nullopt; + } + + HitRecord hit_record; + + hit_record.t = t; + hit_record.front_face = dot(normal_, ray.direction()) > 0; + hit_record.p = p; + hit_record.mat = mat_.get(); + hit_record.normal = normal_; + + return hit_record; + } + + private: + Point3 origin_; + + Vec3 a_; + Vec3 b_; + + Vec3 normal_; + + std::shared_ptr mat_; +}; diff --git a/src/camera.h b/src/camera.h index 7914d38..c2871c8 100644 --- a/src/camera.h +++ b/src/camera.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "raytracer.h" @@ -7,39 +8,102 @@ class Camera { public: - constexpr Camera(Point3 centre, u32 image_width, u32 image_height, f64 focal_length) - : centre_{centre}, - focal_length_{focal_length}, - image_width_{image_width}, - image_height_{image_height} { - Recalculate(); + constexpr Camera(Point3 centre, u32 image_width, u32 image_height) + : centre_{centre}, image_width_{image_width}, image_height_{image_height} { + Update(); } - // can be changed, need to recalculate parameters + // change paramters constexpr Point3 centre() const { return centre_; } constexpr void centre(Point3 centre) { centre_ = centre; - Recalculate(); + + focal_length_ = (look_at_ - centre_).norm(); + + need_update_ = true; } - constexpr Vec3 u() const { return u_; } - constexpr Vec3 v() const { return v_; } - constexpr Vec3 w() const { return w_; } + constexpr Point3 look_at() const { return look_at_; } + constexpr void look_at(Point3 look_at) { + look_at_ = look_at; - constexpr void look_at(Point3 look_at, Vec3 up = Vec3::e_y) { - w_ = (look_at - centre_).normed(); + focal_length_ = (look_at_ - centre_).norm(); - v_ = (up - dot(up, w_) * w_).normed(); - - u_ = cross(v_, w_); - - focal_length_ = (look_at - centre_).norm(); - - Recalculate(); + need_update_ = true; } + constexpr Vec3 up() const { return up_; } + constexpr void up(Vec3 up) { + up_ = up; + + need_update_ = true; + } + + constexpr f64 fov() const { return fov_; } + constexpr void fov(f64 fov) { + fov_ = fov; + + need_update_ = true; + } + + constexpr f64 focal_length() const { return focal_length_; } + constexpr void focal_length(f64 focal_length) { + focal_length_ = focal_length; + + need_update_ = true; + } + + constexpr f64 defocus_angle() const { return defocus_angle_; } + constexpr void defocus_angle(f64 defocus_angle) { defocus_angle_ = defocus_angle; } + + // const-only accessors + + constexpr Vec3 u() const { + Update(); + + return u_; + } + constexpr Vec3 v() const { + Update(); + + return v_; + } + constexpr Vec3 w() const { + Update(); + + return w_; + } + + constexpr Vec3 d_u_pixel() const { + Update(); + + return d_u_pixel_; + } + constexpr Vec3 d_v_pixel() const { + Update(); + + return d_v_pixel_; + } + + constexpr u32 image_width() const { return image_width_; } + constexpr u32 image_height() const { return image_height_; } + + constexpr Vec3 PixelToWorld(f64 i, f64 j) const { + Update(); + + return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_; + } + + constexpr f64 defocus_radius() const { + return focal_length() * std::tan(deg2rad(defocus_angle()) / 2); + } + + // + constexpr void rotate(f64 deg) { + Update(); + f64 rad = deg2rad(deg); Vec3 u = std::cos(rad) * u_ + std::sin(rad) * v_; @@ -47,37 +111,29 @@ class Camera { u_ = u; v_ = v; - - Recalculate(); - } - - constexpr f64 fov_angle_v() const { return fov_deg_v_; } - constexpr void fov_angle_v(f64 fov) { - fov_deg_v_ = fov; - Recalculate(); - } - - // const-only accessors - - constexpr Vec3 d_u_pixel() const { return d_u_pixel_; } - constexpr Vec3 d_v_pixel() const { return d_v_pixel_; } - - constexpr u32 image_width() const { return image_width_; } - constexpr u32 image_height() const { return image_height_; } - - constexpr Vec3 PixelToWorld(u32 i, u32 j) const { - return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_; } private: - constexpr void Recalculate() { - const f64 aspect_ratio = static_cast(image_width_) / image_height_; + constexpr void Update() const { + if (!need_update_) { + return; + } - f64 fov_rad_v = deg2rad(fov_deg_v_); - f64 h = std::tan(fov_rad_v / 2); + f64 aspect_ratio = static_cast(image_width_) / image_height_; - // constexpr f64 viewport_height = 2.0; - const f64 viewport_height = 2 * h * focal_length_; + w_ = -(look_at_ - centre_).normed(); + + v_ = (up_ - dot(up_, w_) * w_).normed(); + + u_ = cross(v_, w_); + + assert(is_zero(dot(u_, v_))); + assert(is_zero(dot(u_, w_))); + assert(is_zero(dot(v_, w_))); + + f64 h = focal_length_ * std::tan(deg2rad(fov_) / 2); + + const f64 viewport_height = 2 * h; f64 viewport_width = viewport_height * aspect_ratio; @@ -92,28 +148,38 @@ class Camera { auto viewport_upper_left = centre_ - u_viewport / 2 - v_viewport / 2 - focal_length_ * w_; pixel_00_ = viewport_upper_left + 0.5 * d_u_pixel_ + 0.5 * d_v_pixel_; + + need_update_ = false; } - // settable camera parameters + // fundamental camera parameters - Point3 centre_ = Vec3::origin; + Point3 centre_ = -Point3::e_z; + Point3 look_at_ = Point3::origin; + Vec3 up_ = Vec3::e_y; - Vec3 u_ = Vec3::e_x; // points to right of camera - Vec3 v_ = Vec3::e_y; // points to up of camera - Vec3 w_ = Vec3::e_z; // points backward of camera + f64 fov_ = 80; - f64 focal_length_; + f64 defocus_angle_ = 0; - f64 fov_deg_v_ = 80; + // saved camera properties, need to be recalculated after updating parameters - // saved camera properties + mutable bool need_update_ = true; + + mutable Vec3 u_ = Vec3::e_x; // points to right of camera + mutable Vec3 v_ = Vec3::e_y; // points to up of camera + mutable Vec3 w_ = Vec3::e_z; // points backward of camera + + mutable f64 focal_length_ = 1.0; // position of pixel 0, 0 - Vec3 pixel_00_; + mutable Vec3 pixel_00_; // Vec3 pointing from pixel to right/lower neighbour resp. - Vec3 d_u_pixel_; - Vec3 d_v_pixel_; + mutable Vec3 d_u_pixel_; + mutable Vec3 d_v_pixel_; + + // const u32 image_width_; u32 image_height_; diff --git a/src/main.cc b/src/main.cc index eaa7186..66329fd 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,6 +1,7 @@ #include #include +#include "2dshapes.h" #include "camera.h" #include "colour.h" #include "image.h" @@ -16,6 +17,8 @@ int main(int /* argc */, char* /* argv */[]) { RenderObjectList world; + constexpr f64 focal_length = 5.0; + /* auto lamb = std::make_shared(Colour{0.7, 0.0, 0.5}); auto metal = std::make_shared(Colour{0.3, 0.3, 0.3}); @@ -26,25 +29,42 @@ int main(int /* argc */, char* /* argv */[]) { world.Add(std::move(sphere)); */ auto mat_ground = std::make_shared(Colour(0.8, 0.8, 0.0)); - auto mat_lamb = std::make_shared(Colour(0.1, 0.2, 0.5)); + auto mat_lamb = std::make_shared(Colour(1.0, 0.0, 0.0)); auto mat_metal = std::make_shared(Colour(0.9, 0.4, 0.6), 0.0); - auto mat_dielec = std::make_shared(1.5, 0.0); - auto mat_dielec2 = std::make_shared(0.66, 0.0); + auto mat_metal2 = std::make_shared(Colour(0.3, 0.5, 0.9), 0.0); + // auto mat_dielec = std::make_shared(1.5, 0.0); + // auto mat_dielec2 = std::make_shared(0.66, 0.0); - world.Add(std::make_unique(Point3(0.0, -100.5, -1.0), 100.0, mat_ground)); + world.Add(std::make_unique(Point3(0, -100, 0), 100, mat_ground)); - world.Add(std::make_unique(Point3(0.0, 0.0, -1.0), 0.5, mat_lamb)); + world.Add(std::make_unique(Point3(-2.5, -2.5, 50), 5 * Vec3::e_x, 5 * Vec3::e_y, + mat_lamb)); - world.Add(std::make_unique(Point3(-1.0, 0.0, -1.0), 0.5, mat_dielec)); - world.Add(std::make_unique(Point3(-1.0, 0.0, -1.0), 0.4, mat_dielec2)); + /* constexpr i32 N = 5; - world.Add(std::make_unique(Point3(1.0, 0.0, -1.0), 0.5, mat_metal)); + for (i32 x = -N; x <= N; x++) { + for (i32 y = 0; y <= N; y++) { + world.Add(std::make_unique(Point3(x, y, 50), Vec3::e_x, Vec3::e_y, + (x + y) % 2 == 0 ? mat_lamb : mat_lamb2)); + } + } */ + + // world.Add(std::make_unique(Point3(-1.0, 0.0, 0.0), 0.5, mat_dielec)); + // world.Add(std::make_unique(Point3(-1.0, 0.0, 0.0), 0.4, mat_dielec2)); + + // world.Add(std::make_unique(Point3{-1, 0, 0}, 0.5, mat_metal2)); + + // world.Add(std::make_unique(Point3(1, 0, 0), 0.5, mat_metal)); + + /* for (f64 z = 0.5; z < 10; z += 0.5) { + world.Add(std::make_unique(Point3(1, 0, z), 1 * z / focal_length, mat_metal)); + } */ // camera constexpr f64 aspect_ratio = 16.0 / 9.0; - constexpr u32 image_width = 800; + constexpr u32 image_width = 1200; constexpr auto image_height = static_cast(image_width / aspect_ratio); // constexpr u32 image_width = 1920; @@ -52,21 +72,18 @@ int main(int /* argc */, char* /* argv */[]) { static_assert(image_height >= 1); - constexpr f64 focal_length = 1.0; + constexpr Point3 camera_centre{0, 1, -1}; - constexpr Point3 camera_centre{0.0, 0.0, 0.0}; + Camera camera{camera_centre, image_width, image_height}; - Camera camera{camera_centre, image_width, image_height, focal_length}; + camera.look_at(Vec3{0, 0, 1}); + camera.focal_length(focal_length); - std::clog << camera.u() << newline; - std::clog << camera.v() << newline; - std::clog << camera.w() << newline << newline; + camera.defocus_angle(1); + // camera.focal_length(1); - camera.look_at(Point3{0.8, 0.0, 1.0}, Vec3::e_x); - - std::clog << camera.u() << newline; - std::clog << camera.v() << newline; - std::clog << camera.w() << newline << newline; + // camera.fov(40); + // camera.centre(Point3{-2, 2, 1}); // render diff --git a/src/material.h b/src/material.h index 84dd607..4dfea80 100644 --- a/src/material.h +++ b/src/material.h @@ -36,7 +36,7 @@ class Lambertian : public Material { std::optional> Scatter(const Ray& /* in */, const HitRecord& hit_record) const override { - auto scatter_dir = hit_record.normal + RandomGen::GenInstance().GenOnUnitSphere(); + auto scatter_dir = hit_record.normal + RandomGen::GenInstance().UnitSphereVec3(); if (scatter_dir.almost_zero()) { scatter_dir = hit_record.normal; @@ -59,7 +59,7 @@ class Metal : public Material { const HitRecord& hit_record) const override { auto reflect_dir = in.direction().reflect(hit_record.normal).normed(); - reflect_dir += fuzz_ * RandomGen::GenInstance().GenOnUnitSphere(); + reflect_dir += fuzz_ * RandomGen::GenInstance().UnitSphereVec3(); Ray scattered{hit_record.p, reflect_dir}; @@ -93,7 +93,7 @@ class Dielectric : public Material { return r2 + (1 - r2) * std::pow(1 - cos, 5); }; - if (eta * sin_theta > 1.0 || reflectance(cos_theta, eta) > rand.GenUniform()) { + if (eta * sin_theta > 1.0 || reflectance(cos_theta, eta) > rand.Uniform()) { // reflect out_dir = in.direction().reflect(hit_record.normal); } else { @@ -101,7 +101,7 @@ class Dielectric : public Material { out_dir = in.direction().refract(hit_record.normal, eta); } - out_dir += fuzz_ * rand.GenOnUnitSphere(); + out_dir += fuzz_ * rand.UnitSphereVec3(); Ray out{hit_record.p, out_dir}; diff --git a/src/parallelepiped.h b/src/parallelepiped.h new file mode 100644 index 0000000..120fc83 --- /dev/null +++ b/src/parallelepiped.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include + +#include "interval.h" +#include "ray.h" +#include "raytracer.h" +#include "renderobject.h" +#include "vec3.h" + +class Parallelepiped : public RenderObject { + public: + constexpr Parallelepiped() = default; + + constexpr Parallelepiped(Point3 centre, Vec3 u, Vec3 v, Vec3 w, f64 a = 1.0, f64 b = 1.0, + f64 c = 1.0) + : centre_{centre}, u_{u.normed()}, v_{v.normed()}, w_{w.normed()}, a_{a}, b_{b}, c_{c} { + assert(a >= 0); + assert(b >= 0); + assert(c >= 0); + + // make sure all axes lie in unit cube + + if (dot(u_, v_) < 0) { + v_ = -v_; + } + + if (dot(u_, w_) < 0) { + w_ = -w_; + } + + // make u, v, w are right-handed (not sure if neccessary, but why not) + if (dot(cross(u_, v_), w_) < 0.0) { + std::swap(v_, w_); + } + } + + constexpr Vec3 u() const { return u_; } + constexpr Vec3 v() const { return v_; } + constexpr Vec3 w() const { return w_; } + + constexpr f64 a() const { return a_; } + constexpr f64 b() const { return b_; } + constexpr f64 c() const { return c_; } + + std::optional hit(const Ray& ray, Interval ts) const override { + // check if ray intersects outsphere first + const f64 radius_ = (a_ * u_ + b_ * v_ + c_ * w_).norm() / 2; + + Vec3 oc = ray.origin() - centre_; + + f64 a = ray.direction().squared(); + f64 b_half = dot(oc, ray.direction()); + f64 c = oc.squared() - radius_ * radius_; + + f64 discr = b_half * b_half - a * c; + + if (discr < 0) { + // miss + return std::nullopt; + } + + // since the sphere completely envelops the cuboid + + // smaller of the ts that hit the sphere + f64 t = (-b_half - std::sqrt(discr)) / a; + + if (t >= ts.max()) { + // if the smaller t is already too large, we don't need to check the second one since it + // will be even larger + + return std::nullopt; + } + + if (t <= ts.min()) { + // if the smaller t is too small, check the larger one + + t = (-b_half + std::sqrt(discr)) / a; + + if (!ts.surronds(t)) { + // larger t also out of range: return false + + return std::nullopt; + } + } + } + + private: + Point3 centre_; + + // face normals + Vec3 u_; + Vec3 v_; + Vec3 w_; + + // face distances + f64 a_ = 0.0; + f64 b_ = 0.0; + f64 c_ = 0.0; +}; diff --git a/src/rand.h b/src/rand.h index 4b1cdd9..b0e0e0c 100644 --- a/src/rand.h +++ b/src/rand.h @@ -21,22 +21,22 @@ class RandomGen { return rand; } - constexpr u64 GenU64() { return philox_.Next(); } + constexpr u64 U64() { return philox_.Next(); } // constexpr u32 GenU32() { return static_cast(GenU64()); } - constexpr f64 GenUniform() { return philox_.NextF64(); } + constexpr f64 Uniform() { return philox_.NextF64(); } - constexpr f64 GenUniform(f64 min, f64 max) { return min + (max - min) * GenUniform(); } + constexpr f64 Uniform(f64 min, f64 max) { return min + (max - min) * Uniform(); } - constexpr Vec3 GenVec3() { return Vec3{GenUniform(), GenUniform(), GenUniform()}; } + constexpr Vec3 UniformVec3() { return Vec3{Uniform(), Uniform(), Uniform()}; } - constexpr Vec3 GenVec3(f64 min, f64 max) { - return Vec3{GenUniform(min, max), GenUniform(min, max), GenUniform(min, max)}; + constexpr Vec3 UniformVec3(f64 min, f64 max) { + return Vec3{Uniform(min, max), Uniform(min, max), Uniform(min, max)}; } - constexpr Vec3 GenInUnitBall() { + constexpr Vec3 UnitBallVec3() { while (true) { - Vec3 v = GenVec3(-1.0, 1.0); + Vec3 v = UniformVec3(-1.0, 1.0); if (v.norm() < 1.0) { return v; @@ -44,10 +44,10 @@ class RandomGen { } } - constexpr Vec3 GenOnUnitSphere() { return GenInUnitBall().normed(); } + constexpr Vec3 UnitSphereVec3() { return UnitBallVec3().normed(); } - constexpr Vec3 GenOnHemisphere(const Vec3& normal) { - Vec3 v = GenOnUnitSphere(); + constexpr Vec3 HemisphereVec3(Vec3 normal) { + Vec3 v = UnitSphereVec3(); if (dot(v, normal) < 0) { v = -v; @@ -56,6 +56,17 @@ class RandomGen { return v; } + constexpr Vec3 UnitDiskVec3(Vec3 normal) { + while (true) { + Vec3 v = UniformVec3(); + v -= dot(v, normal) * normal; + + if (v.norm() < 1) { + return v; + } + } + } + private: RandomGen() = default; diff --git a/src/ray.h b/src/ray.h index 6f260d5..9901f84 100644 --- a/src/ray.h +++ b/src/ray.h @@ -1,5 +1,10 @@ #pragma once +#include +#include +#include +#include + #include "raytracer.h" #include "vec3.h" @@ -7,15 +12,48 @@ class Ray { public: constexpr Ray() = default; - constexpr Ray(const Point3& origin, const Vec3& direction) - : orig_{origin}, dir_{direction.normed()} {} + constexpr Ray(const Point3& origin, Vec3 direction) : orig_{origin}, dir_{direction.normed()} {} Point3 origin() const { return orig_; } Vec3 direction() const { return dir_; } constexpr Point3 At(f64 t) const { return orig_ + t * dir_; } + // return ts at which the ray intersects sphere or nullopt if no intersection + // ts may the the same if tangential + constexpr std::optional> HitSphere(Point3 centre, f64 radius) const { + Vec3 oc = origin() - centre; + + f64 a = 1.0; // direction().squared() is always 1 + f64 b_half = dot(oc, direction()); + f64 c = oc.squared() - radius * radius; + + f64 discr = b_half * b_half - a * c; + + if (discr < 0) { + // miss + return std::nullopt; + } + + // hit + + return std::make_pair((-b_half - std::sqrt(discr)) / a, (-b_half + std::sqrt(discr)) / a); + } + + // return t a which the ray intersects the plane containing `v` with normal `normal` + std::optional HitPlane(Point3 v, Vec3 normal) const { + // if the ray is parallel to the plane, return nullopt, even if the ray runs inside the + // plane! + if (is_zero(dot(direction(), normal))) { + return std::nullopt; + } + + f64 t = dot(v - origin(), normal) / dot(direction(), normal); + + return t; + } + private: Point3 orig_; - Vec3 dir_; + Vec3 dir_; // unit vector }; diff --git a/src/raytracer.h b/src/raytracer.h index 32f9d67..482b373 100644 --- a/src/raytracer.h +++ b/src/raytracer.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -7,6 +9,9 @@ using u32 = uint32_t; using u64 = uint64_t; +using i32 = int32_t; +using i64 = int64_t; + using f64 = double; constexpr f64 kInf = std::numeric_limits::infinity(); @@ -16,6 +21,11 @@ constexpr f64 kPi = std::numbers::pi_v; constexpr char newline = '\n'; -constexpr u32 FToU8(f64 x) { return static_cast(255.999 * x); } +constexpr u32 FToU8(f64 x) { return static_cast(255.999 * x); } constexpr f64 deg2rad(f64 deg) { return deg * kPi / 180.0; } + +constexpr bool is_zero(f64 x, f64 atol = 1e-5) { + assert(atol >= 0.0); + return std::fabs(x) < atol; +} diff --git a/src/renderer.h b/src/renderer.h index 2fad7b7..f932c60 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -91,18 +91,22 @@ class Renderer { private: Ray SampleRay(u32 i, u32 j) { - auto pixel_centre = camera_.PixelToWorld(i, j); - auto& rand = RandomGen::GenInstance(); - Vec3 random_shift = rand.GenUniform(-0.5, 0.5) * camera_.d_u_pixel() + - rand.GenUniform(-0.5, 0.5) * camera_.d_v_pixel(); + auto ray_origin = camera_.centre(); - pixel_centre += random_shift; + /* if (camera_.defocus_angle() > 0) { + ray_origin += camera_.defocus_radius() * rand.UnitDiskVec3(camera_.w()); + } */ - auto ray_direction = (pixel_centre - camera_.centre()).normed(); + f64 x_dist = rand.Uniform(-0.5, 0.5); + f64 y_dist = rand.Uniform(-0.5, 0.5); - return Ray{camera_.centre(), ray_direction}; + auto pixel_centre = camera_.PixelToWorld(i + x_dist, j + y_dist); + + auto ray_direction = pixel_centre - camera_.centre(); + + return Ray{ray_origin, ray_direction}; } constexpr Colour Cast(const Ray& ray, const RenderObject& world, u32 bounces) { diff --git a/src/sphere.h b/src/sphere.h index f968b76..95d40bb 100644 --- a/src/sphere.h +++ b/src/sphere.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -27,49 +26,50 @@ class Sphere : public RenderObject { std::optional hit(const Ray& ray, Interval ts) const override { HitRecord hit_record{}; - Vec3 oc = ray.origin() - centre_; + auto t_low_high = ray.HitSphere(centre_, radius_); - f64 a = ray.direction().squared(); - f64 b_half = dot(oc, ray.direction()); - f64 c = oc.squared() - radius_ * radius_; - - f64 discr = b_half * b_half - a * c; - - if (discr < 0) { + if (!t_low_high.has_value()) { // miss return std::nullopt; } // hit + auto [t_low, t_high] = t_low_high.value(); - // smaller of the ts that hit the sphere - f64 t = (-b_half - std::sqrt(discr)) / a; + // we need to only distinguish 4 cases here: + // 1. t_low higher than interval automatically implies t_high is also higher than interval, + // so return + // 2. t_low is in the interval, so return the t_low hit (t_high doesn't matter) + // 3. t_low is too low, t_high is in interval, so return t_high + // 4. both t_low is too low and t_high is too high - if (t >= ts.max()) { + if (t_low >= ts.max()) { // if the smaller t is already too large, we don't need to check the second one since it // will be even larger return std::nullopt; } - if (t <= ts.min()) { + if (t_low > ts.min()) { + // t_min is hit + + hit_record.t = t_low; + // hit_record.front_face = true; + } else { // if the smaller t is too small, check the larger one - t = (-b_half + std::sqrt(discr)) / a; - - if (!ts.surronds(t)) { + if (!ts.surronds(t_high)) { // larger t also out of range: return false return std::nullopt; } - // smaller t hits front side, smaller t hits back side - // TODO: what if our hit t is less than 0? is that case even relevant? + // t_low hits front side, t_high hits back side + hit_record.t = t_high; hit_record.front_face = false; } - hit_record.t = t; - hit_record.p = ray.At(t); + hit_record.p = ray.At(hit_record.t); hit_record.mat = mat_.get(); if (hit_record.front_face) { diff --git a/src/vec3.h b/src/vec3.h index edec1f9..4e551e4 100644 --- a/src/vec3.h +++ b/src/vec3.h @@ -39,7 +39,7 @@ class Vec3 { return xyz_[i]; } - constexpr Vec3& operator+=(const Vec3& v) { + constexpr Vec3& operator+=(Vec3 v) { x() += v.x(); y() += v.y(); z() += v.z(); @@ -47,6 +47,14 @@ class Vec3 { return *this; } + constexpr Vec3& operator-=(Vec3 v) { + x() -= v.x(); + y() -= v.y(); + z() -= v.z(); + + return *this; + } + constexpr Vec3& operator*=(f64 t) { x() *= t; y() *= t; @@ -88,37 +96,35 @@ constexpr Vec3 Vec3::e_z{0.0, 0.0, 1.0}; using Point3 = Vec3; -constexpr std::ostream& operator<<(std::ostream& out, const Vec3& v) { +constexpr std::ostream& operator<<(std::ostream& out, Vec3 v) { return out << v.x() << ' ' << v.y() << ' ' << v.z(); } -constexpr Vec3 operator+(const Vec3& u, const Vec3& v) { +constexpr Vec3 operator+(Vec3 u, Vec3 v) { Vec3 out = u; out += v; return out; } -constexpr Vec3 operator-(const Vec3& u, const Vec3& v) { return u + (-v); } +constexpr Vec3 operator-(Vec3 u, Vec3 v) { return u + (-v); } -constexpr Vec3 operator*(const Vec3& u, const Vec3& v) { - return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()}; -} +constexpr Vec3 operator*(Vec3 u, Vec3 v) { return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()}; } -constexpr Vec3 operator*(f64 t, const Vec3& v) { +constexpr Vec3 operator*(f64 t, Vec3 v) { Vec3 out = v; out *= t; return out; } -constexpr Vec3 operator/(const Vec3& v, f64 t) { return (1 / t) * v; } +constexpr Vec3 operator/(Vec3 v, f64 t) { return (1 / t) * v; } -constexpr f64 dot(const Vec3& u, const Vec3& v) { +constexpr f64 dot(Vec3 u, Vec3 v) { Vec3 tmp = u * v; return tmp.x() + tmp.y() + tmp.z(); } -constexpr Vec3 cross(const Vec3& u, const Vec3& v) { - return {u.y() * v.z() - u.z() - v.y(), u.z() * v.x() - u.x() * v.z(), +constexpr Vec3 cross(Vec3 u, Vec3 v) { + return {u.y() * v.z() - u.z() * v.y(), u.z() * v.x() - u.x() * v.z(), u.x() * v.y() - u.y() * v.x()}; }