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

Merged
merged 2 commits into from
May 26, 2025

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
Copy link
Contributor

@IQuick143 IQuick143 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find.

@IQuick143 IQuick143 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it D-Straightforward Simple bug fixes and API improvements, docs, test and examples and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 11, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue May 26, 2025
Merged via the queue into bevyengine:main with commit ee3d9b2 May 26, 2025
40 checks passed
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 D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants