3.4 Disks

The disk is an interesting quadric since it has a particularly straightforward intersection routine that avoids solving the quadratic equation. In pbrt, a Disk is a circular disk of radius r at height h along the z axis. It is implemented in the files shapes/disk.h and shapes/disk.cpp.

<<Disk Declarations>>= 
class Disk : public Shape { public: <<Disk Public Methods>> 
Disk(const Transform *ObjectToWorld, const Transform *WorldToObject, bool reverseOrientation, Float height, Float radius, Float innerRadius, Float phiMax) : Shape(ObjectToWorld, WorldToObject, reverseOrientation), height(height), radius(radius), innerRadius(innerRadius), phiMax(Radians(Clamp(phiMax, 0, 360))) { } Bounds3f ObjectBound() const; bool Intersect(const Ray &ray, Float *tHit, SurfaceInteraction *isect, bool testAlphaTexture) const; bool IntersectP(const Ray &ray, bool testAlphaTexture) const; Float Area() const; Interaction Sample(const Point2f &u) const;
private: <<Disk Private Data>> 
const Float height, radius, innerRadius, phiMax;
};

In order to describe partial disks, the user may specify a maximum phi value beyond which the disk is cut off (Figure 3.8). The disk can also be generalized to an annulus by specifying an inner radius, r Subscript normal i . In parametric form, it is described by

StartLayout 1st Row 1st Column phi 2nd Column equals u phi Subscript normal m normal a normal x Baseline 2nd Row 1st Column x 2nd Column equals left-parenthesis left-parenthesis 1 minus v right-parenthesis r plus v r Subscript normal i Baseline right-parenthesis cosine phi 3rd Row 1st Column y 2nd Column equals left-parenthesis left-parenthesis 1 minus v right-parenthesis r plus v r Subscript normal i Baseline right-parenthesis sine phi 4th Row 1st Column z 2nd Column equals h period EndLayout

Figure 3.8: Basic Setting for the Disk Shape. The disk has radius r and is located at height h along the z axis. A partial disk may be swept by specifying a maximum phi value and an inner radius r Subscript normal i .

Figure 3.9 is a rendered image of two disks.

Figure 3.9: Two Disks. A complete disk is on the left, and a partial disk is on the right.

<<Disk Public Methods>>= 
Disk(const Transform *ObjectToWorld, const Transform *WorldToObject, bool reverseOrientation, Float height, Float radius, Float innerRadius, Float phiMax) : Shape(ObjectToWorld, WorldToObject, reverseOrientation), height(height), radius(radius), innerRadius(innerRadius), phiMax(Radians(Clamp(phiMax, 0, 360))) { }

<<Disk Private Data>>= 
const Float height, radius, innerRadius, phiMax;

3.4.1 Bounding

The bounding method is quite straightforward; it computes a bounding box centered at the height of the disk along z , with extent of radius in both the x and y directions.

<<Disk Method Definitions>>= 
Bounds3f Disk::ObjectBound() const { return Bounds3f(Point3f(-radius, -radius, height), Point3f( radius, radius, height)); }

3.4.2 Intersection Tests

Intersecting a ray with a disk is also easy. The intersection of the ray with the z equals h plane that the disk lies in is found and the intersection point is checked to see if it lies inside the disk.

<<Disk Method Definitions>>+=  
bool Disk::Intersect(const Ray &r, Float *tHit, SurfaceInteraction *isect, bool testAlphaTexture) const { <<Transform Ray to object space>> 
Vector3f oErr, dErr; Ray ray = (*WorldToObject)(r, &oErr, &dErr);
<<Compute plane intersection for disk>> 
<<Reject disk intersections for rays parallel to the disk’s plane>> 
if (ray.d.z == 0) return false;
Float tShapeHit = (height - ray.o.z) / ray.d.z; if (tShapeHit <= 0 || tShapeHit >= ray.tMax) return false;
<<See if hit point is inside disk radii and phi Subscript normal m normal a normal x >> 
Point3f pHit = ray(tShapeHit); Float dist2 = pHit.x * pHit.x + pHit.y * pHit.y; if (dist2 > radius * radius || dist2 < innerRadius * innerRadius) return false; <<Test disk phi value against phi Subscript normal m normal a normal x >> 
Float phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; if (phi > phiMax) return false;
<<Find parametric representation of disk hit>> 
Float u = phi / phiMax; Float rHit = std::sqrt(dist2); Float oneMinusV = ((rHit - innerRadius) / (radius - innerRadius)); Float v = 1 - oneMinusV; Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0); Vector3f dpdv = Vector3f(pHit.x, pHit.y, 0.) * (innerRadius - radius) / rHit; Normal3f dndu(0, 0, 0), dndv(0, 0, 0);
<<Refine disk intersection point>> 
pHit.z = height;
<<Compute error bounds for disk intersection>> 
Vector3f pError(0, 0, 0);
<<Initialize SurfaceInteraction from parametric information>> 
*isect = (*ObjectToWorld)( SurfaceInteraction(pHit, pError, Point2f(u, v), -ray.d, dpdu, dpdv, dndu, dndv, ray.time, this));
<<Update tHit for quadric intersection>> 
*tHit = (Float)tShapeHit;
return true; }

The first step is to compute the parametric t value where the ray intersects the plane that the disk lies in. We want to find t such that the z component of the ray’s position is equal to the height of the disk. Thus,

h equals normal o Subscript z Baseline plus t bold d Subscript z

and

t equals StartFraction h minus normal o Subscript z Baseline Over bold d Subscript z Baseline EndFraction period

The intersection method computes a t value and checks to see if it is inside the legal range of values left-parenthesis 0 comma monospace t monospace upper M monospace a monospace x right-parenthesis . If not, the routine can return false.

<<Compute plane intersection for disk>>= 
<<Reject disk intersections for rays parallel to the disk’s plane>> 
if (ray.d.z == 0) return false;
Float tShapeHit = (height - ray.o.z) / ray.d.z; if (tShapeHit <= 0 || tShapeHit >= ray.tMax) return false;

If the ray is parallel to the disk’s plane (i.e., the z component of its direction is zero), no intersection is reported. The case where a ray is both parallel to the disk’s plane and lies within the plane is somewhat ambiguous, but it’s most reasonable to define intersecting a disk edge-on as “no intersection.” This case must be handled explicitly so that NaN floating-point values aren’t generated by the following code.

<<Reject disk intersections for rays parallel to the disk’s plane>>= 
if (ray.d.z == 0) return false;

Now the intersection method can compute the point pHit where the ray intersects the plane. Once the plane intersection is known, false is returned if the distance from the hit to the center of the disk is more than Disk::radius or less than Disk::innerRadius. This process can be optimized by actually computing the squared distance to the center, taking advantage of the fact that the x and y coordinates of the center point left-parenthesis 0 comma 0 comma monospace h monospace e monospace i monospace g monospace h monospace t right-parenthesis are zero, and the z coordinate of pHit is equal to height.

<<See if hit point is inside disk radii and phi Subscript normal m normal a normal x >>= 
Point3f pHit = ray(tShapeHit); Float dist2 = pHit.x * pHit.x + pHit.y * pHit.y; if (dist2 > radius * radius || dist2 < innerRadius * innerRadius) return false; <<Test disk phi value against phi Subscript normal m normal a normal x >> 
Float phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; if (phi > phiMax) return false;

If the distance check passes, a final test makes sure that the phi value of the hit point is between zero and phi Subscript normal m normal a normal x , specified by the caller. Inverting the disk’s parameterization gives the same expression for phi as the other quadric shapes.

<<Test disk phi value against phi Subscript normal m normal a normal x >>= 
Float phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; if (phi > phiMax) return false;

If we’ve gotten this far, there is an intersection with the disk. The parameter u is scaled to reflect the partial disk specified by phi Subscript normal m normal a normal x , and v is computed by inverting the parametric equation. The equations for the partial derivatives at the hit point can be derived with a process similar to that used for the previous quadrics. Because the normal of a disk is the same everywhere, the partial derivatives partial-differential bold n Subscript slash partial-differential u and partial-differential bold n Subscript slash partial-differential v are both trivially left-parenthesis 0 comma 0 comma 0 right-parenthesis .

<<Find parametric representation of disk hit>>= 
Float u = phi / phiMax; Float rHit = std::sqrt(dist2); Float oneMinusV = ((rHit - innerRadius) / (radius - innerRadius)); Float v = 1 - oneMinusV; Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0); Vector3f dpdv = Vector3f(pHit.x, pHit.y, 0.) * (innerRadius - radius) / rHit; Normal3f dndu(0, 0, 0), dndv(0, 0, 0);

3.4.3 Surface Area

Disks have trivially computed surface area, since they’re just portions of an annulus:

upper A equals StartFraction phi Subscript normal m normal a normal x Baseline Over 2 EndFraction left-parenthesis r squared minus r Subscript normal i Superscript 2 Baseline right-parenthesis period

<<Disk Method Definitions>>+=  
Float Disk::Area() const { return phiMax * 0.5 * (radius * radius - innerRadius * innerRadius); }