3D Geometry Basics
Key Points
- •3D geometry relies on a small toolkit: vectors, dot products, cross products, and planes; mastering these unlocks most 3D problem-solving.
- •Dot product measures how aligned two vectors are, while cross product gives a perpendicular vector whose length equals the area of the parallelogram they span.
- •Planes are defined by a normal vector and an offset, and many queries reduce to projecting onto or intersecting with planes and lines.
- •Distances in 3D often come from projecting or using areas: point–plane via projection; point–line via cross product area divided by the line’s length.
- •Robustness matters: always use doubles and an epsilon tolerance to guard against floating-point errors and degeneracies.
- •The signed volume via scalar triple product lets you test orientation and compute volumes; it is the foundation of many mesh algorithms.
- •Line–plane and plane–plane intersections can be expressed in closed form with vector algebra and solved in O(1) time.
- •Full 3D convex hulls exist in O(n log n) time but are intricate to implement; many tasks only require primitives like orientation and intersection.
Prerequisites
- →Vector algebra (2D/3D) — Understanding vector addition, scalar multiplication, and basic properties is essential for dot and cross products.
- →Analytic geometry (lines and planes) — Knowing how equations represent geometric objects underpins the plane equation and parameterized lines.
- →Trigonometry and angles — Dot and cross product interpretations involve cosine and sine relationships.
- →Floating-point arithmetic — 3D computations require careful handling of precision, tolerances, and degeneracies.
- →Basic linear algebra — Solving small linear systems and reasoning about spans, orthogonality, and determinants aids in derivations and implementations.
Detailed Explanation
Tap terms for definitions01Overview
3D Geometry Basics provides the essential tools to represent and compute with points, vectors, lines, and planes in three-dimensional space. At the heart are vector operations: the dot product, which measures alignment and lengths, and the cross product, which produces a vector perpendicular to two inputs and whose magnitude encodes area. With these, we can construct planes from points, compute distances, and find intersections such as where a line hits a plane or where two planes cross. Many practical problems—ray casting in rendering, collision detection in physics, spatial queries in databases, and geometric modeling—reduce to combinations of these primitives. A plane is typically written as ax + by + cz + d = 0, with the normal vector (a, b, c) pointing perpendicularly to the surface. Distances to planes or lines can be derived through projections or area formulas. The triple scalar product a · (b × c) captures signed volume and orientation; it provides both robust orientation tests (coplanarity, handedness) and formulas for volume. While implementing these operations is conceptually straightforward, numerical stability is crucial. Using double precision and epsilon tolerances helps manage edge cases like near-parallel lines or nearly coplanar points. Advanced tasks like computing 3D convex hulls build on these basics but introduce algorithmic complexity and implementation pitfalls. Mastering these fundamentals enables you to confidently tackle a wide range of 3D computational problems.
02Intuition & Analogies
Imagine vectors as arrows in space. The dot product is like asking: “How much does one arrow point in the same direction as the other?” If two arrows point the same way, their dot product is large and positive; if they are at right angles, it’s zero; if they point opposite, it’s negative. This is similar to how, when pushing a door, only the component of your push perpendicular to the hinge (aligned with the door’s motion) actually opens it. The cross product is like using a wrench: you push at a right angle to generate torque. Given two arrows on a tabletop, their cross product is an arrow sticking straight up or down, perpendicular to both. The length of this new arrow is proportional to the area of the parallelogram formed by the original two—just like how a wider grip on a wrench (larger area) generates more torque. A plane is like an infinite sheet of paper floating in space. Its normal vector is a straw poking straight through the sheet, indicating which way is “up” relative to the sheet. The plane equation ax + by + cz + d = 0 tells you whether a point is above (positive), on (zero), or below (negative) the sheet by plugging in its coordinates. Distances often come from projections: the shortest path from a point to a plane is the perpendicular drop—imagine shining a flashlight straight down onto the plane; the shadow falls directly below. The shortest distance from a point to a line is like holding a stick (the line) and measuring the shortest piece of string connecting a nearby point to the stick; that length is tied to the area of the parallelogram formed by the stick and the connecting vector, divided by the stick’s length. Signed volume via a · (b × c) is like asking which way a screw turns: it encodes both size (volume) and handedness (orientation). Add these building blocks together, and you can find where a laser (ray) hits a wall (plane), whether two walls intersect in a seam (line), and how big a 3D shape is (volume).
03Formal Definition
04When to Use
Use 3D geometry primitives whenever you need to model or compute spatial relationships:
- Rendering and ray tracing: ray–triangle intersections (Möller–Trumbore), shadow rays, and reflection directions rely on dot/cross products and plane intersections.
- Physics and collision detection: closest distances (point–line/segment, point–plane) and contact normals determine collision responses and constraints.
- CAD/3D modeling: building planes from points, computing face normals, and measuring angles/areas/volumes are core to mesh processing and solid modeling.
- Robotics and navigation: projecting points onto planes (e.g., ground plane), computing angles between orientations, and intersecting sensor rays with environment geometry.
- Computational geometry problems: detecting coplanarity, computing orientation via scalar triple product, and constructing convex hulls or half-space intersections. Choose these formulas when inputs are small fixed-size vectors and exactness isn’t mandatory (double precision is fine). When you need global structures (e.g., the convex hull of many points), use dedicated algorithms (incremental hull, Quickhull) but still rely on these primitives for orientation tests and plane construction. For highly degenerate or exact arithmetic needs, consider robust predicates (e.g., adaptive exact arithmetic).
⚠️Common Mistakes
- Ignoring normalization: Using a non-unit normal with formulas that assume unit length leads to wrong distances or angles. Either normalize vectors when required or keep length factors explicit in formulas.
- Swapping cross product order: (\mathbf{a}\times\mathbf{b} = -\mathbf{b}\times\mathbf{a}). Reversing order flips directions (normals, line directions), which can invert signs (e.g., volume, face orientation). Be consistent throughout.
- Mishandling plane constants: For plane (\mathbf{n}\cdot\mathbf{x} + d = 0), the sign of (d) is not arbitrary. If the plane passes through point (\mathbf{p}), then (d = -\mathbf{n}\cdot\mathbf{p}). Mixing conventions produces incorrect distances and intersections.
- Floating-point exactness assumptions: Comparing to zero without tolerance causes failures near parallel/coplanar cases. Always use an epsilon (e.g., 1e-9) to test for “near zero.”
- Using integers for intermediate computations: Cross products and triple products can overflow 32-bit integers even for moderate coordinates. Use double (or long double) for safety unless you employ exact arithmetic libraries.
- Forgetting direction domain: A line is infinite, but many problems involve segments or rays. After computing parameter (t), ensure it lies in the valid interval (segment: ([0,1]), ray: ([0,\infty)))).
- Not guarding division by small numbers: In intersection formulas, denominators like (\mathbf{n}\cdot\mathbf{u}) or (|\mathbf{u}|) can be almost zero. Check and handle degeneracies (parallel, zero-length vectors) explicitly.
- Inconsistent triangle winding: Mesh volume and normals depend on consistent vertex order (counterclockwise when viewed from outside). Inconsistent winding yields cancellation or negative volumes.
Key Formulas
Dot Product
Explanation: This computes alignment between vectors. The algebraic sum equals the product of lengths times the cosine of the angle, so you can extract angles or projections.
Cross Product
Explanation: Produces a vector perpendicular to both inputs. Its length equals the area of the parallelogram spanned by a and b, and its direction follows the right-hand rule.
Cross Magnitude
Explanation: The magnitude relates to the sine of the angle between vectors. It is useful for computing areas and perpendicular distances.
Plane Equation
Explanation: Implicitly defines a plane: points satisfying this equation lie on the plane. The vector n is the plane’s normal and d is the offset.
Plane from 3 Points
Explanation: Three non-collinear points define a plane. The cross product of two edges yields the normal vector.
Point–Plane Distance
Explanation: Drop a perpendicular from the point to the plane. The numerator measures signed offset; dividing by the normal’s length yields perpendicular distance.
Line–Plane Intersection Parameter
Explanation: Substitute the line P(t) = P0 + t u into the plane equation and solve for t. If the denominator is near zero, the line is parallel to the plane.
Plane–Plane Intersection Line
Explanation: Two non-parallel planes intersect along a line with direction u. A point on that line can be written using cross products and the planes’ offsets.
Point–Line Distance
Explanation: The area of the parallelogram formed by (p–M) and u equals base times height; solving for the height gives the perpendicular distance.
Tetrahedron Volume
Explanation: The scalar triple product gives six times the signed volume of the tetrahedron with edges a, b, c from a common vertex. Absolute value gives geometric volume.
Angle Between Vectors
Explanation: Rearranges the dot product definition to compute the angle between two vectors. Useful for measuring orientations and checking perpendicularity.
Vector Projection
Explanation: Gives the component of v along direction u. Subtracting from v yields the rejection (perpendicular component).
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Vec3 { 5 double x, y, z; 6 Vec3() : x(0), y(0), z(0) {} 7 Vec3(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {} 8 // Vector addition and subtraction 9 Vec3 operator+(const Vec3& o) const { return Vec3(x + o.x, y + o.y, z + o.z); } 10 Vec3 operator-(const Vec3& o) const { return Vec3(x - o.x, y - o.y, z - o.z); } 11 // Scalar multiplication and division 12 Vec3 operator*(double k) const { return Vec3(x * k, y * k, z * k); } 13 Vec3 operator/(double k) const { return Vec3(x / k, y / k, z / k); } 14 // Dot and cross 15 double dot(const Vec3& o) const { return x * o.x + y * o.y + z * o.z; } 16 Vec3 cross(const Vec3& o) const { 17 return Vec3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x); 18 } 19 // Norms 20 double norm2() const { return this->dot(*this); } 21 double norm() const { return sqrt(norm2()); } 22 // Unit vector (safe) 23 Vec3 unit() const { 24 double n = norm(); 25 if (n == 0) return *this; // or return some convention 26 return *this / n; 27 } 28 }; 29 30 static const double EPS = 1e-9; 31 32 struct Plane { 33 // Plane in form n·x + d = 0 34 Vec3 n; // normal 35 double d; // offset 36 }; 37 38 // Build plane from 3 non-collinear points 39 Plane plane_from_points(const Vec3& A, const Vec3& B, const Vec3& C) { 40 Vec3 n = (B - A).cross(C - A); 41 // It is okay if n is not unit; formulas below handle general length 42 double d = -n.dot(A); 43 return {n, d}; 44 } 45 46 // Signed distance from point P to plane (positive on the side of n) 47 double signed_distance_point_plane(const Vec3& P, const Plane& pl) { 48 return (pl.n.dot(P) + pl.d) / max(EPS, pl.n.norm()); 49 } 50 51 // Absolute (geometric) distance from point to plane 52 double distance_point_plane(const Vec3& P, const Plane& pl) { 53 return fabs(pl.n.dot(P) + pl.d) / max(EPS, pl.n.norm()); 54 } 55 56 // Point-line distance: line defined by point M and direction dir 57 double distance_point_line(const Vec3& P, const Vec3& M, const Vec3& dir) { 58 double denom = max(EPS, dir.norm()); 59 return (P - M).cross(dir).norm() / denom; 60 } 61 62 // Line-plane intersection: line P(t) = P0 + t*dir. Returns (has_intersection, t, point) 63 struct LinePlaneHit { bool ok; double t; Vec3 point; }; 64 LinePlaneHit intersect_line_plane(const Vec3& P0, const Vec3& dir, const Plane& pl) { 65 double denom = pl.n.dot(dir); 66 if (fabs(denom) < EPS) { 67 // Parallel: either no intersection or the line lies in plane 68 // Check if P0 lies in plane 69 double val = pl.n.dot(P0) + pl.d; 70 if (fabs(val) < 1e-8) return {true, numeric_limits<double>::infinity(), P0}; 71 return {false, 0.0, Vec3()}; 72 } 73 double t = -(pl.n.dot(P0) + pl.d) / denom; 74 Vec3 P = P0 + dir * t; 75 return {true, t, P}; 76 } 77 78 // Plane-plane intersection: returns whether they intersect in a line and, if so, a point and direction 79 struct PlanePlaneLine { bool ok; Vec3 point; Vec3 dir; }; 80 PlanePlaneLine intersect_plane_plane(const Plane& p1, const Plane& p2) { 81 Vec3 u = p1.n.cross(p2.n); // direction of intersection line 82 double u2 = u.norm2(); 83 if (u2 < EPS*EPS) { 84 // Parallel or coincident 85 // If normals are parallel, check offsets for coincidence 86 Vec3 n1 = p1.n, n2 = p2.n; 87 // If they are parallel and d matches proportionally, treat as coincident (many solutions) 88 // For simplicity, report no unique line 89 return {false, Vec3(), Vec3()}; 90 } 91 // Solve for a point on the line using formula with c_i = -d_i 92 double c1 = -p1.d, c2 = -p2.d; 93 Vec3 p0 = ( (p2.n.cross(u)) * c1 + (u.cross(p1.n)) * c2 ) / u2; 94 return {true, p0, u}; 95 } 96 97 int main() { 98 // Example usage 99 Vec3 A(0,0,0), B(1,0,0), C(0,1,0); // xy-plane triangle 100 Plane pl = plane_from_points(A,B,C); // plane z=0 with some scaling of normal 101 102 Vec3 P(0.2, 0.3, 5.0); 103 cout.setf(std::ios::fixed); cout<<setprecision(6); 104 cout << "Distance point-plane: " << distance_point_plane(P, pl) << "\n"; 105 106 // Line: passes through (0,0,10) going toward negative z-axis 107 Vec3 P0(0,0,10), dir(0,0,-1); 108 auto hit = intersect_line_plane(P0, dir, pl); 109 if (hit.ok && isfinite(hit.t)) { 110 cout << "Line-plane intersection at t=" << hit.t << ": (" 111 << hit.point.x << ", " << hit.point.y << ", " << hit.point.z << ")\n"; 112 } 113 114 // Plane-plane intersection: z=0 and x=0 -> y-axis line 115 Plane p1 = pl; // z=0 116 Plane p2{Vec3(1,0,0), 0}; // x=0 117 auto line = intersect_plane_plane(p1, p2); 118 if (line.ok) { 119 cout << "Plane-plane dir: (" << line.dir.x << ", " << line.dir.y << ", " << line.dir.z << ")\n"; 120 cout << "A point on the line: (" << line.point.x << ", " << line.point.y << ", " << line.point.z << ")\n"; 121 } 122 123 // Point-line distance: distance from (1,1,1) to z-axis line through origin 124 Vec3 M(0,0,0), zdir(0,0,1), Q(1,1,1); 125 cout << "Distance point-line: " << distance_point_line(Q, M, zdir) << "\n"; 126 127 return 0; 128 } 129
Defines a 3D vector type with dot, cross, and norm operations; constructs planes from three points; computes distances from a point to a plane and to a line; finds line–plane and plane–plane intersections. The main function demonstrates usage with the xy-plane, a vertical ray, and intersecting coordinate planes.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Vec3 { double x,y,z; Vec3() : x(0),y(0),z(0){} Vec3(double X,double Y,double Z):x(X),y(Y),z(Z){} 5 Vec3 operator+(const Vec3& o) const { return Vec3(x+o.x,y+o.y,z+o.z);} 6 Vec3 operator-(const Vec3& o) const { return Vec3(x-o.x,y-o.y,z-o.z);} 7 Vec3 operator*(double k) const { return Vec3(x*k,y*k,z*k);} 8 double dot(const Vec3& o) const { return x*o.x + y*o.y + z*o.z; } 9 Vec3 cross(const Vec3& o) const { return Vec3(y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x); } 10 }; 11 12 static const double EPS = 1e-9; 13 14 // Returns (hit, t, u, v, intersection_point). If cull_backfaces=true, ignores intersections from the back side. 15 struct RayTriHit { bool hit; double t,u,v; Vec3 P; }; 16 RayTriHit ray_triangle_mt(const Vec3& orig, const Vec3& dir, const Vec3& v0, const Vec3& v1, const Vec3& v2, bool cull_backfaces=false) { 17 Vec3 e1 = v1 - v0; 18 Vec3 e2 = v2 - v0; 19 Vec3 pvec = dir.cross(e2); 20 double det = e1.dot(pvec); 21 22 if (cull_backfaces) { 23 if (det < EPS) return {false,0,0,0,Vec3()}; 24 } else { 25 if (fabs(det) < EPS) return {false,0,0,0,Vec3()}; 26 } 27 28 double invDet = 1.0 / det; 29 Vec3 tvec = orig - v0; 30 double u = tvec.dot(pvec) * invDet; 31 if (u < 0.0 - 1e-12 || u > 1.0 + 1e-12) return {false,0,0,0,Vec3()}; 32 33 Vec3 qvec = tvec.cross(e1); 34 double v = dir.dot(qvec) * invDet; 35 if (v < 0.0 - 1e-12 || u + v > 1.0 + 1e-12) return {false,0,0,0,Vec3()}; 36 37 double t = e2.dot(qvec) * invDet; 38 if (t < 0.0) return {false,0,0,0,Vec3()}; // ray only (t >= 0) 39 40 Vec3 P = orig + dir * t; 41 return {true, t, u, v, P}; 42 } 43 44 int main(){ 45 // Define a triangle in the plane z=0 46 Vec3 v0(0,0,0), v1(1,0,0), v2(0,1,0); 47 // Define a ray from (0.2,0.2, 1) pointing downward 48 Vec3 O(0.2, 0.2, 1.0), D(0,0,-1); 49 50 auto hit = ray_triangle_mt(O, D, v0, v1, v2, false); 51 if (hit.hit) { 52 cout.setf(std::ios::fixed); cout<<setprecision(6); 53 cout << "Hit at t=" << hit.t << " point=(" 54 << hit.P.x << ", " << hit.P.y << ", " << hit.P.z << ")\n"; 55 cout << "Barycentric (u,v,w) = (" << hit.u << ", " << hit.v << ", " << (1-hit.u-hit.v) << ")\n"; 56 } else { 57 cout << "No intersection\n"; 58 } 59 return 0; 60 } 61
Implements the Möller–Trumbore algorithm to intersect a ray with a triangle without explicitly building the plane. It computes edge vectors, uses cross/dot products to solve for barycentric coordinates, and returns the hit time t and location. The optional backface culling flag filters hits from the back side of the triangle.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct Vec3 { double x,y,z; Vec3() : x(0),y(0),z(0){} Vec3(double X,double Y,double Z):x(X),y(Y),z(Z){} 5 Vec3 operator+(const Vec3& o) const { return Vec3(x+o.x,y+o.y,z+o.z);} 6 Vec3 operator-(const Vec3& o) const { return Vec3(x-o.x,y-o.y,z-o.z);} 7 Vec3 operator*(double k) const { return Vec3(x*k,y*k,z*k);} 8 double dot(const Vec3& o) const { return x*o.x + y*o.y + z*o.z; } 9 Vec3 cross(const Vec3& o) const { return Vec3(y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x); } 10 }; 11 12 // Signed volume of tetrahedron (O as origin) 13 double signed_tet_volume(const Vec3& A, const Vec3& B, const Vec3& C) { 14 // V = (1/6) * A · (B × C) 15 return (1.0/6.0) * A.dot(B.cross(C)); 16 } 17 18 // Volume of a closed triangle mesh (assuming outward-facing consistent winding) 19 double mesh_volume(const vector<array<Vec3,3>>& tris) { 20 // Sum signed volumes of tetrahedra (O, v0, v1, v2) 21 long double acc = 0.0L; 22 for (const auto& t : tris) { 23 const Vec3 &v0 = t[0], &v1 = t[1], &v2 = t[2]; 24 long double vol6 = (long double)v0.x * ( (long double)v1.y * v2.z - (long double)v1.z * v2.y ) 25 - (long double)v0.y * ( (long double)v1.x * v2.z - (long double)v1.z * v2.x ) 26 + (long double)v0.z * ( (long double)v1.x * v2.y - (long double)v1.y * v2.x ); 27 acc += vol6; // equals 6 * signed_tet_volume(v0,v1,v2) 28 } 29 return (double)(acc / 6.0L); 30 } 31 32 int main(){ 33 // Build a unit cube [0,1]^3 using 12 triangles (each face split in 2) 34 vector<Vec3> V = { 35 {0,0,0},{1,0,0},{1,1,0},{0,1,0}, // bottom z=0 36 {0,0,1},{1,0,1},{1,1,1},{0,1,1} // top z=1 37 }; 38 auto tri = [&](int a,int b,int c){ return array<Vec3,3>{V[a],V[b],V[c]}; }; 39 vector<array<Vec3,3>> tris; 40 // bottom (CCW seen from below? We want outward normals; bottom faces point downwards) 41 tris.push_back(tri(0,2,1)); tris.push_back(tri(0,3,2)); 42 // top (z=1), outward normal +z 43 tris.push_back(tri(4,5,6)); tris.push_back(tri(4,6,7)); 44 // front (y=0) 45 tris.push_back(tri(0,1,5)); tris.push_back(tri(0,5,4)); 46 // back (y=1) 47 tris.push_back(tri(3,7,6)); tris.push_back(tri(3,6,2)); 48 // left (x=0) 49 tris.push_back(tri(0,4,7)); tris.push_back(tri(0,7,3)); 50 // right (x=1) 51 tris.push_back(tri(1,2,6)); tris.push_back(tri(1,6,5)); 52 53 double Vcube = mesh_volume(tris); 54 cout.setf(std::ios::fixed); cout<<setprecision(6); 55 cout << "Computed cube volume: " << Vcube << " (expected 1.0)\n"; 56 57 // Demonstrate single tetrahedron volume (O, A, B, C) 58 Vec3 A(1,0,0), B(0,1,0), C(0,0,1); 59 cout << "Signed volume of tetrahedron OABC: " << signed_tet_volume(A,B,C) << " (expected 1/6)\n"; 60 61 return 0; 62 } 63
Uses the scalar triple product to compute signed volumes. The mesh volume function sums the signed volumes of tetrahedra formed by the origin and each triangle; for a closed, consistently oriented surface, the sum equals the enclosed volume. A cube test verifies correctness; a simple tetrahedron check shows the 1/6 relation.