1use phys_geom::math::{Real, *};
16use phys_geom::shape::Sphere;
17
18use crate::{Raycast, RaycastHitResult};
19
20impl Raycast for Sphere {
21 fn raycast(
22 &self,
23 local_ray: phys_geom::Ray,
24 max_distance: Real,
25 discard_inside_hit: bool,
26 ) -> Option<RaycastHitResult> {
27 let radius = self.radius();
28 let center_to_origin = local_ray.origin - Point3::origin();
29
30 let offset = (-center_to_origin.dot(&local_ray.direction.into_inner()) - radius).max(0.0);
32 let center_to_translated_origin =
33 center_to_origin + local_ray.direction.into_inner() * offset;
34 let discr_half_b = center_to_translated_origin.dot(&local_ray.direction.into_inner());
35 let discr_c = center_to_translated_origin.norm_squared() - radius * radius;
36
37 if discr_half_b > 0.0 && discr_c > 0.0 {
39 return None;
40 }
41
42 let discr = discr_half_b * discr_half_b - discr_c;
43 if discr < 0.0 {
44 None
45 } else {
46 let mut distance = -discr_half_b - discr.sqrt();
47
48 if distance < -offset {
50 if discard_inside_hit {
51 return None;
52 }
53 Some(RaycastHitResult {
54 distance: 0.0,
55 normal: -local_ray.direction,
56 })
57 } else {
58 distance += offset;
59 if distance <= max_distance {
60 let hit_point = center_to_origin + local_ray.direction.into_inner() * distance;
61 Some(RaycastHitResult {
62 distance,
63 normal: UnitVec3::new_normalize(hit_point),
64 })
65 } else {
66 None
67 }
68 }
69 }
70 }
71}
72
73#[cfg(test)]
74mod raycast_sphere_tests {
75
76 use super::*;
77
78 #[test]
79 fn test_raycast() {
80 let sphere = Sphere::new(1.0);
81
82 {
84 let ray = phys_geom::Ray::new(
85 Point3::new(-2.0, 0.0, 0.0),
86 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
87 );
88 assert_eq!(
89 sphere.raycast(ray, 5.0, false),
90 Some(RaycastHitResult {
91 distance: 1.0,
92 normal: UnitVec3::new_normalize(Vec3::new(-1.0, 0.0, 0.0)),
93 })
94 );
95
96 assert_eq!(
98 sphere.raycast(ray, 1.0, false),
99 Some(RaycastHitResult {
100 distance: 1.0,
101 normal: UnitVec3::new_normalize(Vec3::new(-1.0, 0.0, 0.0)),
102 })
103 );
104
105 assert_eq!(sphere.raycast(ray, 0.5, false), None);
106 }
107
108 {
110 let ray = phys_geom::Ray::new(
111 Point3::new(-2.0, 2.0, 0.0),
112 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
113 );
114 assert_eq!(sphere.raycast(ray, 10.0, false), None);
115 }
116
117 {
119 let ray = phys_geom::Ray::new(
120 Point3::new(0.0, 0.0, 0.0),
121 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
122 );
123 assert_eq!(
124 sphere.raycast(ray, 5.0, false),
125 Some(RaycastHitResult {
126 distance: 0.0,
127 normal: UnitVec3::new_normalize(Vec3::new(-1.0, 0.0, 0.0)),
128 })
129 );
130 }
131
132 {
134 let ray = phys_geom::Ray::new(
135 Point3::new(0.0, 0.0, 0.0),
136 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
137 );
138 assert_eq!(sphere.raycast(ray, 5.0, true), None);
139 }
140 }
141
142 #[test]
143 fn test_raycast_inner() {
144 let sphere = Sphere::new(1.0);
145
146 let ray = phys_geom::Ray::new(
147 Point3::new(0.5, 0.0, 0.0),
148 UnitVec3::new_normalize(Vec3::new(-1.0, 0.0, 0.0)),
149 );
150 assert_eq!(
151 sphere.raycast(ray, 5.0, false),
152 Some(RaycastHitResult {
153 distance: 0.0,
154 normal: UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
155 })
156 );
157
158 assert_eq!(sphere.raycast(ray, 5.0, true), None);
159 }
160
161 #[test]
162 fn test_raycast_from_far_origin() {
163 let sphere = Sphere::new(1.0);
164
165 let ray = phys_geom::Ray::new(
166 Point3::new(-100001.0, 0.0, 0.0),
167 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
168 );
169 assert_eq!(
170 sphere.raycast(ray, 100002.0, false),
171 Some(RaycastHitResult {
172 distance: 100000.0,
173 normal: UnitVec3::new_normalize(Vec3::new(-1.0, 0.0, 0.0)),
174 })
175 );
176 }
177}