2.3 Points

A point is a zero-dimensional location in 2D or 3D space. The Point2 and Point3 classes in pbrt represent points in the obvious way: using x , y , z (in 3D) coordinates with respect to a coordinate system. Although the same representation is used for vectors, the fact that a point represents a position whereas a vector represents a direction leads to a number of important differences in how they are treated. Points are denoted in text by normal p Subscript .

In this section, we’ll continue the approach of only including implementations of the 3D point methods in the Point3 class here.

<<Point Declarations>>= 
template <typename T> class Point2 { public: <<Point2 Public Methods>> 
explicit Point2(const Point3<T> &p) : x(p.x), y(p.y) { Assert(!HasNaNs()); } Point2() { x = y = 0; } Point2(T xx, T yy) : x(xx), y(yy) { Assert(!HasNaNs()); } template <typename U> explicit Point2(const Point2<U> &p) { x = (T)p.x; y = (T)p.y; Assert(!HasNaNs()); } template <typename U> explicit Point2(const Vector2<U> &p) { x = (T)p.x; y = (T)p.y; Assert(!HasNaNs()); } template <typename U> explicit operator Vector2<U>() const { return Vector2<U>(x, y); } #ifndef NDEBUG Point2(const Point2<T> &p) { Assert(!p.HasNaNs()); x = p.x; y = p.y; } Point2<T> &operator=(const Point2<T> &p) { Assert(!p.HasNaNs()); x = p.x; y = p.y; return *this; } #endif // !NDEBUG friend std::ostream& operator<<(std::ostream& os, const Point2<T> &p) { os << "[" << p.x << ", " << p.y << "]"; return os; } Point2<T> operator+(const Vector2<T> &v) const { Assert(!v.HasNaNs()); return Point2<T>(x + v.x, y + v.y); } Point2<T> &operator+=(const Vector2<T> &v) { Assert(!v.HasNaNs()); x += v.x; y += v.y; return *this; } Vector2<T> operator-(const Point2<T> &p) const { Assert(!p.HasNaNs()); return Vector2<T>(x - p.x, y - p.y); } Point2<T> operator-(const Vector2<T> &v) const { Assert(!v.HasNaNs()); return Point2<T>(x - v.x, y - v.y); } Point2<T> operator-() const { return Point2<T>(-x, -y); } Point2<T> &operator-=(const Vector2<T> &v) { Assert(!v.HasNaNs()); x -= v.x; y -= v.y; return *this; } Point2<T> &operator+=(const Point2<T> &p) { Assert(!p.HasNaNs()); x += p.x; y += p.y; return *this; } Point2<T> operator+(const Point2<T> &p) const { Assert(!p.HasNaNs()); return Point2<T>(x + p.x, y + p.y); } Point2<T> operator* (T f) const { return Point2<T>(f*x, f*y); } Point2<T> &operator*=(T f) { x *= f; y *= f; return *this; } Point2<T> operator/ (T f) const { Float inv = (Float)1 / f; return Point2<T>(inv*x, inv*y); } Point2<T> &operator/=(T f) { Float inv = (Float)1 / f; x *= inv; y *= inv; return *this; } T operator[](int i) const { Assert(i >= 0 && i <= 1); if (i == 0) return x; return y; } T &operator[](int i) { Assert(i >= 0 && i <= 1); if (i == 0) return x; return y; } bool operator==(const Point2<T> &p) const { return x == p.x && y == p.y; } bool operator!=(const Point2<T> &p) const { return x != p.x || y != p.y; } bool HasNaNs() const { return std::isnan(x) || std::isnan(y); }
<<Point2 Public Data>> 
T x, y;
};

