|
1 | 1 | pub mod components; |
| 2 | +pub mod queries; |
2 | 3 |
|
3 | | -use crate::ecs::{ |
4 | | - camera::components::Projection, |
5 | | - world::{Entity, Vec2, Vec3, Vec4, World}, |
6 | | -}; |
7 | | - |
8 | | -#[derive(Debug, Clone, Copy)] |
9 | | -pub struct PickingRay { |
10 | | - pub origin: Vec3, |
11 | | - pub direction: Vec3, |
12 | | -} |
13 | | - |
14 | | -impl PickingRay { |
15 | | - pub fn from_screen_position(world: &World, screen_pos: Vec2) -> Option<Self> { |
16 | | - let window = &world.resources.window; |
17 | | - let window_handle = window.handle.as_ref()?; |
18 | | - let scale_factor = window_handle.scale_factor() as f32; |
19 | | - let window_size = window_handle.inner_size(); |
20 | | - |
21 | | - let camera_entity = world.resources.active_camera?; |
22 | | - |
23 | | - let camera = world.get_camera(camera_entity)?; |
24 | | - let global_transform = world.get_global_transform(camera_entity)?; |
25 | | - |
26 | | - let physical_x = screen_pos.x * scale_factor; |
27 | | - let physical_y = screen_pos.y * scale_factor; |
28 | | - |
29 | | - let ndc_x = (2.0 * physical_x) / window_size.width as f32 - 1.0; |
30 | | - let ndc_y = 1.0 - (2.0 * physical_y) / window_size.height as f32; |
31 | | - |
32 | | - let view_matrix = global_transform.0.try_inverse()?; |
33 | | - let projection_matrix = camera.projection.matrix(); |
34 | | - let inverse_vp = (projection_matrix * view_matrix).try_inverse()?; |
35 | | - |
36 | | - let (origin, direction) = match camera.projection { |
37 | | - Projection::Perspective { .. } => { |
38 | | - let camera_pos = global_transform.0.column(3).xyz(); |
39 | | - let clip_pos = Vec4::new(ndc_x, ndc_y, -1.0, 1.0); |
40 | | - let world_pos = inverse_vp * clip_pos; |
41 | | - let world_pos = world_pos.xyz() / world_pos.w; |
42 | | - let direction = (world_pos - camera_pos).normalize(); |
43 | | - (camera_pos, direction) |
44 | | - } |
45 | | - Projection::Orthographic { .. } => { |
46 | | - let near_clip = Vec4::new(ndc_x, ndc_y, -1.0, 1.0); |
47 | | - let far_clip = Vec4::new(ndc_x, ndc_y, 1.0, 1.0); |
48 | | - |
49 | | - let near_world = inverse_vp * near_clip; |
50 | | - let far_world = inverse_vp * far_clip; |
51 | | - |
52 | | - let near_point = near_world.xyz() / near_world.w; |
53 | | - let far_point = far_world.xyz() / far_world.w; |
54 | | - |
55 | | - let direction = (far_point - near_point).normalize(); |
56 | | - (near_point, direction) |
57 | | - } |
58 | | - }; |
59 | | - |
60 | | - Some(Self { origin, direction }) |
61 | | - } |
62 | | - |
63 | | - pub fn intersect_plane(&self, plane_normal: Vec3, plane_distance: f32) -> Option<Vec3> { |
64 | | - let denom = nalgebra_glm::dot(&plane_normal, &self.direction); |
65 | | - if denom.abs() < 1e-6 { |
66 | | - return None; |
67 | | - } |
68 | | - |
69 | | - let t = -(nalgebra_glm::dot(&plane_normal, &self.origin) + plane_distance) / denom; |
70 | | - if t < 0.0 { |
71 | | - return None; |
72 | | - } |
73 | | - |
74 | | - Some(self.origin + self.direction * t) |
75 | | - } |
76 | | - |
77 | | - pub fn intersect_ground_plane(&self, y_level: f32) -> Option<Vec3> { |
78 | | - self.intersect_plane(Vec3::new(0.0, 1.0, 0.0), -y_level) |
79 | | - } |
80 | | -} |
81 | | - |
82 | | -#[derive(Debug, Clone)] |
83 | | -pub struct PickingResult { |
84 | | - pub entity: Entity, |
85 | | - pub distance: f32, |
86 | | - pub world_position: Vec3, |
87 | | -} |
88 | | - |
89 | | -#[derive(Debug, Clone, Copy)] |
90 | | -pub struct PickingOptions { |
91 | | - pub max_distance: f32, |
92 | | - pub ignore_invisible: bool, |
93 | | -} |
94 | | - |
95 | | -impl Default for PickingOptions { |
96 | | - fn default() -> Self { |
97 | | - Self { |
98 | | - max_distance: f32::INFINITY, |
99 | | - ignore_invisible: true, |
100 | | - } |
101 | | - } |
102 | | -} |
103 | | - |
104 | | -pub fn pick_entities_with_options( |
105 | | - world: &World, |
106 | | - screen_pos: Vec2, |
107 | | - options: PickingOptions, |
108 | | -) -> Vec<PickingResult> { |
109 | | - let ray = match PickingRay::from_screen_position(world, screen_pos) { |
110 | | - Some(ray) => ray, |
111 | | - None => return Vec::new(), |
112 | | - }; |
113 | | - |
114 | | - let mut results = Vec::new(); |
115 | | - |
116 | | - for entity in world.query_entities(crate::ecs::world::BOUNDING_VOLUME) { |
117 | | - let bounding_volume = match world.get_bounding_volume(entity) { |
118 | | - Some(bv) => bv, |
119 | | - None => continue, |
120 | | - }; |
121 | | - |
122 | | - let global_transform = match world.get_global_transform(entity) { |
123 | | - Some(gt) => gt, |
124 | | - None => continue, |
125 | | - }; |
126 | | - |
127 | | - if options.ignore_invisible |
128 | | - && let Some(visible) = world.get_visibility(entity) |
129 | | - && !visible.visible |
130 | | - { |
131 | | - continue; |
132 | | - } |
133 | | - |
134 | | - let transformed_bv = bounding_volume.transform(&global_transform.0); |
135 | | - |
136 | | - let to_center = transformed_bv.obb.center - ray.origin; |
137 | | - let projection = nalgebra_glm::dot(&to_center, &ray.direction); |
138 | | - let closest_point = if projection < 0.0 { |
139 | | - ray.origin |
140 | | - } else { |
141 | | - ray.origin + ray.direction * projection |
142 | | - }; |
143 | | - |
144 | | - let distance_to_sphere = nalgebra_glm::distance(&closest_point, &transformed_bv.obb.center); |
145 | | - if distance_to_sphere > transformed_bv.sphere_radius { |
146 | | - continue; |
147 | | - } |
148 | | - |
149 | | - if let Some(distance) = transformed_bv.obb.intersect_ray(ray.origin, ray.direction) |
150 | | - && distance <= options.max_distance |
151 | | - { |
152 | | - let world_position = ray.origin + ray.direction * distance; |
153 | | - results.push(PickingResult { |
154 | | - entity, |
155 | | - distance, |
156 | | - world_position, |
157 | | - }); |
158 | | - } |
159 | | - } |
160 | | - |
161 | | - results.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap()); |
162 | | - results |
163 | | -} |
164 | | - |
165 | | -pub fn pick_entities(world: &World, screen_pos: Vec2) -> Vec<PickingResult> { |
166 | | - pick_entities_with_options(world, screen_pos, PickingOptions::default()) |
167 | | -} |
168 | | - |
169 | | -pub fn pick_closest_entity(world: &World, screen_pos: Vec2) -> Option<PickingResult> { |
170 | | - pick_entities(world, screen_pos).into_iter().next() |
171 | | -} |
172 | | - |
173 | | -pub fn get_ground_position_from_screen( |
174 | | - world: &World, |
175 | | - screen_pos: Vec2, |
176 | | - y_level: f32, |
177 | | -) -> Option<Vec3> { |
178 | | - let ray = PickingRay::from_screen_position(world, screen_pos)?; |
179 | | - ray.intersect_ground_plane(y_level) |
180 | | -} |
181 | | - |
182 | | -pub fn pick_entities_in_frustum(world: &World, entities: &[Entity]) -> Vec<Entity> { |
183 | | - let camera_entity = match world.resources.active_camera { |
184 | | - Some(entity) => entity, |
185 | | - None => return Vec::new(), |
186 | | - }; |
187 | | - |
188 | | - let camera = match world.get_camera(camera_entity) { |
189 | | - Some(cam) => cam, |
190 | | - None => return Vec::new(), |
191 | | - }; |
192 | | - |
193 | | - let global_transform = match world.get_global_transform(camera_entity) { |
194 | | - Some(gt) => gt, |
195 | | - None => return Vec::new(), |
196 | | - }; |
197 | | - |
198 | | - let view_matrix = match global_transform.0.try_inverse() { |
199 | | - Some(vm) => vm, |
200 | | - None => return Vec::new(), |
201 | | - }; |
202 | | - |
203 | | - let projection_matrix = camera.projection.matrix(); |
204 | | - let view_projection = projection_matrix * view_matrix; |
205 | | - |
206 | | - let mut visible_entities = Vec::new(); |
207 | | - |
208 | | - for &entity in entities { |
209 | | - let bounding_volume = match world.get_bounding_volume(entity) { |
210 | | - Some(bv) => bv, |
211 | | - None => continue, |
212 | | - }; |
213 | | - |
214 | | - let global_transform = match world.get_global_transform(entity) { |
215 | | - Some(gt) => gt, |
216 | | - None => continue, |
217 | | - }; |
218 | | - |
219 | | - let transformed_bv = bounding_volume.transform(&global_transform.0); |
220 | | - let center = transformed_bv.obb.center; |
221 | | - |
222 | | - let clip_pos = view_projection * Vec4::new(center.x, center.y, center.z, 1.0); |
223 | | - if clip_pos.w <= 0.0 { |
224 | | - continue; |
225 | | - } |
226 | | - |
227 | | - let ndc = clip_pos.xyz() / clip_pos.w; |
228 | | - let sphere_radius_ndc = transformed_bv.sphere_radius / clip_pos.w; |
229 | | - |
230 | | - if ndc.x + sphere_radius_ndc >= -1.0 |
231 | | - && ndc.x - sphere_radius_ndc <= 1.0 |
232 | | - && ndc.y + sphere_radius_ndc >= -1.0 |
233 | | - && ndc.y - sphere_radius_ndc <= 1.0 |
234 | | - && ndc.z + sphere_radius_ndc >= -1.0 |
235 | | - && ndc.z - sphere_radius_ndc <= 1.0 |
236 | | - { |
237 | | - visible_entities.push(entity); |
238 | | - } |
239 | | - } |
240 | | - |
241 | | - visible_entities |
242 | | -} |
| 4 | +pub use components::*; |
| 5 | +pub use queries::*; |
0 commit comments