Skip to main content

klayout_core/
coord.rs

1//! Integer coordinates and exact orthogonal transforms.
2//!
3//! All layout coordinates are i64 DBU. The float `DTrans` exists only as an
4//! escape hatch for arbitrary-angle work (mask rotation, angled photonics);
5//! conversion back to grid is explicit and lossy.
6
7use std::ops::{Add, Neg, Sub};
8
9#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
10pub struct Point {
11    pub x: i64,
12    pub y: i64,
13}
14
15impl Point {
16    pub const ZERO: Self = Self { x: 0, y: 0 };
17    #[inline]
18    pub const fn new(x: i64, y: i64) -> Self {
19        Self { x, y }
20    }
21}
22
23impl Add<Vec2> for Point {
24    type Output = Point;
25    #[inline]
26    fn add(self, v: Vec2) -> Point {
27        Point::new(self.x + v.x, self.y + v.y)
28    }
29}
30
31impl Sub for Point {
32    type Output = Vec2;
33    #[inline]
34    fn sub(self, o: Point) -> Vec2 {
35        Vec2::new(self.x - o.x, self.y - o.y)
36    }
37}
38
39#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
40pub struct Vec2 {
41    pub x: i64,
42    pub y: i64,
43}
44
45impl Vec2 {
46    pub const ZERO: Self = Self { x: 0, y: 0 };
47    #[inline]
48    pub const fn new(x: i64, y: i64) -> Self {
49        Self { x, y }
50    }
51}
52
53impl Add for Vec2 {
54    type Output = Vec2;
55    #[inline]
56    fn add(self, o: Vec2) -> Vec2 {
57        Vec2::new(self.x + o.x, self.y + o.y)
58    }
59}
60
61impl Sub for Vec2 {
62    type Output = Vec2;
63    #[inline]
64    fn sub(self, o: Vec2) -> Vec2 {
65        Vec2::new(self.x - o.x, self.y - o.y)
66    }
67}
68
69impl Neg for Vec2 {
70    type Output = Vec2;
71    #[inline]
72    fn neg(self) -> Vec2 {
73        Vec2::new(-self.x, -self.y)
74    }
75}
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
78pub struct Bbox {
79    pub min: Point,
80    pub max: Point,
81}
82
83impl Bbox {
84    pub const EMPTY: Self = Self {
85        min: Point::new(i64::MAX, i64::MAX),
86        max: Point::new(i64::MIN, i64::MIN),
87    };
88
89    #[inline]
90    pub const fn new(min: Point, max: Point) -> Self {
91        Self { min, max }
92    }
93
94    #[inline]
95    pub fn from_point(p: Point) -> Self {
96        Self { min: p, max: p }
97    }
98
99    #[inline]
100    pub fn is_empty(&self) -> bool {
101        self.min.x > self.max.x || self.min.y > self.max.y
102    }
103
104    #[inline]
105    pub fn width(&self) -> i64 {
106        if self.is_empty() {
107            0
108        } else {
109            self.max.x - self.min.x
110        }
111    }
112
113    #[inline]
114    pub fn height(&self) -> i64 {
115        if self.is_empty() {
116            0
117        } else {
118            self.max.y - self.min.y
119        }
120    }
121
122    pub fn contains(&self, p: Point) -> bool {
123        !self.is_empty()
124            && p.x >= self.min.x
125            && p.x <= self.max.x
126            && p.y >= self.min.y
127            && p.y <= self.max.y
128    }
129
130    pub fn intersects(&self, other: &Bbox) -> bool {
131        !self.is_empty()
132            && !other.is_empty()
133            && self.min.x <= other.max.x
134            && self.max.x >= other.min.x
135            && self.min.y <= other.max.y
136            && self.max.y >= other.min.y
137    }
138
139    pub fn intersection(&self, other: &Bbox) -> Bbox {
140        if self.is_empty() || other.is_empty() {
141            return Bbox::EMPTY;
142        }
143        let min = Point::new(self.min.x.max(other.min.x), self.min.y.max(other.min.y));
144        let max = Point::new(self.max.x.min(other.max.x), self.max.y.min(other.max.y));
145        if min.x > max.x || min.y > max.y {
146            Bbox::EMPTY
147        } else {
148            Bbox::new(min, max)
149        }
150    }
151
152    pub fn union(&self, other: &Bbox) -> Bbox {
153        if self.is_empty() {
154            return *other;
155        }
156        if other.is_empty() {
157            return *self;
158        }
159        Bbox::new(
160            Point::new(self.min.x.min(other.min.x), self.min.y.min(other.min.y)),
161            Point::new(self.max.x.max(other.max.x), self.max.y.max(other.max.y)),
162        )
163    }
164
165    pub fn expand_to(&mut self, p: Point) {
166        if self.is_empty() {
167            self.min = p;
168            self.max = p;
169        } else {
170            self.min.x = self.min.x.min(p.x);
171            self.min.y = self.min.y.min(p.y);
172            self.max.x = self.max.x.max(p.x);
173            self.max.y = self.max.y.max(p.y);
174        }
175    }
176
177    pub fn corners(&self) -> [Point; 4] {
178        [
179            self.min,
180            Point::new(self.max.x, self.min.y),
181            self.max,
182            Point::new(self.min.x, self.max.y),
183        ]
184    }
185}
186
187impl Default for Bbox {
188    fn default() -> Self {
189        Self::EMPTY
190    }
191}
192
193#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
194#[repr(u8)]
195pub enum Rot4 {
196    R0 = 0,
197    R90 = 1,
198    R180 = 2,
199    R270 = 3,
200}
201
202impl Rot4 {
203    #[inline]
204    pub fn apply(self, p: Point) -> Point {
205        match self {
206            Rot4::R0 => p,
207            Rot4::R90 => Point::new(-p.y, p.x),
208            Rot4::R180 => Point::new(-p.x, -p.y),
209            Rot4::R270 => Point::new(p.y, -p.x),
210        }
211    }
212
213    #[inline]
214    pub fn apply_vec(self, v: Vec2) -> Vec2 {
215        match self {
216            Rot4::R0 => v,
217            Rot4::R90 => Vec2::new(-v.y, v.x),
218            Rot4::R180 => Vec2::new(-v.x, -v.y),
219            Rot4::R270 => Vec2::new(v.y, -v.x),
220        }
221    }
222
223    #[inline]
224    pub fn compose(self, other: Rot4) -> Rot4 {
225        let s = self as u8;
226        let o = other as u8;
227        match (s + o) % 4 {
228            0 => Rot4::R0,
229            1 => Rot4::R90,
230            2 => Rot4::R180,
231            _ => Rot4::R270,
232        }
233    }
234
235    #[inline]
236    pub fn inverse(self) -> Rot4 {
237        match self {
238            Rot4::R0 => Rot4::R0,
239            Rot4::R90 => Rot4::R270,
240            Rot4::R180 => Rot4::R180,
241            Rot4::R270 => Rot4::R90,
242        }
243    }
244}
245
246/// Exact orthogonal transform: optional mirror about X axis, then rotation,
247/// then translation. Composition is exact; coordinates stay on grid.
248#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
249pub struct Trans {
250    pub rot: Rot4,
251    pub mirror: bool,
252    pub disp: Vec2,
253}
254
255impl Trans {
256    pub const IDENTITY: Self = Self {
257        rot: Rot4::R0,
258        mirror: false,
259        disp: Vec2::ZERO,
260    };
261
262    #[inline]
263    pub const fn new(rot: Rot4, mirror: bool, disp: Vec2) -> Self {
264        Self { rot, mirror, disp }
265    }
266
267    #[inline]
268    pub fn translate(disp: Vec2) -> Self {
269        Self::new(Rot4::R0, false, disp)
270    }
271
272    #[inline]
273    pub fn rotate(rot: Rot4) -> Self {
274        Self::new(rot, false, Vec2::ZERO)
275    }
276
277    #[inline]
278    pub fn mirror_x() -> Self {
279        Self::new(Rot4::R0, true, Vec2::ZERO)
280    }
281
282    #[inline]
283    pub fn apply(self, p: Point) -> Point {
284        let p = if self.mirror { Point::new(p.x, -p.y) } else { p };
285        let p = self.rot.apply(p);
286        Point::new(p.x + self.disp.x, p.y + self.disp.y)
287    }
288
289    pub fn apply_bbox(self, b: Bbox) -> Bbox {
290        if b.is_empty() {
291            return b;
292        }
293        let mut out = Bbox::EMPTY;
294        for c in b.corners() {
295            out.expand_to(self.apply(c));
296        }
297        out
298    }
299
300    /// Composition: `(self * other).apply(p) == self.apply(other.apply(p))`.
301    pub fn compose(self, other: Trans) -> Trans {
302        let mirror = self.mirror ^ other.mirror;
303        // Mirror across X commutes with rotation by negating the rotation.
304        let inner_rot = if self.mirror {
305            other.rot.inverse()
306        } else {
307            other.rot
308        };
309        let rot = self.rot.compose(inner_rot);
310        // Apply self to other.disp, then add self.disp.
311        let other_disp = if self.mirror {
312            Vec2::new(other.disp.x, -other.disp.y)
313        } else {
314            other.disp
315        };
316        let rotated = self.rot.apply_vec(other_disp);
317        let disp = Vec2::new(rotated.x + self.disp.x, rotated.y + self.disp.y);
318        Trans { rot, mirror, disp }
319    }
320
321    pub fn inverse(self) -> Trans {
322        // self(p) = R(M(p)) + d  =>  inverse: M'(R'(q - d)) where M' = M, R' = R^-1
323        let neg_d = -self.disp;
324        let rot_inv = self.rot.inverse();
325        let r_neg_d = rot_inv.apply_vec(neg_d);
326        let disp = if self.mirror {
327            Vec2::new(r_neg_d.x, -r_neg_d.y)
328        } else {
329            r_neg_d
330        };
331        Trans {
332            rot: if self.mirror { self.rot } else { rot_inv },
333            mirror: self.mirror,
334            disp,
335        }
336    }
337}
338
339impl Default for Trans {
340    fn default() -> Self {
341        Self::IDENTITY
342    }
343}
344
345/// Floating-point complex transform. Use only for non-orthogonal rotation
346/// or magnification. Conversion back to integer DBU is explicit and lossy.
347#[derive(Copy, Clone, Debug, PartialEq)]
348pub struct DTrans {
349    pub angle_deg: f64,
350    pub mag: f64,
351    pub mirror: bool,
352    pub disp: Vec2,
353}
354
355impl DTrans {
356    pub const IDENTITY: Self = Self {
357        angle_deg: 0.0,
358        mag: 1.0,
359        mirror: false,
360        disp: Vec2::ZERO,
361    };
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn rot90_apply() {
370        let p = Point::new(10, 0);
371        assert_eq!(Rot4::R90.apply(p), Point::new(0, 10));
372        assert_eq!(Rot4::R180.apply(p), Point::new(-10, 0));
373        assert_eq!(Rot4::R270.apply(p), Point::new(0, -10));
374    }
375
376    #[test]
377    fn trans_inverse_roundtrip() {
378        let t = Trans::new(Rot4::R90, true, Vec2::new(7, -3));
379        let inv = t.inverse();
380        for p in [
381            Point::new(0, 0),
382            Point::new(100, 200),
383            Point::new(-50, 75),
384        ] {
385            assert_eq!(inv.apply(t.apply(p)), p, "inverse failed for {p:?}");
386        }
387    }
388
389    #[test]
390    fn trans_compose_matches_apply() {
391        let a = Trans::new(Rot4::R90, false, Vec2::new(10, 20));
392        let b = Trans::new(Rot4::R180, true, Vec2::new(-5, 3));
393        let ab = a.compose(b);
394        for p in [Point::new(1, 2), Point::new(-7, 8), Point::new(0, 0)] {
395            assert_eq!(ab.apply(p), a.apply(b.apply(p)));
396        }
397    }
398
399    #[test]
400    fn bbox_ops() {
401        let a = Bbox::new(Point::new(0, 0), Point::new(10, 10));
402        let b = Bbox::new(Point::new(5, 5), Point::new(15, 15));
403        assert!(a.intersects(&b));
404        assert_eq!(
405            a.union(&b),
406            Bbox::new(Point::new(0, 0), Point::new(15, 15))
407        );
408        assert!(Bbox::EMPTY.is_empty());
409    }
410}