二次公式在游戏编程中的应用:球体线段相交检测代码解析
Hey there! Let's break down exactly why this code uses the quadratic formula for sphere intersection checks—it's all about translating geometry into math, which is such a common pattern in game development. Let's start from the basics so you can fully grasp it and reuse the logic confidently.
First: The Geometric Problem Translated to Math
This code checks if a line segment (from startPoint to endPoint) intersects a sphere (with radius m_radius, and crucially—this code assumes the sphere is centered at the origin (0,0,0); we'll come back to this later).
To find intersections, we use two key equations:
Segment Parameterization: Any point along the segment can be written as:
P(t) = startPoint + t*(endPoint - startPoint)
Here,tis a scalar where:t=0= the start pointt=1= the end point- Values between 0 and 1 are points on the segment itself.
Sphere Equation: For a sphere centered at the origin with radius
r, any point(x,y,z)on the sphere satisfies:x² + y² + z² = r²
Combine the Equations
If we substitute the segment's parameterized point into the sphere equation, we get a quadratic equation in terms of t. Let's define two vectors to simplify:
p = startPoint(vector from sphere origin to segment start)v = endPoint - startPoint(direction vector of the segment)
Substituting P(t) = p + t*v into the sphere equation gives:(p₀ + t*v₀)² + (p₁ + t*v₁)² + (p₂ + t*v₂)² = m_radius²
When you expand and rearrange this, it becomes the standard quadratic form a*t² + b*t + c = 0, where:
a = v₀² + v₁² + v₂²→ This is the squared length of vectorvb = 2*(p₀*v₀ + p₁*v₁ + p₂*v₂)→ Twice the dot product of vectorspandvc = p₀² + p₁² + p₂² - m_radius²→ Squared length ofpminus squared sphere radius
Which is exactly what the code calculates for a, b, and c!
What the Discriminant (disc) Tells Us
The quadratic formula solves a*t² + b*t + c = 0 with t = [-b ± √(b²-4ac)]/(2a). The term inside the square root—disc = b²-4ac—is the discriminant, and it tells us everything about the intersection:
disc < 0: No real solutions → The segment doesn't intersect the sphere at all (code returnsfalse).disc = 0: Exactly one real solution → The segment is tangent to the sphere (touches at exactly one point).disc > 0: Two real solutions → The segment passes through the sphere, entering and exiting at two points.
Breaking Down the Code Logic
Let's walk through the code step by step:
Define Vectors:
hduVector3Dd p = startPoint; hduVector3Dd v = endPoint - startPoint;pis the vector from the sphere's origin to the segment start;vis the direction of the segment.Calculate Quadratic Coefficients:
Thea,b,cvariables match exactly the math we derived above.Check Intersection Possibility:
Computediscto see if there are real solutions. Ifdisc < 0, returnfalseimmediately—no intersection.Solve for
t(callednin the code):- If
disc = 0, take the single solutionn = -b/(2a). - If
disc > 0, calculate both solutions and pick the smaller one—this is the first intersection point as you move from the segment's start to end.
- If
Validate the Solution:
The code checks ifnis between 0 and 1:n > 1: The intersection is beyond the segment's end point.n < 0: The intersection is behind the segment's start point.
Either case means the segment doesn't intersect the sphere, so returnfalse.
Reusing This Code (With Improvements!)
The original code has a key assumption: the sphere is centered at the origin. To reuse it for spheres anywhere in space, adjust the p vector to be relative to the sphere's center:
hduVector3Dd p = startPoint - sphereCenter; // sphereCenter is your sphere's actual center hduVector3Dd v = endPoint - startPoint;
For a more robust reusable function, add handling for floating-point precision and edge cases (like a zero-length segment):
#include <cmath> #include <algorithm> // for std::min, std::max, std::clamp bool doesSegmentIntersectSphere(hduVector3Dd start, hduVector3Dd end, hduVector3Dd sphereCenter, double sphereRadius, double &outIntersectionT) { hduVector3Dd p = start - sphereCenter; hduVector3Dd v = end - start; double a = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; // Handle zero-length segment (start == end) if (a < 1e-9) { double distSq = p[0]*p[0] + p[1]*p[1] + p[2]*p[2]; if (distSq <= sphereRadius*sphereRadius + 1e-9) { outIntersectionT = 0.0; return true; } return false; } double b = 2 * (p[0]*v[0] + p[1]*v[1] + p[2]*v[2]); double c = p[0]*p[0] + p[1]*p[1] + p[2]*p[2] - sphereRadius * sphereRadius; double disc = b*b - 4*a*c; // Account for floating-point precision errors if (disc < -1e-9) return false; disc = std::max(disc, 0.0); double n; if (disc <= 1e-9) { n = (-b)/(2*a); } else { double posN = (-b + sqrt(disc))/(2*a); double negN = (-b - sqrt(disc))/(2*a); n = std::min(posN, negN); } // Check if n is within segment bounds, with precision tolerance if (n < -1e-9 || n > 1.0 + 1e-9) { // If the smaller t is out of bounds, check the larger one if (disc > 1e-9) { double otherN = std::max(posN, negN); if (otherN >= -1e-9 && otherN <= 1.0 + 1e-9) { outIntersectionT = std::clamp(otherN, 0.0, 1.0); return true; } } return false; } outIntersectionT = std::clamp(n, 0.0, 1.0); return true; }
This function returns true if the segment intersects the sphere, and outputs the t value (for the parameterized segment) where the intersection happens.
内容的提问来源于stack exchange,提问作者KIM CHANGJUN




