11use core:: fmt:: Debug ;
22
3+ #[ cfg( feature = "std" ) ] // TODO separate fp feature for geom
4+ use retrofire_core:: geom:: Sphere ;
35use retrofire_core:: {
4- geom:: { Plane3 , Ray } ,
6+ geom:: { Plane3 , Ray , Ray3 } ,
57 math:: { ApproxEq , Point3 , vec3} ,
68 render:: scene:: BBox ,
79} ;
@@ -18,8 +20,10 @@ pub trait Intersect<T> {
1820 fn intersect ( & self , other : & T ) -> Self :: Result ;
1921}
2022
21- impl < B > Intersect < Plane3 < B > > for Ray < Point3 < B > > {
22- type Result = Option < ( f32 , Point3 < B > ) > ;
23+ type RayIntersect3 < B > = Option < ( f32 , Point3 < B > ) > ;
24+
25+ impl < B > Intersect < Plane3 < B > > for Ray3 < B > {
26+ type Result = RayIntersect3 < B > ;
2327
2428 /// Returns the unique intersection point of `self` and a plane,
2529 /// or `None` if they do not intersect.
@@ -52,8 +56,8 @@ impl<B> Intersect<Plane3<B>> for Ray<Point3<B>> {
5256 }
5357}
5458
55- impl < B : Debug + Default > Intersect < BBox < B > > for Ray < Point3 < B > > {
56- type Result = Option < ( f32 , Point3 < B > ) > ; // Only closest for now
59+ impl < B : Debug + Default > Intersect < BBox < B > > for Ray3 < B > {
60+ type Result = RayIntersect3 < B > ; // Only closest for now
5761
5862 /// Returns the nearest intersection point of `self` and a box,
5963 /// or `None` if they do not intersect.
@@ -120,6 +124,93 @@ impl<B: Debug + Default> Intersect<BBox<B>> for Ray<Point3<B>> {
120124 }
121125}
122126
127+ #[ cfg( feature = "std" ) ]
128+ impl < B > Intersect < Sphere < B > > for Ray3 < B > {
129+ type Result = RayIntersect3 < B > ; // Only closest for now
130+
131+ /// Returns the intersection point of `self` and a sphere closest to the
132+ /// origin of `self`, or `None` if they do not intersect.
133+ ///
134+ /// # Examples
135+ /// ```
136+ /// ```
137+ fn intersect ( & self , & Sphere ( center, r) : & Sphere < B > ) -> Self :: Result {
138+ let & Ray ( orig, dir) = self ;
139+
140+ // > r, no intersection
141+ // If |C - C'| = r, one -"-
142+ // < r, two -"-
143+ // _______
144+ // / \
145+ // / \
146+ // | C--r--|
147+ // \ | /
148+ // \___|___/
149+ // _|
150+ // O--------+-C'----> d
151+ //
152+ // Find point P = (x, y, z) given sphere (C, r) and ray (O, d)
153+ //
154+ // Sphere equation:
155+ // (x - c_x)² + (y - c_y)² + (z - c_z)² = r²
156+ // or in vector form
157+ // (P - C) · (P - C) = r²
158+ //
159+ // Ray equation:
160+ // P = O + t·d
161+ //
162+ // Intersection:
163+ //
164+ // Substitute ray equation to sphere equation:
165+ // (o + t·d - c) · (o + t·d - c) = r²
166+ //
167+ // Multiply out
168+ // o (o + td - c) + t·d (o + td - c) - c (o + td - c) = r²
169+ //
170+ // Distribute
171+ // o·o + o·td - o·c + o·td + td·td - td·c - c·o - c·td + c·c = r²
172+ //
173+ // Reorder
174+ // td·td + o·td + o·td - td·c + o·o - o·c - c·o + c·c - r² = 0
175+ //
176+ // Factor out t's
177+ // (d·d) t² + (o·d + o·d - c·d) t + o·o - 2o·c + c·c - r² = 0
178+ //
179+ // Solve quadratic equation:
180+ // (d·d) t² + 2(o - c)·d t + (o - c)² - r² = 0
181+ //
182+ // t = (-b ± √(b² - 4ac)) / 2a
183+
184+ let c_to_o = orig - center;
185+ let a = dir. len_sqr ( ) ; // >= 0
186+ let b = 2.0 * c_to_o. dot ( & dir) ;
187+ let c = c_to_o. len_sqr ( ) - r * r;
188+
189+ let discriminant = b * b - 4.0 * a * c;
190+ if discriminant < 0.0 {
191+ // the line of the ray does not hit the sphere
192+ return None ;
193+ }
194+
195+ use retrofire_core:: math:: float:: f32;
196+ let sqrt = f32:: sqrt ( discriminant) ;
197+ // sqrt >= 0.0, thus t0 <= t1 always
198+ let ( t0, t1) = ( -b - sqrt, -b + sqrt) ;
199+ let t = if t0 >= 0.0 {
200+ // ray hits both points
201+ t0
202+ } else if t1 >= 0.0 {
203+ // ray origin is inside sphere
204+ t1
205+ } else {
206+ // sphere is behind ray
207+ return None ;
208+ } ;
209+ let t = t / ( 2.0 * a) ;
210+ Some ( ( t, orig + t * dir) )
211+ }
212+ }
213+
123214#[ cfg( test) ]
124215mod tests {
125216 use retrofire_core:: math:: { Linear , Vec3 , pt3} ;
@@ -299,4 +390,46 @@ mod tests {
299390 assert_eq ! ( ray. intersect( & empty) , None ) ;
300391 }
301392 }
393+
394+ // TODO until sqrt has a fallback
395+ #[ cfg( feature = "std" ) ]
396+ mod ray_sphere {
397+ use super :: * ;
398+
399+ const SPHERE : Sphere = Sphere ( pt3 ( 0.0 , 0.0 , 1.0 ) , 2.0 ) ;
400+
401+ #[ test]
402+ fn ray_passes_through_sphere ( ) {
403+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 0.0 , -3.0 ) , vec3 ( 0.0 , 0.0 , 2.0 ) ) ;
404+ assert_eq ! (
405+ ray. intersect( & SPHERE ) ,
406+ Some ( ( 1.0 , pt3( 0.0 , 0.0 , -1.0 ) ) )
407+ ) ;
408+ }
409+ #[ test]
410+ fn ray_tangent_to_sphere ( ) {
411+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 2.0 , -3.0 ) , vec3 ( 0.0 , 0.0 , 2.0 ) ) ;
412+ assert_eq ! ( ray. intersect( & SPHERE ) , Some ( ( 2.0 , pt3( 0.0 , 2.0 , 1.0 ) ) ) ) ;
413+ }
414+
415+ #[ test]
416+ fn ray_origin_inside_sphere ( ) {
417+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 0.0 , 0.0 ) , vec3 ( 0.0 , 0.0 , 2.0 ) ) ;
418+ assert_eq ! ( ray. intersect( & SPHERE ) , Some ( ( 1.5 , pt3( 0.0 , 0.0 , 3.0 ) ) ) ) ;
419+
420+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 0.0 , 2.0 ) , vec3 ( 0.0 , 0.0 , 2.0 ) ) ;
421+ assert_eq ! ( ray. intersect( & SPHERE ) , Some ( ( 0.5 , pt3( 0.0 , 0.0 , 3.0 ) ) ) ) ;
422+ }
423+
424+ fn sphere_behind_ray ( ) {
425+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 0.0 , -3.0 ) , vec3 ( 0.0 , 0.0 , -1.0 ) ) ;
426+ assert_eq ! ( ray. intersect( & SPHERE ) , None ) ;
427+ }
428+
429+ #[ test]
430+ fn ray_misses_sphere ( ) {
431+ let ray: Ray3 = Ray ( pt3 ( 0.0 , 0.0 , -3.0 ) , vec3 ( 0.0 , 1.0 , 1.0 ) ) ;
432+ assert_eq ! ( ray. intersect( & SPHERE ) , None ) ;
433+ }
434+ }
302435}
0 commit comments