Skip to content

Reduce operations in RayCast2d::circle_intersection_at using cross product #19103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kumikaya
Copy link
Contributor

@kumikaya kumikaya commented May 6, 2025

Objective

  • Using the cross product to compute the perpendicular distance reduces one multiplication and two additions.

@kumikaya
Copy link
Contributor Author

kumikaya commented May 6, 2025

I conducted a simple performance benchmark on my laptop:

use bevy_math::{
    bounding::{BoundingCircle, RayCast2d},
    ops, Dir2, FloatPow, Vec2,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::Rng;

pub trait RayCast2dExt {
    /// Get the distance of an intersection with a [`BoundingCircle`], if any.
    fn circle_intersection_at_new(&self, circle: &BoundingCircle) -> Option<f32>;
}

impl RayCast2dExt for RayCast2d {
    fn circle_intersection_at_new(&self, circle: &BoundingCircle) -> Option<f32> {
        let offset = self.ray.origin - circle.center;
        let projected = offset.dot(*self.ray.direction);
        let normal_dist = offset.x * self.ray.direction.y - offset.y * self.ray.direction.x;
        let distance_squared = circle.radius().squared() - normal_dist.squared();
        if distance_squared < 0.
            || ops::copysign(projected.squared(), -projected) < -distance_squared
        {
            None
        } else {
            let toi = -projected - ops::sqrt(distance_squared);
            if toi > self.max {
                None
            } else {
                Some(toi.max(0.))
            }
        }
    }
}

fn generate_rays(n: usize) -> Vec<RayCast2d> {
    let mut rays = Vec::with_capacity(n);
    let mut rng = rand::rng();
    for _ in 0..n {
        let origin = Vec2::new(
            rng.random_range(-1000.0..1000.0),
            rng.random_range(-1000.0..1000.0),
        );
        let dir = Vec2::new(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0));
        if let Ok(dir2) = Dir2::new(dir) {
            rays.push(RayCast2d::new(origin, dir2, 10000.0));
        }
    }
    rays
}

fn bench_intersection(c: &mut Criterion) {
    let rays = generate_rays(100_000);
    let circle = BoundingCircle::new(Vec2::ZERO, 1.5);

    c.bench_function("circle_intersection_at", |b| {
        b.iter(|| {
            let mut sum = 0.0;
            for ray in &rays {
                if let Some(d) = black_box(ray).circle_intersection_at(black_box(&circle)) {
                    sum += d;
                }
            }
            black_box(sum);
        })
    });

    c.bench_function("circle_intersection_at_new", |b| {
        b.iter(|| {
            let mut sum = 0.0;
            for ray in &rays {
                if let Some(d) = black_box(ray).circle_intersection_at_new(black_box(&circle)) {
                    sum += d;
                }
            }
            black_box(sum);
        })
    });
}

criterion_group!(benches, bench_intersection);
criterion_main!(benches);

The results are as follows:

Gnuplot not found, using plotters backend
circle_intersection_at  time:   [310.15 µs 320.96 µs 332.61 µs]
                        change: [-9.9764% -5.0982% -0.5121%] (p = 0.04 < 0.05)
                        Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
  1 (1.00%) high mild
  3 (3.00%) high severe

circle_intersection_at_new
                        time:   [174.53 µs 176.12 µs 177.92 µs]
                        change: [-9.3318% -6.9158% -4.5095%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  3 (3.00%) high mild
  2 (2.00%) high severe

@mockersf mockersf added A-Math Fundamental domain-agnostic mathematical operations S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants