I'm writing a ray-tracer in C++, and while writing a constructor for the Ray
class:
class Ray {
public:
Ray(const glm::vec3& origin, const glm::vec3& direction) : o{origin}, d{direction} {}
private:
...
}
my IDE helpfully put a zigzag line, saying:
This got me thinking, and I decided I wanted to be a little stricter with my types, so that Point
could not possibly be confused with Vector
(a fairly common mistake in graphics, as far as I've seen).
The essence is that in 3D space, points are position vectors, and are 4-tuples with the last element always 1 (i.e. (x, y, z, 1)
), whereas vectors are 4-tuples with the last element always zero (i.e. (x, y, z, 0)
). There are some other constraints on binary operators; for instance:
Point + Point = undefined
Point - Point = Vector
Point ± Vector = Point
Vector ± Vector = Vector
dot(Vector, Vector) = float
cross(Vector, Vector) = Vector (not defined for Point)
I therefore thought of this, as a first attempt:
struct Point {
glm::vec4 p{0, 0, 0, 1};
};
struct Vector {
glm::vec4 v{0, 0, 0, 0};
};
and the constructor, rewritten:
Ray(const Point& origin, const Vector& direction) : o{origin.p}, d{direction.v} {}
However, this is clearly insufficient, as we now would either have to access the members to use glm
's operator overloads. Plus, it is missing rewrites for all the operator overloads to satisfy the type constraints above.
Furthermore, this does not prevent consumers of the Point
/Vector
structs from violating the constraint on the w-coordinate (although trivially solved by making them classes and the corresponding members private
).
Is there a neater way of doing this (maybe with some C++20 concepts), and more importantly, is this worthwhile at all?
w
coordinate when I don't need to, but that don't prevent me to use it if I want to do manual perspective transform shenanigans, or color transformations, or whatever. 2/2glm::vec3
?