Skip to content
22 changes: 22 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4456,6 +4456,28 @@ description = "Exhibits different modes of constructing cubic curves using splin
category = "Math"
wasm = true

[[example]]
name = "ray_cast_2d"
path = "examples/math/ray_cast_2d.rs"
doc-scrape-examples = true

[package.metadata.example.ray_cast_2d]
name = "2D Ray Casting for Primitives"
description = "Shows off ray casting for primitive shapes in 2D"
category = "Math"
wasm = true

[[example]]
name = "ray_cast_3d"
path = "examples/math/ray_cast_3d.rs"
doc-scrape-examples = true

[package.metadata.example.ray_cast_3d]
name = "3D Ray Casting for Primitives"
description = "Shows off ray casting for primitive shapes in 3D"
category = "Math"
wasm = true

[[example]]
name = "render_primitives"
path = "examples/math/render_primitives.rs"
Expand Down
9 changes: 8 additions & 1 deletion benches/benches/bevy_math/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,12 @@ use criterion::criterion_main;

mod bezier;
mod bounding;
mod ray_cast_2d;
mod ray_cast_3d;

criterion_main!(bezier::benches, bounding::benches);
criterion_main!(
bezier::benches,
bounding::benches,
ray_cast_2d::benches,
ray_cast_3d::benches
);
100 changes: 100 additions & 0 deletions benches/benches/bevy_math/ray_cast_2d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::time::Duration;

use bevy_math::{prelude::*, FromRng, ShapeSample};
use criterion::{criterion_group, measurement::WallTime, BenchmarkGroup, Criterion};
use rand::{rngs::StdRng, RngExt as _, SeedableRng};

const SAMPLES: usize = 100_000;

criterion_group!(benches, ray_cast_2d);

fn bench_shape<S: PrimitiveRayCast2d>(
group: &mut BenchmarkGroup<'_, WallTime>,
rng: &mut StdRng,
name: &str,
shape_constructor: impl Fn(&mut StdRng) -> S,
) {
group.bench_function(format!("{name}_ray_cast"), |b| {
// Generate random shapes and rays.
let shapes = (0..SAMPLES)
.map(|_| shape_constructor(rng))
.collect::<Vec<_>>();
let rays = (0..SAMPLES)
.map(|_| Ray2d {
origin: Circle::new(10.0).sample_interior(rng),
direction: Dir2::from_rng(rng),
})
.collect::<Vec<_>>();
let items = shapes.into_iter().zip(rays).collect::<Vec<_>>();

// Cast rays against the shapes.
b.iter(|| {
items.iter().for_each(|(shape, ray)| {
core::hint::black_box(shape.local_ray_cast(*ray, f32::MAX, false));
});
});
});
}

