I am trying to implement a way to store intersections of rays with arbitrary objects. So far I thought I could make the object derived from an interface and put that as the type in the Intersection alongside the time t the ray intersects the object. Then I need another data structure to hold all the intersections while maintaing order where the closes intersections are earlier. This is my current implementation but it feels quite convoluted and might be too slow later on. I am looking for ideas to improve it or resources that I can learn from to develop a better way.
enum class VObjectType {
SPHERE,
CUBE
};
class GeometricObject {
public:
virtual ~GeometricObject() = default;
virtual Tuple<float> origin() const = 0;
virtual int id() const = 0;
virtual VObjectType type() const = 0;
virtual void printType() const = 0;
};
template<typename prec>
class VSphere: public GeometricObject {
public:
Tuple<float> m_origin;
static int idCounter;
int m_id;
VSphere() : m_id{++idCounter}, m_origin{Tuple<prec>::point(0,0,0)} {};
VSphere(Tuple<float> o) : m_id{++idCounter}, m_origin{o} {};
Tuple<float> origin() const override {
return m_origin;
}
int id() const override {
return m_id;
}
VObjectType type() const override {
return VObjectType::SPHERE;
}
void printType() const override {
std::cout << "Sphere" << std::endl;
}
};
template<typename prec>
class VCube: public GeometricObject {
public:
Tuple<float> m_origin;
static int idCounter;
int m_id;
VCube() : m_id{++idCounter}, m_origin{Tuple<float>::point(0,0,0)} {};
VCube(Tuple<float> o) : m_id{++idCounter}, m_origin{o} {};
Tuple<float> origin() const override {
return m_origin;
}
int id() const override {
return m_id;
}
VObjectType type() const override {
return VObjectType::CUBE;
}
void printType() const override {
std::cout << "Cube" << std::endl;
}
};
template<typename prec>
int VSphere<prec>::idCounter = 0;
template<typename prec>
int VCube<prec>::idCounter = 0;
template<typename prec>
struct VIntersection {
prec t;
std::shared_ptr<GeometricObject> object;
template<typename T>
VIntersection(prec t, T&& obj)
: t{t}, object{std::make_shared<std::decay_t<T>>(std::forward<T>(obj))} {};
template<typename T>
VIntersection(prec t, T* obj)
: t{t}, object{std::shared_ptr<T>(obj)} {};
template<typename T>
VIntersection(prec t, std::shared_ptr<T> obj)
: t{t}, object{std::move(obj)} {};
bool operator==(const VIntersection& i) const {
return (t == i.t && object->id() == i.object->id()) &&
(object->origin() == i.object->origin()) &&
(object->type() == i.object->type());
}
};
template<typename prec>
struct VIntersections {
std::vector<VIntersection<prec>> intersections;
std::vector<VIntersection<prec>> negIntersections;
VIntersections() = default;
void add(const VIntersection<prec>& i){
if (i.t < 0) {
auto it = std::lower_bound(negIntersections.begin(), negIntersections.end(), i,
[](const VIntersection<prec>& i1, const VIntersection<prec>& i2) {
return i1.t > i2.t;
});
negIntersections.insert(it, i);
} else {
auto it = std::lower_bound(intersections.begin(), intersections.end(), i,
[](const VIntersection<prec>& i1, const VIntersection<prec>& i2) {
return i1.t < i2.t;
});
intersections.insert(it, i);
}
}
std::optional<VIntersection<prec>> hit() {
if (intersections.empty()) {
//return VIntersection<prec>(-10000000000000000, VSphere<prec>());
return std::nullopt;
}
return intersections.front();
}
int count() const {
return intersections.size() + negIntersections.size();
}
const VIntersection<prec>& operator[](size_t i) const {
if (i < intersections.size()) {
return intersections.at(i);
} else {
return negIntersections.at(i - intersections.size());
}
}
};
template<typename prec>
void vIntersects(const VSphere<prec>& s,
const Ray<prec>& r, VIntersections<prec>& ts) {
Tuple<prec> sphereToRay = r.origin - s.origin();
prec a = dot<prec>(r.direction, r.direction);
prec b = 2 * dot<prec>(r.direction, sphereToRay);
prec c = dot<prec>(sphereToRay, sphereToRay) - 1;
prec discriminant = b*b - 4 * a * c;
if (discriminant < 0){
return;
}
prec t1 = (-b - std::sqrt(discriminant)) / (2 * a);
prec t2 = (-b + std::sqrt(discriminant)) / (2 * a);
ts.add(VIntersection<prec>(t1, s));
ts.add(VIntersection<prec>(t2, s));
}
template<typename prec>
void vIntersects(const VCube<prec>& c,
const Ray<prec>& r, VIntersections<prec>& ts) {
//get some random values for now
prec t1 = 1;
prec t2 = 2;
ts.add(VIntersection<prec>(t1, c));
ts.add(VIntersection<prec>(t2, c));
}