Skip to content

Commit

Permalink
Add new Rect::intersects_ray_from_center method (#5415)
Browse files Browse the repository at this point in the history
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->
Title.

* [x] I have followed the instructions in the PR template
  • Loading branch information
grtlr authored Dec 2, 2024
1 parent 328422d commit 6833cf5
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 24 deletions.
81 changes: 81 additions & 0 deletions crates/emath/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ impl Rect {
///
/// A ray that starts inside the rect will return `true`.
pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool {
debug_assert!(d.is_normalized(), "expected normalized direction");

let mut tmin = -f32::INFINITY;
let mut tmax = f32::INFINITY;

Expand All @@ -670,6 +672,32 @@ impl Rect {

0.0 <= tmax && tmin <= tmax
}

/// Where does a ray from the center intersect the rectangle?
///
/// `d` is the direction of the ray and assumed to be normalized.
pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 {
debug_assert!(d.is_normalized(), "expected normalized direction");

let mut tmin = f32::NEG_INFINITY;
let mut tmax = f32::INFINITY;

for i in 0..2 {
let inv_d = 1.0 / -d[i];
let mut t0 = (self.min[i] - self.center()[i]) * inv_d;
let mut t1 = (self.max[i] - self.center()[i]) * inv_d;

if inv_d < 0.0 {
std::mem::swap(&mut t0, &mut t1);
}

tmin = tmin.max(t0);
tmax = tmax.min(t1);
}

let t = tmax.min(tmin);
self.center() + t * -d
}
}

impl fmt::Debug for Rect {
Expand Down Expand Up @@ -792,4 +820,57 @@ mod tests {
println!("Leftward ray from right:");
assert!(rect.intersects_ray(pos2(4.0, 2.0), Vec2::LEFT));
}

#[test]
fn test_ray_from_center_intersection() {
let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0));

assert_eq!(
rect.intersects_ray_from_center(Vec2::RIGHT),
pos2(3.0, 2.0),
"rightward ray"
);

assert_eq!(
rect.intersects_ray_from_center(Vec2::UP),
pos2(2.0, 1.0),
"upward ray"
);

assert_eq!(
rect.intersects_ray_from_center(Vec2::LEFT),
pos2(1.0, 2.0),
"leftward ray"
);

assert_eq!(
rect.intersects_ray_from_center(Vec2::DOWN),
pos2(2.0, 3.0),
"downward ray"
);

assert_eq!(
rect.intersects_ray_from_center((Vec2::LEFT + Vec2::DOWN).normalized()),
pos2(1.0, 3.0),
"bottom-left corner ray"
);

assert_eq!(
rect.intersects_ray_from_center((Vec2::LEFT + Vec2::UP).normalized()),
pos2(1.0, 1.0),
"top-left corner ray"
);

assert_eq!(
rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::DOWN).normalized()),
pos2(3.0, 3.0),
"bottom-right corner ray"
);

assert_eq!(
rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::UP).normalized()),
pos2(3.0, 1.0),
"top-right corner ray"
);
}
}
82 changes: 58 additions & 24 deletions crates/emath/src/vec2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ impl Vec2 {
}
}

/// Checks if `self` has length `1.0` up to a precision of `1e-6`.
#[inline(always)]
pub fn is_normalized(self) -> bool {
(self.length_sq() - 1.0).abs() < 2e-6
}

/// Rotates the vector by 90°, i.e positive X to positive Y
/// (clockwise in egui coordinates).
#[inline(always)]
Expand Down Expand Up @@ -497,41 +503,69 @@ impl fmt::Display for Vec2 {
}
}

#[test]
fn test_vec2() {
#[cfg(test)]
mod test {
use super::*;

macro_rules! almost_eq {
($left: expr, $right: expr) => {
let left = $left;
let right = $right;
assert!((left - right).abs() < 1e-6, "{} != {}", left, right);
};
}
use std::f32::consts::TAU;

assert_eq!(Vec2::ZERO.angle(), 0.0);
assert_eq!(Vec2::angled(0.0).angle(), 0.0);
assert_eq!(Vec2::angled(1.0).angle(), 1.0);
assert_eq!(Vec2::X.angle(), 0.0);
assert_eq!(Vec2::Y.angle(), 0.25 * TAU);
#[test]
fn test_vec2() {
use std::f32::consts::TAU;

assert_eq!(Vec2::ZERO.angle(), 0.0);
assert_eq!(Vec2::angled(0.0).angle(), 0.0);
assert_eq!(Vec2::angled(1.0).angle(), 1.0);
assert_eq!(Vec2::X.angle(), 0.0);
assert_eq!(Vec2::Y.angle(), 0.25 * TAU);

assert_eq!(Vec2::RIGHT.angle(), 0.0);
assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU);
almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU);
assert_eq!(Vec2::UP.angle(), -0.25 * TAU);

assert_eq!(Vec2::RIGHT.angle(), 0.0);
assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU);
almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU);
assert_eq!(Vec2::UP.angle(), -0.25 * TAU);
let mut assignment = vec2(1.0, 2.0);
assignment += vec2(3.0, 4.0);
assert_eq!(assignment, vec2(4.0, 6.0));

let mut assignment = vec2(1.0, 2.0);
assignment += vec2(3.0, 4.0);
assert_eq!(assignment, vec2(4.0, 6.0));
let mut assignment = vec2(4.0, 6.0);
assignment -= vec2(1.0, 2.0);
assert_eq!(assignment, vec2(3.0, 4.0));

let mut assignment = vec2(4.0, 6.0);
assignment -= vec2(1.0, 2.0);
assert_eq!(assignment, vec2(3.0, 4.0));
let mut assignment = vec2(1.0, 2.0);
assignment *= 2.0;
assert_eq!(assignment, vec2(2.0, 4.0));

let mut assignment = vec2(1.0, 2.0);
assignment *= 2.0;
assert_eq!(assignment, vec2(2.0, 4.0));
let mut assignment = vec2(2.0, 4.0);
assignment /= 2.0;
assert_eq!(assignment, vec2(1.0, 2.0));
}

#[test]
fn test_vec2_normalized() {
fn generate_spiral(n: usize, start: Vec2, end: Vec2) -> impl Iterator<Item = Vec2> {
let angle_step = 2.0 * std::f32::consts::PI / n as f32;
let radius_step = (end.length() - start.length()) / n as f32;

(0..n).map(move |i| {
let angle = i as f32 * angle_step;
let radius = start.length() + i as f32 * radius_step;
let x = radius * angle.cos();
let y = radius * angle.sin();
vec2(x, y)
})
}

let mut assignment = vec2(2.0, 4.0);
assignment /= 2.0;
assert_eq!(assignment, vec2(1.0, 2.0));
for v in generate_spiral(40, Vec2::splat(0.1), Vec2::splat(2.0)) {
let vn = v.normalized();
almost_eq!(vn.length(), 1.0);
assert!(vn.is_normalized());
}
}
}

0 comments on commit 6833cf5

Please sign in to comment.