use crate::Region;
use klayout_core::{Bbox, Point, Polygon};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Edge {
pub a: Point,
pub b: Point,
}
impl Edge {
pub const fn new(a: Point, b: Point) -> Self {
Self { a, b }
}
pub fn dx(&self) -> i64 {
self.b.x - self.a.x
}
pub fn dy(&self) -> i64 {
self.b.y - self.a.y
}
pub fn length_squared(&self) -> i128 {
let dx = self.dx() as i128;
let dy = self.dy() as i128;
dx * dx + dy * dy
}
pub fn length(&self) -> f64 {
(self.length_squared() as f64).sqrt()
}
pub fn midpoint(&self) -> Point {
Point::new((self.a.x + self.b.x) / 2, (self.a.y + self.b.y) / 2)
}
pub fn bbox(&self) -> Bbox {
Bbox::new(
Point::new(self.a.x.min(self.b.x), self.a.y.min(self.b.y)),
Point::new(self.a.x.max(self.b.x), self.a.y.max(self.b.y)),
)
}
pub fn angle_degrees(&self) -> f64 {
let mut a = (self.dy() as f64).atan2(self.dx() as f64).to_degrees();
if a < 0.0 {
a += 180.0;
}
if a >= 180.0 {
a -= 180.0;
}
a
}
pub fn is_horizontal(&self) -> bool {
self.dy() == 0 && self.dx() != 0
}
pub fn is_vertical(&self) -> bool {
self.dx() == 0 && self.dy() != 0
}
pub fn is_axis_aligned(&self) -> bool {
self.is_horizontal() || self.is_vertical()
}
}
#[derive(Default, Clone, Debug)]
pub struct Edges {
edges: Vec<Edge>,
}
impl Edges {
pub fn empty() -> Self {
Self::default()
}
pub fn from_edges<I: IntoIterator<Item = Edge>>(it: I) -> Self {
Self {
edges: it.into_iter().collect(),
}
}
pub fn from_polygon(p: &Polygon) -> Self {
let mut edges = Vec::new();
push_loop(&mut edges, &p.hull);
for hole in &p.holes {
push_loop(&mut edges, hole);
}
Self { edges }
}
pub fn from_region(r: &Region) -> Self {
let mut edges = Vec::new();
for p in r.polygons() {
push_loop(&mut edges, &p.hull);
for hole in &p.holes {
push_loop(&mut edges, hole);
}
}
Self { edges }
}
pub fn len(&self) -> usize {
self.edges.len()
}
pub fn is_empty(&self) -> bool {
self.edges.is_empty()
}
pub fn edges(&self) -> &[Edge] {
&self.edges
}
pub fn into_inner(self) -> Vec<Edge> {
self.edges
}
pub fn centers(&self, length: i64) -> Edges {
if length <= 0 {
return Edges::empty();
}
let mut out = Vec::with_capacity(self.edges.len());
for e in &self.edges {
let len2 = e.length_squared();
if len2 == 0 {
continue;
}
let len = (len2 as f64).sqrt();
if (len as i64) <= length {
out.push(*e);
continue;
}
let t = length as f64 / (2.0 * len);
let mid = e.midpoint();
let dx = e.dx() as f64;
let dy = e.dy() as f64;
let ax = mid.x as f64 - dx * t;
let ay = mid.y as f64 - dy * t;
let bx = mid.x as f64 + dx * t;
let by = mid.y as f64 + dy * t;
out.push(Edge::new(
Point::new(ax.round() as i64, ay.round() as i64),
Point::new(bx.round() as i64, by.round() as i64),
));
}
Edges::from_edges(out)
}
pub fn extended(&self, before: i64, after: i64) -> Edges {
let mut out = Vec::with_capacity(self.edges.len());
for e in &self.edges {
let len2 = e.length_squared();
if len2 == 0 {
out.push(*e);
continue;
}
let len = (len2 as f64).sqrt();
let dx = e.dx() as f64 / len;
let dy = e.dy() as f64 / len;
let ax = (e.a.x as f64) - dx * (before as f64);
let ay = (e.a.y as f64) - dy * (before as f64);
let bx = (e.b.x as f64) + dx * (after as f64);
let by = (e.b.y as f64) + dy * (after as f64);
out.push(Edge::new(
Point::new(ax.round() as i64, ay.round() as i64),
Point::new(bx.round() as i64, by.round() as i64),
));
}
Edges::from_edges(out)
}
pub fn with_angle(&self, angle_deg: f64, tolerance_deg: f64) -> Edges {
Edges::from_edges(self.edges.iter().copied().filter(|e| {
if e.length_squared() == 0 {
return false;
}
angles_close(e.angle_degrees(), angle_deg, tolerance_deg)
}))
}
pub fn not_with_angle(&self, angle_deg: f64, tolerance_deg: f64) -> Edges {
Edges::from_edges(self.edges.iter().copied().filter(|e| {
if e.length_squared() == 0 {
return false;
}
!angles_close(e.angle_degrees(), angle_deg, tolerance_deg)
}))
}
pub fn length_at_least(&self, min: i64) -> Edges {
let m2 = (min as i128) * (min as i128);
Edges::from_edges(
self.edges
.iter()
.copied()
.filter(|e| e.length_squared() >= m2),
)
}
pub fn length_at_most(&self, max: i64) -> Edges {
let m2 = (max as i128) * (max as i128);
Edges::from_edges(
self.edges
.iter()
.copied()
.filter(|e| e.length_squared() <= m2),
)
}
pub fn interacting(&self, other: &Edges) -> Edges {
if self.is_empty() || other.is_empty() {
return Edges::empty();
}
let other_bboxes: Vec<Bbox> = other.edges.iter().map(|e| e.bbox()).collect();
Edges::from_edges(self.edges.iter().copied().filter(|e| {
let eb = e.bbox();
other_bboxes.iter().any(|ob| eb.intersects(ob))
}))
}
pub fn outside_part(&self, region: &Region) -> Edges {
clip_edges(&self.edges, region, false)
}
pub fn inside_part(&self, region: &Region) -> Edges {
clip_edges(&self.edges, region, true)
}
}
fn push_loop(out: &mut Vec<Edge>, pts: &[Point]) {
let n = pts.len();
if n < 2 {
return;
}
for i in 0..n {
let a = pts[i];
let b = pts[(i + 1) % n];
if a != b {
out.push(Edge::new(a, b));
}
}
}
fn angles_close(a: f64, b: f64, tol: f64) -> bool {
let mut diff = (a - b).abs() % 180.0;
if diff > 90.0 {
diff = 180.0 - diff;
}
diff <= tol
}
fn clip_edges(edges: &[Edge], region: &Region, inside: bool) -> Edges {
if edges.is_empty() {
return Edges::empty();
}
if region.is_empty() {
return if inside {
Edges::empty()
} else {
Edges::from_edges(edges.iter().copied())
};
}
let mut out = Vec::with_capacity(edges.len());
for e in edges {
if !e.is_axis_aligned() {
out.push(*e);
continue;
}
let segments = clip_axis_aligned(e, region, inside);
out.extend(segments);
}
Edges::from_edges(out)
}
fn clip_axis_aligned(e: &Edge, region: &Region, inside: bool) -> Vec<Edge> {
let horizontal = e.is_horizontal();
let (axis_lo, axis_hi) = if horizontal {
let lo = e.a.x.min(e.b.x);
let hi = e.a.x.max(e.b.x);
(lo, hi)
} else {
let lo = e.a.y.min(e.b.y);
let hi = e.a.y.max(e.b.y);
(lo, hi)
};
let perp = if horizontal { e.a.y } else { e.a.x };
let bbox = e.bbox();
let mut splits: Vec<i64> = vec![axis_lo, axis_hi];
for poly in region.polygons() {
if !bbox.intersects(&poly.bbox()) {
continue;
}
for loop_pts in std::iter::once(poly.hull.as_slice()).chain(poly.holes.iter().map(|h| h.as_slice())) {
let n = loop_pts.len();
for i in 0..n {
let a = loop_pts[i];
let b = loop_pts[(i + 1) % n];
if let Some(x) = axis_aligned_crossing(a, b, perp, horizontal, axis_lo, axis_hi) {
splits.push(x);
}
}
}
}
splits.sort();
splits.dedup();
let mut segments = Vec::new();
for w in splits.windows(2) {
let (s, t) = (w[0], w[1]);
if s == t {
continue;
}
let mid_axis = (s + t) / 2;
let mid_point = if horizontal {
Point::new(mid_axis, perp)
} else {
Point::new(perp, mid_axis)
};
let in_region = region_contains_point(region, mid_point);
if inside == in_region {
let p_a = if horizontal {
Point::new(s, perp)
} else {
Point::new(perp, s)
};
let p_b = if horizontal {
Point::new(t, perp)
} else {
Point::new(perp, t)
};
let forward_axis = if horizontal { e.b.x - e.a.x } else { e.b.y - e.a.y } >= 0;
if forward_axis {
segments.push(Edge::new(p_a, p_b));
} else {
segments.push(Edge::new(p_b, p_a));
}
}
}
segments
}
fn axis_aligned_crossing(
a: Point,
b: Point,
perp: i64,
horizontal: bool,
lo: i64,
hi: i64,
) -> Option<i64> {
let (perp_a, perp_b, axis_a, axis_b) = if horizontal {
(a.y, b.y, a.x, b.x)
} else {
(a.x, b.x, a.y, b.y)
};
if perp_a == perp_b {
if perp_a == perp {
let lo_seg = axis_a.min(axis_b).max(lo);
let hi_seg = axis_a.max(axis_b).min(hi);
if lo_seg < hi_seg {
return Some(lo_seg);
}
}
return None;
}
if (perp_a < perp && perp_b < perp) || (perp_a > perp && perp_b > perp) {
return None;
}
let t = (perp - perp_a) as f64 / (perp_b - perp_a) as f64;
let axis = (axis_a as f64 + t * (axis_b as f64 - axis_a as f64)).round() as i64;
if axis < lo || axis > hi {
return None;
}
Some(axis)
}
fn region_contains_point(r: &Region, p: Point) -> bool {
for poly in r.polygons() {
if !poly.bbox().contains(p) {
continue;
}
if point_in_polygon(p, &poly.hull) {
let in_hole = poly.holes.iter().any(|h| point_in_polygon(p, h));
if !in_hole {
return true;
}
}
}
false
}
fn point_in_polygon(p: Point, ring: &[Point]) -> bool {
let mut inside = false;
let n = ring.len();
if n < 3 {
return false;
}
for i in 0..n {
let a = ring[i];
let b = ring[(i + 1) % n];
let crosses = (a.y > p.y) != (b.y > p.y);
if crosses {
let t = (p.y - a.y) as f64 / (b.y - a.y) as f64;
let x_at = a.x as f64 + t * (b.x - a.x) as f64;
if x_at > p.x as f64 {
inside = !inside;
}
}
}
inside
}
#[cfg(test)]
mod tests {
use super::*;
use klayout_core::Polygon;
fn pt(x: i64, y: i64) -> Point {
Point::new(x, y)
}
#[test]
fn polygon_edges_are_directed() {
let p = Polygon::rect(Bbox::new(pt(0, 0), pt(10, 5)));
let e = Edges::from_polygon(&p);
assert_eq!(e.len(), 4);
let dirs: Vec<(i64, i64)> = e.edges().iter().map(|x| (x.dx(), x.dy())).collect();
assert!(dirs.contains(&(10, 0)));
assert!(dirs.contains(&(0, 5)));
assert!(dirs.contains(&(-10, 0)));
assert!(dirs.contains(&(0, -5)));
}
#[test]
fn with_angle_filters_horizontals() {
let p = Polygon::rect(Bbox::new(pt(0, 0), pt(10, 5)));
let e = Edges::from_polygon(&p);
let h = e.with_angle(0.0, 1.0);
assert_eq!(h.len(), 2); let v = e.with_angle(90.0, 1.0);
assert_eq!(v.len(), 2); }
#[test]
fn length_filter() {
let p = Polygon::rect(Bbox::new(pt(0, 0), pt(10, 5)));
let e = Edges::from_polygon(&p);
assert_eq!(e.length_at_least(8).len(), 2); assert_eq!(e.length_at_most(6).len(), 2); }
#[test]
fn centers_emits_centered_segment() {
let e = Edges::from_edges([Edge::new(pt(0, 0), pt(10, 0))]);
let c = e.centers(2);
assert_eq!(c.len(), 1);
let seg = c.edges()[0];
assert_eq!(seg.a, pt(4, 0));
assert_eq!(seg.b, pt(6, 0));
}
#[test]
fn extended_grows_endpoints() {
let e = Edges::from_edges([Edge::new(pt(0, 0), pt(10, 0))]);
let x = e.extended(2, 3);
assert_eq!(x.edges()[0].a, pt(-2, 0));
assert_eq!(x.edges()[0].b, pt(13, 0));
}
#[test]
fn interacting_finds_overlapping_bboxes() {
let a = Edges::from_edges([Edge::new(pt(0, 0), pt(10, 0))]);
let b = Edges::from_edges([
Edge::new(pt(5, 0), pt(5, 10)),
Edge::new(pt(100, 100), pt(110, 100)),
]);
let i = a.interacting(&b);
assert_eq!(i.len(), 1);
}
#[test]
fn outside_part_clips_against_region() {
let e = Edges::from_edges([Edge::new(pt(0, 0), pt(20, 0))]);
let r = Region::from_polygons([Polygon::rect(Bbox::new(pt(5, -5), pt(15, 5)))]);
let out = e.outside_part(&r);
assert_eq!(out.len(), 2);
}
#[test]
fn inside_part_clips_against_region() {
let e = Edges::from_edges([Edge::new(pt(0, 0), pt(20, 0))]);
let r = Region::from_polygons([Polygon::rect(Bbox::new(pt(5, -5), pt(15, 5)))]);
let inside = e.inside_part(&r);
assert_eq!(inside.len(), 1);
let seg = inside.edges()[0];
assert_eq!(seg.bbox().min.x, 5);
assert_eq!(seg.bbox().max.x, 15);
}
}