| 181 | } |
| 182 | |
| 183 | fn cpu_pick_hit( |
| 184 | points: &[Point], |
| 185 | series: &[SeriesSpan], |
| 186 | camera: &Camera, |
| 187 | bounds: &Rectangle, |
| 188 | cursor: Vec2, |
| 189 | hover_radius_px: f32, |
| 190 | ) -> Option<PointId> { |
| 191 | if points.is_empty() || series.is_empty() { |
| 192 | return None; |
| 193 | } |
| 194 | |
| 195 | let width = bounds.width.max(1.0) as f64; |
| 196 | let height = bounds.height.max(1.0) as f64; |
| 197 | let cursor_x = cursor.x as f64; |
| 198 | let cursor_y = cursor.y as f64; |
| 199 | |
| 200 | let mut span_idx = 0usize; |
| 201 | let mut span_start = 0usize; |
| 202 | let mut best: Option<(usize, f64)> = None; |
| 203 | |
| 204 | for (idx, pt) in points.iter().enumerate() { |
| 205 | while span_idx < series.len() && idx >= span_start + series[span_idx].len { |
| 206 | span_start += series[span_idx].len; |
| 207 | span_idx += 1; |
| 208 | } |
| 209 | if span_idx >= series.len() { |
| 210 | break; |
| 211 | } |
| 212 | if !series[span_idx].pickable { |
| 213 | continue; |
| 214 | } |
| 215 | |
| 216 | let world = marker_center_world(pt); |
| 217 | let ndc_x = (world.x - camera.position.x) / camera.half_extents.x; |
| 218 | let ndc_y = (world.y - camera.position.y) / camera.half_extents.y; |
| 219 | let screen_x = (ndc_x + 1.0) * 0.5 * width; |
| 220 | let screen_y = (1.0 - ndc_y) * 0.5 * height; |
| 221 | |
| 222 | let dx = screen_x - cursor_x; |
| 223 | let dy = screen_y - cursor_y; |
| 224 | let d2 = dx * dx + dy * dy; |
| 225 | let marker_px = Size::size_px(pt.size, pt.size_mode, camera, bounds) as f64; |
| 226 | let radius = hover_radius_px as f64 + marker_px * 0.5; |
| 227 | if d2 <= radius * radius { |
| 228 | if let Some((_, best_d2)) = best { |
| 229 | if d2 < best_d2 { |
| 230 | best = Some((idx, d2)); |
| 231 | } |
| 232 | } else { |
| 233 | best = Some((idx, d2)); |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | let (best_idx, _) = best?; |
| 239 | let mut span_idx = 0usize; |
| 240 | let mut span_start = 0usize; |