@@ -44,33 +44,39 @@ inline Float3 Scale(const Float3& v, float s)
4444}
4545
4646// -----------------------------------------------------------------------------
47- // ClosestPointOnSegment — returns t in [0,1]
47+ // RaySphere — test ray against a sphere
48+ //
49+ // Returns true if hit, outT = entry distance along ray.
4850// -----------------------------------------------------------------------------
49- inline float ClosestTOnSegment (const Float3& segA , const Float3& segB ,
50- const Float3& point )
51+ inline bool RaySphere (const Float3& origin , const Float3& dir ,
52+ const Float3& center, float radius, float & outT )
5153{
52- Float3 ab = Sub (segB, segA);
53- Float3 ap = Sub (point, segA);
54- float denom = Dot (ab, ab);
55- if (denom < 1e-8f ) return 0 .0f ;
56- float t = Dot (ap, ab) / denom;
57- if (t < 0 .0f ) t = 0 .0f ;
58- if (t > 1 .0f ) t = 1 .0f ;
59- return t;
54+ Float3 oc = Sub (origin, center);
55+ float a = Dot (dir, dir);
56+ float h = Dot (oc, dir);
57+ float c = Dot (oc, oc) - radius * radius;
58+ float disc = h * h - a * c;
59+ if (disc < 0 .0f ) return false ;
60+ float sqrtDisc = sqrtf (disc);
61+ float t = (-h - sqrtDisc) / a;
62+ if (t < 0 .0f ) t = (-h + sqrtDisc) / a;
63+ if (t < 0 .0f ) return false ;
64+ outT = t;
65+ return true ;
6066}
6167
6268// -----------------------------------------------------------------------------
63- // RayCapsule — test ray against a capsule (two hemispheres + cylinder )
69+ // RayCapsule — test ray against a capsule (cylinder + two hemispheres )
6470//
65- // Returns true if the ray hits the capsule, and outT is the hit distance.
66- // Uses the "closest approach between two lines" method:
67- // Ray line: P = origin + t * dir
68- // Capsule segment: Q = segA + s * (segB - segA )
69- // Find (t, s) that minimize |P - Q|, check if distance <= radius .
71+ // Decomposes the capsule into:
72+ // 1. Infinite cylinder (clamped to segment extent)
73+ // 2. Bottom hemisphere (sphere at segA)
74+ // 3. Top hemisphere (sphere at segB )
75+ // Returns the closest hit among all three .
7076//
7177// Parameters:
72- // origin — ray start position (eye position)
73- // dir — ray direction (normalized)
78+ // origin — ray start position (eye position)
79+ // dir — ray direction (normalized)
7480// capBottom — capsule foot position (bottom of capsule)
7581// capHeight — total height of capsule
7682// capRadius — capsule radius
@@ -83,67 +89,78 @@ inline bool RayCapsule(const Float3& origin, const Float3& dir,
8389 const Float3& capBottom, float capHeight, float capRadius,
8490 float & outT, float maxRange = 200 .0f )
8591{
86- // Capsule segment: A = bottom + (0, radius, 0), B = bottom + (0, height - radius, 0 )
92+ // Capsule segment endpoints (sphere centers )
8793 Float3 segA = { capBottom.x , capBottom.y + capRadius, capBottom.z };
8894 Float3 segB = { capBottom.x , capBottom.y + capHeight - capRadius, capBottom.z };
89-
90- // Line-segment closest approach
91- // Ray: P(t) = origin + t * dir
92- // Seg: Q(s) = segA + s * segDir, where segDir = segB - segA
9395 Float3 segDir = Sub (segB, segA);
94- Float3 w0 = Sub (origin, segA);
95-
96- float a = Dot (dir, dir); // always 1 if dir is normalized
97- float b = Dot (dir, segDir);
98- float c = Dot (segDir, segDir);
99- float d = Dot (dir, w0);
100- float e = Dot (segDir, w0);
96+ float segLenSq = Dot (segDir, segDir);
10197
102- float denom = a * c - b * b;
98+ float bestT = maxRange + 1 .0f ;
99+ bool hasHit = false ;
103100
104- float t, s;
105-
106- if (denom < 1e-6f )
107- {
108- // Lines are nearly parallel
109- s = 0 .0f ;
110- t = -d / a;
111- }
112- else
101+ // 1. Test ray against infinite cylinder, clamp to segment extent
102+ if (segLenSq > 1e-8f )
113103 {
114- s = (b * d - a * e) / denom;
115- t = (c * d - b * e) / denom;
104+ float segLen = sqrtf (segLenSq);
105+ Float3 axis = Scale (segDir, 1 .0f / segLen);
106+ Float3 oc = Sub (origin, segA);
107+
108+ float dDotAxis = Dot (dir, axis);
109+ float ocDotAxis = Dot (oc, axis);
110+
111+ // Project out the capsule axis component
112+ Float3 dPerp = Sub (dir, Scale (axis, dDotAxis));
113+ Float3 ocPerp = Sub (oc, Scale (axis, ocDotAxis));
114+
115+ float a = Dot (dPerp, dPerp);
116+ float b = Dot (dPerp, ocPerp);
117+ float c = Dot (ocPerp, ocPerp) - capRadius * capRadius;
118+
119+ float disc = b * b - a * c;
120+ if (disc >= 0 .0f && a > 1e-8f )
121+ {
122+ float sqrtDisc = sqrtf (disc);
123+ float t = (-b - sqrtDisc) / a;
124+ if (t < 0 .0f ) t = (-b + sqrtDisc) / a;
125+
126+ if (t >= 0 .0f && t <= maxRange)
127+ {
128+ float hitOnAxis = ocDotAxis + t * dDotAxis;
129+ if (hitOnAxis >= 0 .0f && hitOnAxis <= segLen)
130+ {
131+ bestT = t;
132+ hasHit = true ;
133+ }
134+ }
135+ }
116136 }
117137
118- // Clamp s to [0, 1] (capsule segment) and recompute t
119- if (s < 0 .0f )
138+ // 2. Test against bottom hemisphere (sphere at segA)
139+ float tSphere;
140+ if (RaySphere (origin, dir, segA, capRadius, tSphere))
120141 {
121- s = 0 .0f ;
122- t = -d / a; // dot(dir, origin - segA) / dot(dir, dir)
142+ if (tSphere <= maxRange && tSphere < bestT)
143+ {
144+ bestT = tSphere;
145+ hasHit = true ;
146+ }
123147 }
124- else if (s > 1 .0f )
148+
149+ // 3. Test against top hemisphere (sphere at segB)
150+ if (RaySphere (origin, dir, segB, capRadius, tSphere))
125151 {
126- s = 1 .0f ;
127- Float3 w1 = Sub (origin, segB);
128- t = -Dot (dir, w1) / a;
152+ if (tSphere <= maxRange && tSphere < bestT)
153+ {
154+ bestT = tSphere;
155+ hasHit = true ;
156+ }
129157 }
130158
131- // t must be positive (ray goes forward) and within range
132- if (t < 0 .0f ) t = 0 .0f ;
133- if (t > maxRange) return false ;
134-
135- // Compute closest points
136- Float3 closestOnRay = Add (origin, Scale (dir, t));
137- Float3 closestOnSeg = Add (segA, Scale (segDir, s));
138- Float3 diff = Sub (closestOnRay, closestOnSeg);
139- float distSq = Dot (diff, diff);
140-
141- if (distSq <= capRadius * capRadius)
159+ if (hasHit)
142160 {
143- outT = t ;
161+ outT = bestT ;
144162 return true ;
145163 }
146-
147164 return false ;
148165}
149166
0 commit comments