diff --git a/topola/Cargo.toml b/topola/Cargo.toml index c9f0c3f..14ae63b 100644 --- a/topola/Cargo.toml +++ b/topola/Cargo.toml @@ -9,6 +9,7 @@ version = "0.1.0" edition = "2024" [dependencies] +dearcut = { version = "0.1", features = ["undoredo"] } derive-getters.workspace = true derive_more.workspace = true serde.workspace = true diff --git a/topola/src/board.rs b/topola/src/board.rs index b6f7c66..b7e2034 100644 --- a/topola/src/board.rs +++ b/topola/src/board.rs @@ -5,14 +5,16 @@ use derive_getters::{Dissolve, Getters}; use undoredo::{ApplyDelta, Delta, FlushDelta}; -use crate::layout::{Layout, LayoutHalfDelta}; +use crate::layout::{ + Arc, ArcId, Joint, JointId, Layout, LayoutHalfDelta, Segment, SegmentId, Via, ViaId, +}; struct Layer { name: String, index: usize, } -#[derive(Getters)] +#[derive(Clone, Debug, Getters)] pub struct Board { layout: Layout, } @@ -23,6 +25,22 @@ impl Board { layout: Layout::new(boundary), } } + + pub fn add_joint(&mut self, joint: Joint) -> JointId { + self.layout.add_joint(joint) + } + + pub fn add_segment(&mut self, segment: Segment) -> SegmentId { + self.layout.add_segment(segment) + } + + pub fn add_arc(&mut self, arc: Arc) -> ArcId { + self.layout.add_arc(arc) + } + + pub fn add_via(&mut self, via: Via) -> ViaId { + self.layout.add_via(via) + } } #[derive(Clone, Debug, Dissolve)] diff --git a/topola/src/layout.rs b/topola/src/layout.rs index 655461e..1ab1533 100644 --- a/topola/src/layout.rs +++ b/topola/src/layout.rs @@ -25,10 +25,11 @@ impl JointId { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Joint { - position: [i64; 2], - radius: u64, + pub position: [i64; 2], + pub layer: usize, + pub radius: u64, } #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -48,10 +49,11 @@ impl SegmentId { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Segment { - endpoints: [JointId; 2], - half_width: u64, + pub endpoints: [JointId; 2], + pub layer: usize, + pub half_width: u64, } #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -71,11 +73,12 @@ impl ArcId { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Arc { - endpoints: [JointId; 2], - focus: [i64; 2], - half_width: u64, + pub endpoints: [JointId; 2], + pub focus: [i64; 2], + pub layer: usize, + pub half_width: u64, } #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -95,10 +98,11 @@ impl ViaId { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Via { - endpoints: [JointId; 2], - radius: u64, + pub endpoints: [JointId; 2], + pub layer: usize, + pub radius: u64, } #[derive(Clone, Debug, Getters)] diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 21e8dc5..072bb67 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -4,6 +4,7 @@ mod board; mod layout; +mod navmesher; mod specctra; pub use crate::board::Board; diff --git a/topola/src/navmesher.rs b/topola/src/navmesher.rs new file mode 100644 index 0000000..7cb9890 --- /dev/null +++ b/topola/src/navmesher.rs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use dearcut::RecordingTriangulator; +use derive_getters::Getters; + +use crate::{ + Board, + layout::{Arc, ArcId, Joint, JointId, Segment, SegmentId, Via, ViaId}, +}; + +#[derive(Clone, Debug, Getters)] +pub struct LayerNavmesher { + navmeshes: Vec>, + inflation_factors: Vec, +} + +impl LayerNavmesher { + pub fn insert_polygon(&mut self, polygon: impl IntoIterator) { + let polygon: Vec<[i64; 2]> = polygon.into_iter().collect(); + + for i in 0..self.navmeshes.len() { + self.navmeshes[i].insert_polygon(Self::inflate_polygon( + polygon.clone(), + self.inflation_factors[i], + )); + } + } + + fn inflate_polygon( + polygon: impl IntoIterator, + inflation_factor: f64, + ) -> impl IntoIterator { + let polygon: Vec<[i64; 2]> = polygon.into_iter().collect(); + + // Centroid. + let cx = polygon.iter().map(|p| p[0] as f64).sum::() / polygon.len() as f64; + let cy = polygon.iter().map(|p| p[1] as f64).sum::() / polygon.len() as f64; + + polygon.into_iter().map(move |[px, py]| { + // Delta. + let dx = px as f64 - cx; + let dy = py as f64 - cy; + let d = (dx * dx + dy * dy).sqrt(); + + // Normalize delta. + let nx = dx / d; + let ny = dy / d; + + // Shift away from centroid. + let fx = px as f64 + nx * inflation_factor; + let fy = py as f64 + ny * inflation_factor; + + // Round away from centroid. + let rx = if fx >= cx { fx.ceil() } else { fx.floor() }; + let ry = if fy >= cy { fy.ceil() } else { fy.floor() }; + + [rx as i64, ry as i64] + }) + } +} + +#[derive(Clone, Debug, Getters)] +pub struct Navmesher { + layers: Vec, +} + +impl Navmesher { + pub fn insert_polygon(&mut self, layer: usize, polygon: impl IntoIterator) { + self.layers[layer].insert_polygon(polygon); + } +} + +#[derive(Clone, Debug, Getters)] +pub struct NavmesherBoard { + navmesher: Navmesher, + board: Board, +} + +impl NavmesherBoard { + pub fn insert_joint(&mut self, joint: Joint) -> JointId { + self.navmesher + .insert_polygon(joint.layer, Self::joint_circumscribed_octagon(joint)); + self.board.add_joint(joint) + } + + fn joint_circumscribed_octagon(joint: Joint) -> [[i64; 2]; 8] { + let cx = joint.position[0]; + let cy = joint.position[1]; + let r = joint.radius as i64; + + // 1.082392... = 1 / cos(π/8) + // 0.414213... = tan(π/8) + + // Approximate multipliers as fractions. + let r1 = (r * 277 + 128) / 256; // round(r * 1.0823922) + let r2 = (r * 106 + 128) / 256; // round(r * 0.41421356) + + [ + [cx + r1, cy], // right + [cx + r2, cy + r2], // top-right + [cx, cy + r1], // top + [cx - r2, cy + r2], // top-left + [cx - r1, cy], // left + [cx - r2, cy - r2], // bottom-left + [cx, cy - r1], // bottom + [cx + r2, cy - r2], // bottom-right + ] + } + + pub fn insert_segment(&mut self, segment: Segment) -> SegmentId { + // TODO: Insert into navmesh. + self.board.add_segment(segment) + } + + pub fn insert_arc(&mut self, arc: Arc) -> ArcId { + // TODO: Insert into navmesh. + self.board.add_arc(arc) + } + + pub fn insert_via(&mut self, via: Via) -> ViaId { + // TODO: Insert into navmesh. + self.board.add_via(via) + } +}