>Utility.hpp
#pragma once
// headers
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <memory>
#include <vector>
#include "Ray.hpp"
#include "Vec3.hpp"
// usings
using std::cerr;
using std::cout;
using std::make_shared;
using std::shared_ptr;
using std::vector;
// constants
const double inf = std::numeric_limits<double>::infinity();
const double pi = 3.1415926535897932385;
// functions
inline double deg_to_rad(const double deg) {
return (deg * pi) / 180.0;
}
inline double ffmin(const double a, const double b) {
return a <= b ? a : b;
}
inline double ffmax(const double a, const double b) {
return a >= b ? a : b;
}
>Hittable.hpp
#pragma once
#include "Utility.hpp"
struct Hit_rec
{
double t;
Vec3 p;
Vec3 normal;
bool front_face;
// we're using the geo approach and just having normals always face outwards
inline void set_normal(const Ray& r, const Vec3& out_normal) {
front_face = dot(r.dir(), out_normal) < 0; // is normal in same dir as ray?
normal = front_face ? out_normal : -out_normal;
}
};
/**-----------------------------------------------------------------------------
"...a very clean solution is the make an “abstract class” for anything a ray
might hit and make both a sphere and a list of spheres just something you can
hit.
What that class should be called is something of a quandary — calling it an
“object” would be good if not for “object oriented” programming. “Surface” is
often used, with the weakness being maybe we will want volumes. “hittable”
emphasizes the member function that unites them. I don’t love any of these but I
will go with “hittable”.
*/
class Hittable
{
public:
virtual bool hit(const Ray& r, const double t_min, const double t_max,
Hit_rec& rec) const = 0;
};
>Hittable_list.hpp
#pragma once
#include "Hittable.hpp"
#include "Utility.hpp"
/**-----------------------------------------------------------------------------
*/
class Hittable_list : public Hittable
{
public:
Hittable_list() {}
Hittable_list(shared_ptr<Hittable> obj) { add(obj); }
// funcs
void clear() { objs.clear(); }
void add(shared_ptr<Hittable> obj) { objs.push_back(obj); }
virtual bool hit(const Ray& r, const double t_min, const double t_max,
Hit_rec& rec) const override;
// fields
vector<shared_ptr<Hittable>> objs;
};
/**-----------------------------------------------------------------------------
*/
bool Hittable_list::hit(const Ray& r, const double t_min, const double t_max,
Hit_rec& rec) const {
Hit_rec tmp_rec{};
bool have_hit{false};
auto closest{t_max}; // we only need to be concerned w/ closest normal
for (const auto& obj : objs) {
if (obj->hit(r, t_min, closest, tmp_rec)) {
have_hit = true;
closest = tmp_rec.t; // set during obj.hit()
rec = tmp_rec;
}
}
return have_hit;
}
>Sphere.hpp
#pragma once
#include "Hittable.hpp"
/**-----------------------------------------------------------------------------
*/
class Sphere : public Hittable
{
public:
Sphere() {}
Sphere(const Vec3& center, const double radius)
: center_{center}
, radius_{radius} {}
virtual bool hit(const Ray& r, const double t_min, const double t_max,
Hit_rec& rec) const override;
bool set_normal(const Ray& r, Hit_rec& rec, const double tmp) const;
// fields
Vec3 center_;
double radius_;
};
/**-----------------------------------------------------------------------------
*/
bool Sphere::hit(const Ray& r, const double t_min, const double t_max,
Hit_rec& rec) const {
Vec3 oc{r.origin() - center_};
auto a{r.dir().len_squared()};
auto half_b{dot(oc, r.dir())};
auto c{oc.len_squared() - (radius_ * radius_)};
auto dscrmnt{(half_b * half_b) - (a * c)};
if (dscrmnt > 0.0) {
auto root{sqrt(dscrmnt)};
auto tmp{(-half_b - root) / a};
if (tmp > t_min && tmp < t_max) return set_normal(r, rec, tmp);
// let's try it from the other side then
tmp = (-half_b + root) / a;
if (tmp > t_min && tmp < t_max) return set_normal(r, rec, tmp);
}
return false;
}
/**-----------------------------------------------------------------------------
*/
bool Sphere::set_normal(const Ray& r, Hit_rec& rec, const double tmp) const {
rec.t = tmp;
rec.p = r.at(rec.t);
auto out_normal{(rec.p - center_) / radius_};
rec.set_normal(r, out_normal);
return true;
}