fn ray_cast_2d(c: &mut Criterion) {
let mut group = c.benchmark_group("ray_cast_2d_100k");
group.warm_up_time(Duration::from_millis(500));

let mut rng = StdRng::seed_from_u64(46);

bench_shape(&mut group, &mut rng, "circle", |rng| {
Circle::new(rng.random_range(0.1..2.5))
});
bench_shape(&mut group, &mut rng, "arc", |rng| {
Arc2d::new(
rng.random_range(0.1..2.5),
rng.random_range(0.1..std::f32::consts::PI),
)
});
bench_shape(&mut group, &mut rng, "circular_sector", |rng| {
CircularSector::new(
rng.random_range(0.1..2.5),
rng.random_range(0.1..std::f32::consts::PI),
)
});
bench_shape(&mut group, &mut rng, "circular_segment", |rng| {
CircularSegment::new(
rng.random_range(0.1..2.5),
rng.random_range(0.1..std::f32::consts::PI),
)
});
bench_shape(&mut group, &mut rng, "ellipse", |rng| {
Ellipse::new(rng.random_range(0.1..2.5), rng.random_range(0.1..2.5))
});
bench_shape(&mut group, &mut rng, "annulus", |rng| {
Annulus::new(rng.random_range(0.1..1.25), rng.random_range(1.26..2.5))
});
bench_shape(&mut group, &mut rng, "capsule2d", |rng| {
Capsule2d::new(rng.random_range(0.1..1.25), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "rectangle", |rng| {
Rectangle::new(rng.random_range(0.1..5.0), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "rhombus", |rng| {
Rhombus::new(rng.random_range(0.1..5.0), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "line2d", |rng| Line2d {
direction: Dir2::from_rng(rng),
});
bench_shape(&mut group, &mut rng, "segment2d", |rng| {
Segment2d::new(
rng.random::<Vec2>() * rng.random_range(0.1..2.5),
rng.random::<Vec2>() * rng.random_range(0.1..2.5),
)
});
bench_shape(&mut group, &mut rng, "regular_polygon", |rng| {
RegularPolygon::new(rng.random_range(0.1..2.5), rng.random_range(3..6))
});
bench_shape(&mut group, &mut rng, "triangle2d", |rng| {
Triangle2d::new(
Vec2::new(rng.random_range(-7.5..7.5), rng.random_range(-7.5..7.5)),
Vec2::new(rng.random_range(-7.5..7.5), rng.random_range(-7.5..7.5)),
Vec2::new(rng.random_range(-7.5..7.5), rng.random_range(-7.5..7.5)),
)
});
}
117 changes: 117 additions & 0 deletions benches/benches/bevy_math/ray_cast_3d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::time::Duration;

use bevy_math::{prelude::*, FromRng, ShapeSample};
use criterion::{criterion_group, measurement::WallTime, BenchmarkGroup, Criterion};
use rand::{rngs::StdRng, RngExt as _, SeedableRng};

const SAMPLES: usize = 100_000;

criterion_group!(benches, ray_cast_3d);

fn bench_shape<S: PrimitiveRayCast3d>(
group: &mut BenchmarkGroup<'_, WallTime>,
rng: &mut StdRng,
name: &str,
shape_constructor: impl Fn(&mut StdRng) -> S,
) {
group.bench_function(format!("{name}_ray_cast"), |b| {
// Generate random shapes and rays.
let shapes = (0..SAMPLES)
.map(|_| shape_constructor(rng))
.collect::<Vec<_>>();
let rays = (0..SAMPLES)
.map(|_| Ray3d {
origin: Sphere::new(10.0).sample_interior(rng),
direction: Dir3::from_rng(rng),
})
.collect::<Vec<_>>();
let items = shapes.into_iter().zip(rays).collect::<Vec<_>>();

// Cast rays against the shapes.
b.iter(|| {
items.iter().for_each(|(shape, ray)| {
core::hint::black_box(shape.local_ray_cast(*ray, f32::MAX, false));
});
});
});
}

fn ray_cast_3d(c: &mut Criterion) {
let mut group = c.benchmark_group("ray_cast_3d_100k");
group.warm_up_time(Duration::from_millis(500));

let mut rng = StdRng::seed_from_u64(46);

bench_shape(&mut group, &mut rng, "sphere", |rng| {
Sphere::new(rng.random_range(0.1..2.5))
});
bench_shape(&mut group, &mut rng, "cuboid", |rng| {
Cuboid::new(
rng.random_range(0.1..5.0),
rng.random_range(0.1..5.0),
rng.random_range(0.1..5.0),
)
});
bench_shape(&mut group, &mut rng, "cylinder", |rng| {
Cylinder::new(rng.random_range(0.1..2.5), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "cone", |rng| {
Cone::new(rng.random_range(0.1..2.5), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "conical_frustum", |rng| {
ConicalFrustum {
radius_top: rng.random_range(0.1..2.5),
radius_bottom: rng.random_range(0.1..2.5),
height: rng.random_range(0.1..5.0),
}
});
bench_shape(&mut group, &mut rng, "capsule3d", |rng| {
Capsule3d::new(rng.random_range(0.1..2.5), rng.random_range(0.1..5.0))
});
bench_shape(&mut group, &mut rng, "triangle3d", |rng| {
Triangle3d::new(
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
)
});
bench_shape(&mut group, &mut rng, "tetrahedron", |rng| {
Tetrahedron::new(
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
Vec3::new(
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
rng.random_range(-7.5..7.5),
),
)
});
bench_shape(&mut group, &mut rng, "torus", |rng| {
Torus::new(rng.random_range(0.1..1.25), rng.random_range(1.26..2.5))
});
}
5 changes: 4 additions & 1 deletion crates/bevy_math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod mat3;
pub mod ops;
pub mod primitives;
mod ray;
pub mod ray_cast;
mod rects;
mod rotation2d;

Expand Down Expand Up @@ -78,7 +79,9 @@ pub mod prelude {
direction::{Dir2, Dir3, Dir3A},
ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops,
primitives::*,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
quat,
ray_cast::{PrimitiveRayCast2d, PrimitiveRayCast3d, RayHit2d, RayHit3d},
uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
Expand Down
46 changes: 45 additions & 1 deletion crates/bevy_math/src/ray.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
ops,
primitives::{InfinitePlane3d, Plane2d},
Dir2, Dir3, Vec2, Vec3,
Dir2, Dir3, Isometry2d, Isometry3d, Vec2, Vec3,
};

#[cfg(feature = "bevy_reflect")]
Expand Down Expand Up @@ -64,6 +64,28 @@ impl Ray2d {
self.intersect_plane(plane_origin, plane)
.map(|distance| self.get_point(distance))
}

/// Returns `self` transformed by the given [isometry].
///
/// [isometry]: crate::Isometry2d
#[inline]
pub fn transformed_by(&self, isometry: Isometry2d) -> Self {
Self {
origin: isometry.transform_point(self.origin),
direction: isometry.rotation * self.direction,
}
}

/// Returns `self` transformed by the inverse of the given [isometry].
///
/// [isometry]: crate::Isometry2d
#[inline]
pub fn inverse_transformed_by(&self, isometry: Isometry2d) -> Self {
Self {
origin: isometry.inverse_transform_point(self.origin),
direction: isometry.rotation.inverse() * self.direction,
}
}
}

/// An infinite half-line starting at `origin` and going in `direction` in 3D space.
Expand Down Expand Up @@ -125,6 +147,28 @@ impl Ray3d {
self.intersect_plane(plane_origin, plane)
.map(|distance| self.get_point(distance))
}

/// Returns `self` transformed by the given [isometry].
///
/// [isometry]: crate::Isometry3d
#[inline]
pub fn transformed_by(&self, isometry: Isometry3d) -> Self {
Self {
origin: isometry.transform_point(self.origin).into(),
direction: isometry.rotation * self.direction,
}
}

/// Returns `self` transformed by the inverse of the given [isometry].
///
/// [isometry]: crate::Isometry3d
#[inline]
pub fn inverse_transformed_by(&self, isometry: Isometry3d) -> Self {
Self {
origin: isometry.inverse_transform_point(self.origin).into(),
direction: isometry.rotation.inverse() * self.direction,
}
}
}

#[cfg(test)]
Expand Down
Loading
Loading