<<Point Declarations>>+=  
template <typename T> class Point3 { public: <<Point3 Public Methods>> 
Point3() { x = y = z = 0; } Point3(T x, T y, T z) : x(x), y(y), z(z) { Assert(!HasNaNs()); } template <typename U> explicit Point3(const Point3<U> &p) : x((T)p.x), y((T)p.y), z((T)p.z) { Assert(!HasNaNs()); } template <typename U> explicit operator Vector3<U>() const { return Vector3<U>(x, y, z); } #ifndef NDEBUG Point3(const Point3<T> &p) { Assert(!p.HasNaNs()); x = p.x; y = p.y; z = p.z; } Point3<T> &operator=(const Point3<T> &p) { Assert(!p.HasNaNs()); x = p.x; y = p.y; z = p.z; return *this; } #endif // !NDEBUG friend std::ostream& operator<<(std::ostream& os, const Point3<T> &p) { os << "[" << p.x << ", " << p.y << ", " << p.z << "]"; return os; } Point3<T> operator+(const Vector3<T> &v) const { return Point3<T>(x + v.x, y + v.y, z + v.z); } Point3<T> &operator+=(const Vector3<T> &v) { x += v.x; y += v.y; z += v.z; return *this; } Vector3<T> operator-(const Point3<T> &p) const { return Vector3<T>(x - p.x, y - p.y, z - p.z); } Point3<T> operator-(const Vector3<T> &v) const { return Point3<T>(x - v.x, y - v.y, z - v.z); } Point3<T> &operator-=(const Vector3<T> &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } Point3<T> &operator+=(const Point3<T> &p) { x += p.x; y += p.y; z += p.z; return *this; } Point3<T> operator+(const Point3<T> &p) const { return Point3<T>(x + p.x, y + p.y, z + p.z); } Point3<T> operator*(T f) const { return Point3<T>(f*x, f*y, f*z); } Point3<T> &operator*=(T f) { x *= f; y *= f; z *= f; return *this; } Point3<T> operator/(T f) const { Float inv = (Float)1 / f; return Point3<T>(inv*x, inv*y, inv*z); } Point3<T> &operator/=(T f) { Float inv = (Float)1 / f; x *= inv; y *= inv; z *= inv; return *this; } T operator[](int i) const { Assert(i >= 0 && i <= 2); if (i == 0) return x; if (i == 1) return y; return z; } T &operator[](int i) { Assert(i >= 0 && i <= 2); if (i == 0) return x; if (i == 1) return y; return z; } bool operator==(const Point3<T> &p) const { return x == p.x && y == p.y && z == p.z; } bool operator!=(const Point3<T> &p) const { return x != p.x || y != p.y || z != p.z; } bool HasNaNs() const { return std::isnan(x) || std::isnan(y) || std::isnan(z); } Point3<T> operator-() const { return Point3<T>(-x, -y, -z); }
<<Point3 Public Data>> 
T x, y, z;
};

As with vectors, it’s helpful to have shorter type names for commonly used point types.

<<Point Declarations>>+= 
typedef Point2<Float> Point2f; typedef Point2<int> Point2i; typedef Point3<Float> Point3f; typedef Point3<int> Point3i;

<<Point2 Public Data>>= 
T x, y;

<<Point3 Public Data>>= 
T x, y, z;

Also like vectors, a Point3 constructor takes parameters to set the x, y, and z coordinate values.

<<Point3 Public Methods>>= 
Point3() { x = y = z = 0; } Point3(T x, T y, T z) : x(x), y(y), z(z) { Assert(!HasNaNs()); }

It can be useful to convert a Point3 to a Point2 by dropping the z coordinate. The constructor that does this conversion has the explicit qualifier so that this conversion can’t happen without an explicit cast, lest it happen unintentionally.

<<Point2 Public Methods>>= 
explicit Point2(const Point3<T> &p) : x(p.x), y(p.y) { Assert(!HasNaNs()); }

It’s also useful to be able to convert a point with one element type (e.g., a Point3f) to a point of another one (e.g., Point3i) as well as to be able to convert a point to a vector with a different underlying element type. The following constructor and conversion operator provide these conversions. Both also require an explicit cast, to make it clear in source code when they are being used.

<<Point3 Public Methods>>+=  
template <typename U> explicit Point3(const Point3<U> &p) : x((T)p.x), y((T)p.y), z((T)p.z) { Assert(!HasNaNs()); } template <typename U> explicit operator Vector3<U>() const { return Vector3<U>(x, y, z); }

There are certain Point3 methods that either return or take a Vector3. For instance, one can add a vector to a point, offsetting it in the given direction to obtain a new point.

<<Point3 Public Methods>>+=  
Point3<T> operator+(const Vector3<T> &v) const { return Point3<T>(x + v.x, y + v.y, z + v.z); } Point3<T> &operator+=(const Vector3<T> &v) { x += v.x; y += v.y; z += v.z; return *this; }

Alternately, one can subtract one point from another, obtaining the vector between them, as shown in Figure 2.6.

Figure 2.6: Obtaining the Vector between Two Points. The vector bold v equals normal p prime minus normal p Subscript is given by the component-wise subtraction of the points normal p prime and normal p Subscript .

<<Point3 Public Methods>>+=  
Vector3<T> operator-(const Point3<T> &p) const { return Vector3<T>(x - p.x, y - p.y, z - p.z); }

Subtracting a vector from a point gives a new point.

<<Point3 Public Methods>>+= 
Point3<T> operator-(const Vector3<T> &v) const { return Point3<T>(x - v.x, y - v.y, z - v.z); } Point3<T> &operator-=(const Vector3<T> &v) { x -= v.x; y -= v.y; z -= v.z; return *this; }

The distance between two points can be computed by subtracting them to compute the vector between them and then finding the length of that vector:

<<Geometry Inline Functions>>+=  
template <typename T> inline Float Distance(const Point3<T> &p1, const Point3<T> &p2) { return (p1 - p2).Length(); } template <typename T> inline Float DistanceSquared(const Point3<T> &p1, const Point3<T> &p2) { return (p1 - p2).LengthSquared(); }

Although in general it doesn’t make sense mathematically to weight points by a scalar or add two points together, the point classes still allow these operations in order to be able to compute weighted sums of points, which is mathematically meaningful as long as the weights used all sum to one. The code for scalar multiplication and addition with points is identical to the corresponding code for vectors, so it is not shown here.

On a related note, it’s useful to be able to linearly interpolate between two points. Lerp() returns p0 at t==0, p1 at t==1, and linearly interpolates between them at other values of t. For t<0 or t>1, Lerp() extrapolates.

<<Geometry Inline Functions>>+=  
template <typename T> Point3<T> Lerp(Float t, const Point3<T> &p0, const Point3<T> &p1) { return (1 - t) * p0 + t * p1; }

The Min() and Max() functions return points representing the component-wise minimums and maximums of the two given points.

<<Geometry Inline Functions>>+=  
template <typename T> Point3<T> Min(const Point3<T> &p1, const Point3<T> &p2) { return Point3<T>(std::min(p1.x, p2.x), std::min(p1.y, p2.y), std::min(p1.z, p2.z)); } template <typename T> Point3<T> Max(const Point3<T> &p1, const Point3<T> &p2) { return Point3<T>(std::max(p1.x, p2.x), std::max(p1.y, p2.y), std::max(p1.z, p2.z)); }

Floor(), Ceil(), and Abs() apply the corresponding operation component-wise to the given point.

<<Geometry Inline Functions>>+=  
template <typename T> Point3<T> Floor(const Point3<T> &p) { return Point3<T>(std::floor(p.x), std::floor(p.y), std::floor(p.z)); } template <typename T> Point3<T> Ceil(const Point3<T> &p) { return Point3<T>(std::ceil(p.x), std::ceil(p.y), std::ceil(p.z)); } template <typename T> Point3<T> Abs(const Point3<T> &p) { return Point3<T>(std::abs(p.x), std::abs(p.y), std::abs(p.z)); }

And finally, Permute() permutes the coordinate values according to the provided permutation.

<<Geometry Inline Functions>>+=  
template <typename T> Point3<T> Permute(const Point3<T> &p, int x, int y, int z) { return Point3<T>(p[x], p[y], p[z]); }