Skip to content
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

Make raycasting code more extensible #940

Merged
merged 6 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions crates/fj-kernel/src/algorithms/cast_ray/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Ray casting

mod segment;

pub use self::segment::RaySegmentHit;

use fj_math::Point;

/// Implemented by types that support ray casting
pub trait CastRay<const D: usize> {
/// The type that describes a hit of the ray on the implementing type
type Hit;

/// Cast a ray against `self`
fn cast_ray(&self, ray: HorizontalRayToTheRight<D>) -> Option<Self::Hit>;
}

/// A horizontal ray that goes to the right
///
/// For in-kernel use, we don't need anything more flexible, and being exactly
/// horizontal simplifies some calculations.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HorizontalRayToTheRight<const D: usize> {
/// The point where the ray originates
pub origin: Point<D>,
}

impl<P, const D: usize> From<P> for HorizontalRayToTheRight<D>
where
P: Into<Point<D>>,
{
fn from(point: P) -> Self {
Self {
origin: point.into(),
}
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,31 @@
//! Ray casting
use fj_math::Segment;

use fj_math::{Point, Segment};
use super::{CastRay, HorizontalRayToTheRight};

/// A horizontal ray that goes to the right
///
/// For in-kernel use, we don't need anything more flexible, and being exactly
/// horizontal simplifies some calculations.
pub struct HorizontalRayToTheRight<const D: usize> {
/// The point where the ray originates
pub origin: Point<D>,
}
impl CastRay<2> for Segment<2> {
type Hit = RaySegmentHit;

impl HorizontalRayToTheRight<2> {
/// Determine whether the ray hits the given line segment
pub fn hits_segment(
fn cast_ray(
&self,
segment: impl Into<Segment<2>>,
ray: HorizontalRayToTheRight<2>,
) -> Option<RaySegmentHit> {
let [a, b] = segment.into().points();
let [a, b] = self.points();
let [lower, upper] = if a.v <= b.v { [a, b] } else { [b, a] };
let right = if a.u > b.u { a } else { b };

if self.origin.v > upper.v {
if ray.origin.v > upper.v {
// ray is above segment
return None;
}
if self.origin.v < lower.v {
if ray.origin.v < lower.v {
// ray is below segment
return None;
}

if self.origin.v == lower.v && lower.v == upper.v {
if ray.origin.v == lower.v && lower.v == upper.v {
// ray and segment are parallel and at same height

if self.origin.u > right.u {
if ray.origin.u > right.u {
return None;
}

Expand All @@ -49,17 +41,17 @@ impl HorizontalRayToTheRight<2> {
y: upper.v,
};
let pc = robust::Coord {
x: self.origin.u,
y: self.origin.v,
x: ray.origin.u,
y: ray.origin.v,
};

if robust::orient2d(pa, pb, pc) >= 0. {
// ray starts on the line or left of it

if self.origin.v == upper.v {
if ray.origin.v == upper.v {
return Some(RaySegmentHit::UpperVertex);
}
if self.origin.v == lower.v {
if ray.origin.v == lower.v {
return Some(RaySegmentHit::LowerVertex);
}

Expand All @@ -70,17 +62,6 @@ impl HorizontalRayToTheRight<2> {
}
}

impl<P, const D: usize> From<P> for HorizontalRayToTheRight<D>
where
P: Into<Point<D>>,
{
fn from(point: P) -> Self {
Self {
origin: point.into(),
}
}
}

/// A hit between a ray and a line segment
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RaySegmentHit {
Expand All @@ -99,20 +80,24 @@ pub enum RaySegmentHit {

#[cfg(test)]
mod tests {
use fj_math::Segment;

use crate::algorithms::cast_ray::CastRay;

use super::{HorizontalRayToTheRight, RaySegmentHit};

#[test]
fn hits_segment_right() {
let ray = HorizontalRayToTheRight::from([0., 2.]);

let below = [[1., 0.], [1., 1.]];
let above = [[1., 3.], [1., 4.]];
let same_level = [[1., 1.], [1., 3.]];
let below = Segment::from([[1., 0.], [1., 1.]]);
let above = Segment::from([[1., 3.], [1., 4.]]);
let same_level = Segment::from([[1., 1.], [1., 3.]]);

assert!(ray.hits_segment(below).is_none());
assert!(ray.hits_segment(above).is_none());
assert!(below.cast_ray(ray).is_none());
assert!(above.cast_ray(ray).is_none());
assert!(matches!(
ray.hits_segment(same_level),
same_level.cast_ray(ray),
Some(RaySegmentHit::Segment)
));
}
Expand All @@ -121,31 +106,31 @@ mod tests {
fn hits_segment_left() {
let ray = HorizontalRayToTheRight::from([1., 2.]);

let same_level = [[0., 1.], [0., 3.]];
assert!(ray.hits_segment(same_level).is_none());
let same_level = Segment::from([[0., 1.], [0., 3.]]);
assert!(same_level.cast_ray(ray).is_none());
}

#[test]
fn hits_segment_overlapping() {
let ray = HorizontalRayToTheRight::from([1., 1.]);

let no_hit = [[0., 0.], [2., 3.]];
let no_hit = Segment::from([[0., 0.], [2., 3.]]);

let hit_segment = [[0., 0.], [3., 2.]];
let hit_upper = [[0., 0.], [2., 1.]];
let hit_lower = [[0., 2.], [2., 1.]];
let hit_segment = Segment::from([[0., 0.], [3., 2.]]);
let hit_upper = Segment::from([[0., 0.], [2., 1.]]);
let hit_lower = Segment::from([[0., 2.], [2., 1.]]);

assert!(ray.hits_segment(no_hit).is_none());
assert!(no_hit.cast_ray(ray).is_none());
assert!(matches!(
ray.hits_segment(hit_segment),
hit_segment.cast_ray(ray),
Some(RaySegmentHit::Segment)
));
assert!(matches!(
ray.hits_segment(hit_upper),
hit_upper.cast_ray(ray),
Some(RaySegmentHit::UpperVertex),
));
assert!(matches!(
ray.hits_segment(hit_lower),
hit_lower.cast_ray(ray),
Some(RaySegmentHit::LowerVertex),
));
}
Expand All @@ -154,20 +139,20 @@ mod tests {
fn hits_segment_on_segment() {
let ray = HorizontalRayToTheRight::from([1., 1.]);

let hit_segment = [[0., 0.], [2., 2.]];
let hit_upper = [[0., 0.], [1., 1.]];
let hit_lower = [[1., 1.], [2., 2.]];
let hit_segment = Segment::from([[0., 0.], [2., 2.]]);
let hit_upper = Segment::from([[0., 0.], [1., 1.]]);
let hit_lower = Segment::from([[1., 1.], [2., 2.]]);

assert!(matches!(
ray.hits_segment(hit_segment),
hit_segment.cast_ray(ray),
Some(RaySegmentHit::Segment)
));
assert!(matches!(
ray.hits_segment(hit_upper),
hit_upper.cast_ray(ray),
Some(RaySegmentHit::UpperVertex),
));
assert!(matches!(
ray.hits_segment(hit_lower),
hit_lower.cast_ray(ray),
Some(RaySegmentHit::LowerVertex),
));
}
Expand All @@ -176,18 +161,15 @@ mod tests {
fn hits_segment_parallel() {
let ray = HorizontalRayToTheRight::from([2., 0.]);

let left = [[0., 0.], [1., 0.]];
let overlapping = [[1., 0.], [3., 0.]];
let right = [[3., 0.], [4., 0.]];
let left = Segment::from([[0., 0.], [1., 0.]]);
let overlapping = Segment::from([[1., 0.], [3., 0.]]);
let right = Segment::from([[3., 0.], [4., 0.]]);

assert!(ray.hits_segment(left).is_none());
assert!(matches!(
ray.hits_segment(overlapping),
Some(RaySegmentHit::Parallel)
));
assert!(left.cast_ray(ray).is_none());
assert!(matches!(
ray.hits_segment(right),
overlapping.cast_ray(ray),
Some(RaySegmentHit::Parallel)
));
assert!(matches!(right.cast_ray(ray), Some(RaySegmentHit::Parallel)));
}
}
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ mod sweep;
mod transform;
mod triangulate;

pub mod cast_ray;
pub mod intersection;
pub mod ray_cast;

pub use self::{
approx::{CycleApprox, FaceApprox, InvalidTolerance, Tolerance},
Expand Down
10 changes: 4 additions & 6 deletions crates/fj-kernel/src/algorithms/triangulate/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use fj_interop::debug::{DebugInfo, TriangleEdgeCheck};
use fj_math::{Point, PolyChain, Segment};

use crate::{
algorithms::ray_cast::{HorizontalRayToTheRight, RaySegmentHit},
algorithms::cast_ray::{CastRay, HorizontalRayToTheRight, RaySegmentHit},
objects::Surface,
};

Expand Down Expand Up @@ -154,13 +154,11 @@ impl Polygon {
// first segment. The logic in the loop properly takes care of that,
// as long as we initialize the `previous_hit` variable with the
// result of the last segment.
let mut previous_hit = edges
.last()
.copied()
.and_then(|edge| ray.hits_segment(edge));
let mut previous_hit =
edges.last().copied().and_then(|edge| edge.cast_ray(ray));

for edge in edges {
let hit = ray.hits_segment(edge);
let hit = edge.cast_ray(ray);

let count_hit = match (hit, previous_hit) {
(Some(RaySegmentHit::Segment), _) => {
Expand Down