diff --git a/crates/topola-egui/src/actions.rs b/crates/topola-egui/src/actions.rs index a96c76e..02326ac 100644 --- a/crates/topola-egui/src/actions.rs +++ b/crates/topola-egui/src/actions.rs @@ -166,6 +166,7 @@ pub struct ViewActions { pub zoom_to_fit: Switch, pub show_ratsnest: Switch, pub show_navmesh: Switch, + pub show_guide_circles: Switch, pub show_triangulation: Switch, pub show_triangulation_constraints: Switch, pub show_pathfinding_scores: Switch, @@ -181,6 +182,8 @@ impl ViewActions { zoom_to_fit: Action::new_keyless(tr.text("tr-menu-view-zoom-to-fit")).into_switch(), show_ratsnest: Action::new_keyless(tr.text("tr-menu-view-show-ratsnest")).into_switch(), show_navmesh: Action::new_keyless(tr.text("tr-menu-view-show-navmesh")).into_switch(), + show_guide_circles: Action::new_keyless(tr.text("tr-menu-view-show-guide-circles")) + .into_switch(), show_triangulation: Action::new_keyless(tr.text("tr-menu-view-show-triangulation")) .into_switch(), show_triangulation_constraints: Action::new_keyless( @@ -219,6 +222,8 @@ impl ViewActions { ui.add_enabled_ui(have_workspace, |ui| { self.show_ratsnest.checkbox(ui, &mut menu_bar.show_ratsnest); self.show_navmesh.checkbox(ui, &mut menu_bar.show_navmesh); + self.show_guide_circles + .checkbox(ui, &mut menu_bar.show_guide_circles); self.show_triangulation .checkbox(ui, &mut menu_bar.show_triangulation); self.show_triangulation_constraints diff --git a/crates/topola-egui/src/displayer.rs b/crates/topola-egui/src/displayer.rs index b5a7a97..ae2e091 100644 --- a/crates/topola-egui/src/displayer.rs +++ b/crates/topola-egui/src/displayer.rs @@ -11,7 +11,11 @@ use topola::{ autorouter::invoker::GetDebugOverlayData, board::AccessMesadata, drawing::{ + bend::BendIndex, + dot::DotIndex, graph::{MakePrimitive, PrimitiveIndex}, + guide::Guide, + head::GetFace, primitive::MakePrimitiveShape, }, geometry::{shape::AccessShape, GenericNode}, @@ -50,8 +54,8 @@ impl<'a> Displayer<'a> { self.display_ratsnest(); } - if menu_bar.show_navmesh { - self.display_navmesh(menu_bar); + if menu_bar.show_navmesh || menu_bar.show_guide_circles { + self.display_navmesh_or_guides(menu_bar); } if menu_bar.show_triangulation { @@ -158,7 +162,7 @@ impl<'a> Displayer<'a> { let from = graph.node_weight(edge.source()).unwrap().pos; let to = graph.node_weight(edge.target()).unwrap().pos; - self.painter.paint_edge( + self.painter.paint_line_segment( from, to, egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)), @@ -166,7 +170,7 @@ impl<'a> Displayer<'a> { } } - fn display_navmesh(&mut self, menu_bar: &MenuBar) { + fn display_navmesh_or_guides(&mut self, menu_bar: &MenuBar) { let board = self.workspace.interactor.invoker().autorouter().board(); if let Some(activity) = self.workspace.interactor.maybe_activity() { @@ -220,7 +224,9 @@ impl<'a> Displayer<'a> { egui::Stroke::new(1.0, egui::Color32::from_rgb(125, 125, 125)) }; - self.painter.paint_edge(from, to, stroke); + if menu_bar.show_navmesh { + self.painter.paint_line_segment(from, to, stroke); + } if let Some(text) = activity.navedge_debug_text((edge.source(), edge.target())) { @@ -235,11 +241,43 @@ impl<'a> Displayer<'a> { for index in navmesh.graph().node_indices() { let navnode = NavnodeIndex(index); - let mut pos = PrimitiveIndex::from(navmesh.node_weight(navnode).unwrap().node) + let primitive = + PrimitiveIndex::from(navmesh.node_weight(navnode).unwrap().node); + let mut pos = primitive .primitive(board.layout().drawing()) .shape() .center(); + if menu_bar.show_guide_circles { + if let Some(navcord) = activity.maybe_navcord() { + if let Ok(dot) = DotIndex::try_from(primitive) { + let drawing = board.layout().drawing(); + + self.painter.paint_hollow_circle( + drawing.dot_circle( + dot, + navcord.width, + drawing.conditions(navcord.head.face().into()).as_ref(), + ), + 1.0, + egui::epaint::Color32::WHITE, + ); + } else if let Ok(bend) = BendIndex::try_from(primitive) { + let drawing = board.layout().drawing(); + + self.painter.paint_hollow_circle( + drawing.bend_circle( + bend, + navcord.width, + drawing.conditions(navcord.head.face().into()).as_ref(), + ), + 1.0, + egui::epaint::Color32::WHITE, + ); + } + } + } + pos += match navmesh.node_weight(navnode).unwrap().maybe_sense { Some(RotationSense::Counterclockwise) => [0.0, 150.0].into(), Some(RotationSense::Clockwise) => [-0.0, -150.0].into(), @@ -286,7 +324,7 @@ impl<'a> Displayer<'a> { .shape() .center(); - self.painter.paint_edge( + self.painter.paint_line_segment( from, to, egui::Stroke::new(1.0, egui::Color32::from_rgb(255, 255, 255)), @@ -307,7 +345,7 @@ impl<'a> Displayer<'a> { let from = from_weight.pos + [100.0, 100.0].into(); let to = to_weight.pos + [100.0, 100.0].into(); - self.painter.paint_edge( + self.painter.paint_line_segment( from, to, egui::Stroke::new(1.0, egui::Color32::from_rgb(255, 255, 0)), @@ -399,14 +437,14 @@ impl<'a> Displayer<'a> { offset_lhs /= offset_lhs.dot(offset_lhs).sqrt() / 50.0; let mut offset_rhs = rhs - middle; offset_rhs /= offset_rhs.dot(offset_rhs).sqrt() / 50.0; - self.painter.paint_edge( + self.painter.paint_line_segment( a_pos + offset_lhs, b_pos + offset_lhs, make_stroke(edge_lens[0].len()), ); self.painter - .paint_edge(a_pos, b_pos, make_stroke(edge_lens[1].len())); - self.painter.paint_edge( + .paint_line_segment(a_pos, b_pos, make_stroke(edge_lens[1].len())); + self.painter.paint_line_segment( a_pos + offset_rhs, b_pos + offset_rhs, make_stroke(edge_lens[2].len()), @@ -416,7 +454,8 @@ impl<'a> Displayer<'a> { .iter() .filter(|i| matches!(i, pie::RelaxedPath::Normal(_))) .count(); - self.painter.paint_edge(a_pos, b_pos, make_stroke(edge_len)); + self.painter + .paint_line_segment(a_pos, b_pos, make_stroke(edge_len)); } } } @@ -447,25 +486,25 @@ impl<'a> Displayer<'a> { activity.activity() { self.painter - .paint_linestring(&rp.lines, egui::Color32::from_rgb(245, 182, 66)); + .paint_polyline(&rp.lines, egui::Color32::from_rgb(245, 182, 66)); } for linestring in activity.polygonal_blockers() { self.painter - .paint_linestring(linestring, egui::Color32::from_rgb(115, 0, 255)); + .paint_polyline(linestring, egui::Color32::from_rgb(115, 0, 255)); } if let Some(ref navmesh) = activity.maybe_thetastar().map(|astar| astar.graph()) { if menu_bar.show_origin_destination { let (origin, destination) = (navmesh.origin(), navmesh.destination()); - self.painter.paint_dot( + self.painter.paint_solid_circle( Circle { pos: board.layout().drawing().primitive(origin).shape().center(), r: 150.0, }, egui::Color32::from_rgb(255, 255, 100), ); - self.painter.paint_dot( + self.painter.paint_solid_circle( Circle { pos: board .layout() diff --git a/crates/topola-egui/src/menu_bar.rs b/crates/topola-egui/src/menu_bar.rs index ac7b142..11038db 100644 --- a/crates/topola-egui/src/menu_bar.rs +++ b/crates/topola-egui/src/menu_bar.rs @@ -29,6 +29,7 @@ pub struct MenuBar { pub is_placing_via: bool, pub show_ratsnest: bool, pub show_navmesh: bool, + pub show_guide_circles: bool, pub show_triangulation: bool, pub show_triangulation_constraints: bool, pub show_pathfinding_scores: bool, @@ -54,6 +55,7 @@ impl MenuBar { is_placing_via: false, show_ratsnest: true, show_navmesh: false, + show_guide_circles: false, show_triangulation: false, show_triangulation_constraints: false, show_pathfinding_scores: false, diff --git a/crates/topola-egui/src/painter.rs b/crates/topola-egui/src/painter.rs index e1a61b5..337cf7a 100644 --- a/crates/topola-egui/src/painter.rs +++ b/crates/topola-egui/src/painter.rs @@ -30,7 +30,7 @@ impl<'a> Painter<'a> { pub fn paint_primitive(&mut self, shape: &PrimitiveShape, color: egui::epaint::Color32) { let epaint_shape = match shape { - PrimitiveShape::Dot(dot) => self.dot_shape(dot.circle, color), + PrimitiveShape::Dot(dot) => self.solid_circle_shape(dot.circle, color), PrimitiveShape::Seg(seg) => egui::Shape::line_segment( [ self.transform @@ -75,12 +75,26 @@ impl<'a> Painter<'a> { )); } - pub fn paint_dot(&mut self, circle: Circle, color: egui::epaint::Color32) { - let shape = self.dot_shape(circle, color); + pub fn paint_hollow_circle( + &mut self, + circle: Circle, + width: f32, + color: egui::epaint::Color32, + ) { + self.ui.painter().add(egui::Shape::circle_stroke( + self.transform + .mul_pos([circle.pos.x() as f32, -circle.pos.y() as f32].into()), + circle.r as f32 * self.transform.scaling, + egui::Stroke { width, color }, + )); + } + + pub fn paint_solid_circle(&mut self, circle: Circle, color: egui::epaint::Color32) { + let shape = self.solid_circle_shape(circle, color); self.ui.painter().add(shape); } - fn dot_shape(&mut self, circle: Circle, color: egui::epaint::Color32) -> egui::Shape { + fn solid_circle_shape(&mut self, circle: Circle, color: egui::epaint::Color32) -> egui::Shape { egui::Shape::circle_filled( self.transform .mul_pos([circle.pos.x() as f32, -circle.pos.y() as f32].into()), @@ -89,7 +103,7 @@ impl<'a> Painter<'a> { ) } - pub fn paint_linestring(&mut self, linestring: &LineString, color: egui::epaint::Color32) { + pub fn paint_polyline(&mut self, linestring: &LineString, color: egui::epaint::Color32) { self.ui.painter().add(egui::Shape::line( linestring .exterior_coords_iter() @@ -120,7 +134,7 @@ impl<'a> Painter<'a> { )); } - pub fn paint_edge(&mut self, from: Point, to: Point, stroke: egui::Stroke) { + pub fn paint_line_segment(&mut self, from: Point, to: Point, stroke: egui::Stroke) { self.ui.painter().add(egui::Shape::line_segment( [ self.transform diff --git a/locales/en-US/main.ftl b/locales/en-US/main.ftl index 68d4634..e85e88d 100644 --- a/locales/en-US/main.ftl +++ b/locales/en-US/main.ftl @@ -22,6 +22,7 @@ tr-menu-view = View tr-menu-view-zoom-to-fit = Zoom to Fit tr-menu-view-show-ratsnest = Show Ratsnest tr-menu-view-show-navmesh = Show Navmesh +tr-menu-view-show-guide-circles = Show Guide-Circles tr-menu-view-show-triangulation = Show Triangulation tr-menu-view-show-triangulation-constraints = Show Triangulation Constraints tr-menu-view-show-pathfinding-scores = Show Pathfinding Scores diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index 41186c6..05a4119 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -39,10 +39,8 @@ use crate::{ AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, GetLayer, GetOffset, GetSetPos, GetWidth, }, - graph::MakeRef, - graph::{GenericIndex, GetPetgraphIndex}, - math::NoTangents, - math::RotationSense, + graph::{GenericIndex, GetPetgraphIndex, MakeRef}, + math::{NoTangents, RotationSense}, }; use super::gear::{GetOuterGears, WalkOutwards}; diff --git a/src/drawing/guide.rs b/src/drawing/guide.rs index 3ce6c4d..293f170 100644 --- a/src/drawing/guide.rs +++ b/src/drawing/guide.rs @@ -69,6 +69,22 @@ pub trait Guide { fn rear_head(&self, face: LooseDotIndex) -> Head; fn head(&self, face: DotIndex) -> Head; + + fn bend_circle( + &self, + bend: BendIndex, + width: f64, + guide_conditions: Option<&Conditions<'_>>, + ) -> Circle; + + fn dot_circle( + &self, + dot: DotIndex, + width: f64, + guide_conditions: Option<&Conditions<'_>>, + ) -> Circle; + + fn conditions(&self, node: PrimitiveIndex) -> Option>; } impl Guide for Drawing { @@ -200,6 +216,44 @@ impl Guide for Drawing { DotIndex::Loose(dot) => self.cane_head(dot).into(), } } + + fn dot_circle( + &self, + dot: DotIndex, + width: f64, + guide_conditions: Option<&Conditions<'_>>, + ) -> Circle { + let shape = dot.primitive(self).shape(); + Circle { + pos: shape.center(), + r: shape.width() / 2.0 + + width / 2.0 + + self.clearance(self.conditions(dot.into()).as_ref(), guide_conditions), + } + } + + fn bend_circle( + &self, + bend: BendIndex, + width: f64, + guide_conditions: Option<&Conditions<'_>>, + ) -> Circle { + let outer_circle = match bend.primitive(self).shape() { + PrimitiveShape::Bend(shape) => shape.outer_circle(), + _ => unreachable!(), + }; + + Circle { + pos: outer_circle.pos, + r: outer_circle.r + + width / 2.0 + + self.clearance(self.conditions(bend.into()).as_ref(), guide_conditions), + } + } + + fn conditions(&self, node: PrimitiveIndex) -> Option> { + node.primitive(self).conditions() + } } trait GuidePrivate { @@ -207,23 +261,7 @@ trait GuidePrivate { fn head_circle(&self, head: &Head, width: f64) -> Circle; - fn bend_circle( - &self, - bend: BendIndex, - width: f64, - guide_conditions: Option<&Conditions<'_>>, - ) -> Circle; - - fn dot_circle( - &self, - dot: DotIndex, - width: f64, - guide_conditions: Option<&Conditions<'_>>, - ) -> Circle; - fn rear(&self, head: CaneHead) -> DotIndex; - - fn conditions(&self, node: PrimitiveIndex) -> Option>; } impl GuidePrivate for Drawing { @@ -258,46 +296,8 @@ impl GuidePrivate for Drawing } } - fn bend_circle( - &self, - bend: BendIndex, - width: f64, - guide_conditions: Option<&Conditions<'_>>, - ) -> Circle { - let outer_circle = match bend.primitive(self).shape() { - PrimitiveShape::Bend(shape) => shape.outer_circle(), - _ => unreachable!(), - }; - - Circle { - pos: outer_circle.pos, - r: outer_circle.r - + width / 2.0 - + self.clearance(self.conditions(bend.into()).as_ref(), guide_conditions), - } - } - - fn dot_circle( - &self, - dot: DotIndex, - width: f64, - guide_conditions: Option<&Conditions<'_>>, - ) -> Circle { - let shape = dot.primitive(self).shape(); - Circle { - pos: shape.center(), - r: shape.width() / 2.0 - + width / 2.0 - + self.clearance(self.conditions(dot.into()).as_ref(), guide_conditions), - } - } - fn rear(&self, head: CaneHead) -> DotIndex { self.primitive(head.cane.seg) .other_joint(head.cane.dot.into()) } - - fn conditions(&self, node: PrimitiveIndex) -> Option> { - node.primitive(self).conditions() - } } diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs index 4b3ef35..fb490da 100644 --- a/src/drawing/mod.rs +++ b/src/drawing/mod.rs @@ -11,7 +11,7 @@ mod collect; pub mod dot; mod drawing; pub mod gear; -mod guide; +pub mod guide; pub mod head; pub mod loose; pub mod primitive; @@ -21,4 +21,3 @@ pub mod seg; pub use cane::Cane; pub use collect::Collect; pub use drawing::*; -pub use guide::Guide; diff --git a/src/router/draw.rs b/src/router/draw.rs index 7533626..547238d 100644 --- a/src/router/draw.rs +++ b/src/router/draw.rs @@ -15,11 +15,12 @@ use crate::{ dot::{DotIndex, FixedDotIndex, GeneralDotWeight, LooseDotIndex, LooseDotWeight}, gear::GearIndex, graph::{GetMaybeNet, MakePrimitive}, + guide::Guide, head::{CaneHead, GetFace, Head}, primitive::GetOtherJoint, rules::AccessRules, seg::{GeneralSegWeight, LoneLooseSegWeight, SeqLooseSegWeight}, - DrawingException, Guide, Infringement, + DrawingException, Infringement, }, geometry::{GetLayer, GetSetPos}, layout::{Layout, LayoutEdit},