use beambook::geometry::{point,Point,Rectangle};
pub fn segment_fits(rect:&Rectangle,p0:Point,p1:Point)->bool {
rect.contains(p0) && rect.contains(p1)
}
pub fn clip_segment_half_plane(u:Point,s:f64,p0:Point,p1:Point)->Option<(Point,Point)> {
let c0 = u.dot(p0) >= s;
let c1 = u.dot(p1) >= s;
match (c0,c1) {
(false,false) => None,
(true,true) => Some((p0,p1)),
(true,false)|(false,true) => {
let (p0,p1) =
if c0 {
(p0,p1)
} else {
(p1,p0)
};
let v = p1 - p0;
let lambda = (s - u.dot(p0))/u.dot(v);
Some((p0,p0 + lambda*v))
}
}
}
pub fn clip_segment_rect(rect:&Rectangle,p0:Point,p1:Point)->Option<(Point,Point)> {
clip_segment_half_plane(point(1.0,0.0),rect.a.x,p0,p1)
.and_then(|(p0,p1)| clip_segment_half_plane(point(-1.0,0.0),-rect.b.x,p0,p1))
.and_then(|(p0,p1)| clip_segment_half_plane(point(0.0,1.0),rect.a.y,p0,p1))
.and_then(|(p0,p1)| clip_segment_half_plane(point(0.0,-1.0),-rect.b.y,p0,p1))
}
pub struct LineBuilder {
lines:Vec<Vec<Point>>,
line:Vec<Point>
}
impl LineBuilder {
pub fn new()->Self {
Self{ lines:Vec::new(),line:Vec::new() }
}
pub fn flush_line(&mut self) {
if !self.line.is_empty() {
let mut new_line = Vec::new();
new_line.append(&mut self.line);
self.lines.push(new_line);
}
}
pub fn add_segment(&mut self,p0:Point,p1:Point) {
if self.line.is_empty() {
self.line.push(p0);
self.line.push(p1);
} else {
if self.line[self.line.len() - 1].equivalent(p0) {
self.line.push(p1);
} else {
self.flush_line();
self.line.push(p0);
self.line.push(p1);
}
}
}
pub fn finish(mut self)->Vec<Vec<Point>> {
self.flush_line();
self.lines
}
}
pub fn clip_line(rect:&Rectangle,line:&[Point])->Vec<Vec<Point>> {
let mut lb = LineBuilder::new();
let m = line.len();
for i in 1..m {
let p0 = line[i - 1];
let p1 = line[i];
let seg =
if segment_fits(rect,p0,p1) {
Some((p0,p1))
} else {
clip_segment_rect(rect,p0,p1)
};
if let Some((q0,q1)) = seg {
lb.add_segment(q0,q1)
}
}
lb.finish()
}
#[cfg(test)]
fn sample_along_segment(p0:Point,p1:Point)->Point {
p0 + fastrand::f64()*(p1-p0)
}
fn segment_contains(p0:Point,p1:Point,p:Point,tol:f64)->bool {
let t = p1 - p0;
let r = t.norm();
let u = t/r;
let v = u.ortho();
let dp = p - p0;
if v.dot(dp).abs() < tol {
let w = u.dot(dp);
-tol < w && w < r + tol
} else {
false
}
}
#[test]
fn test_clip() {
let tol = std::f64::EPSILON.sqrt();
let f = |r:f64| r*(2.0*fastrand::f64() - 1.0);
let d = 50.0;
let ntest = 200;
for iter in 0..ntest {
let (x0,x1,y0,y1) = (f(d),f(d),f(d),f(d));
let (x0,x1) = (x0.min(x1),x0.max(x1));
let (y0,y1) = (y0.min(y1),y0.max(y1));
let r = rectangle(point(x0,y0),point(x1,y1));
let s = fastrand::f64() * 2.0 * d;
let (x0,x1,y0,y1) = (f(s),f(s),f(s),f(s));
let p0 = point(x0,y0);
let p1 = point(x1,y1);
for _icheck in 0..ntest {
let p = sample_along_segment(p0,p1);
let sc = segment_contains(p0,p1,p,tol);
if !sc {
panic!("p0={:?} p1={:?} p={:?} sc={}",p0,p1,p,sc);
}
}
let s = clip_segment_rect(&r,p0,p1);
match s {
None => (),
Some((q0,q1)) =>
for _icheck in 0..ntest {
let q = sample_along_segment(q0,q1);
let rc = r.contains(q);
let sc = segment_contains(p0,p1,q,tol);
assert!(sc);
if !rc || !sc {
panic!("q0={:?} q1={:?} q={:?} rc={} sc={}",
q0,q1,q,rc,sc);
}
}
}
for _icheck in 0..ntest {
let p = sample_along_segment(p0,p1);
let sc =
s.map(|(q0,q1)| segment_contains(q0,q1,p,tol))
.unwrap_or(false);
let rc = r.contains(p);
assert!(rc == sc);
}
}
}