// SPDX-FileCopyrightText: 2024 Topola contributors // // SPDX-License-Identifier: MIT use core::marker::PhantomData; use enum_dispatch::enum_dispatch; use geo::Point; use petgraph::{ stable_graph::{NodeIndex, StableDiGraph}, visit::{EdgeRef, Walker}, Direction::{Incoming, Outgoing}, }; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; use crate::{ drawing::{ bend::{BendWeight, FixedBendWeight, LooseBendWeight}, dot::{DotWeight, FixedDotWeight, LooseDotWeight}, graph::PrimitiveWeight, primitive::Primitive, seg::{FixedSegWeight, LoneLooseSegWeight, SegWeight, SeqLooseSegWeight}, }, geometry::{ compound::ManageCompounds, primitive::{BendShape, DotShape, PrimitiveShape, SegShape}, }, graph::{GenericIndex, GetPetgraphIndex}, math::Circle, }; pub trait Retag { type Index: Sized + GetPetgraphIndex + PartialEq + Copy; fn retag(&self, index: NodeIndex) -> Self::Index; } #[enum_dispatch] pub trait GetLayer { fn layer(&self) -> usize; } #[enum_dispatch] pub trait GetSetPos { fn pos(&self) -> Point; fn set_pos(&mut self, pos: Point); } #[enum_dispatch] pub trait GetWidth { fn width(&self) -> f64; } #[enum_dispatch] pub trait GetOffset { fn offset(&self) -> f64; } #[enum_dispatch] pub trait SetOffset: GetOffset { fn set_offset(&mut self, offset: f64); } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum GeometryLabel { Joined, Outer, Core, Compound(Cel), } #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] pub enum GenericNode { Primitive(P), Compound(C), } impl GetPetgraphIndex for GenericNode { fn petgraph_index(&self) -> NodeIndex { match self { Self::Primitive(x) => x.petgraph_index(), Self::Compound(x) => x.petgraph_index(), } } } pub trait AccessDotWeight: GetSetPos + GetWidth + Copy {} impl AccessDotWeight for T {} pub trait AccessSegWeight: GetWidth + Copy {} impl AccessSegWeight for T {} pub trait AccessBendWeight: SetOffset + GetWidth + Copy {} impl AccessBendWeight for T {} #[derive(Debug)] pub struct Geometry { graph: StableDiGraph, GeometryLabel, usize>, primitive_weight_marker: PhantomData, dot_weight_marker: PhantomData, seg_weight_marker: PhantomData, bend_weight_marker: PhantomData, compound_weight_marker: PhantomData, index_marker: PhantomData, dot_index_marker: PhantomData, seg_index_marker: PhantomData, bend_index_marker: PhantomData, } impl Clone for Geometry { fn clone(&self) -> Self { Self { graph: self.graph.clone(), ..Self::new() } } } impl Default for Geometry { fn default() -> Self { Self::new() } } impl Geometry { pub fn new() -> Self { Self { graph: StableDiGraph::default(), primitive_weight_marker: PhantomData, dot_weight_marker: PhantomData, seg_weight_marker: PhantomData, bend_weight_marker: PhantomData, compound_weight_marker: PhantomData, index_marker: PhantomData, dot_index_marker: PhantomData, seg_index_marker: PhantomData, bend_index_marker: PhantomData, } } // we could use `derive_getters` to generate these, but `Geometry` only wraps a single // field that actually contains data... #[inline(always)] pub fn graph(&self) -> &StableDiGraph, GeometryLabel, usize> { &self.graph } fn primitive_weight(&self, index: NodeIndex) -> PW where PW: Copy, { if let GenericNode::Primitive(weight) = self.graph.node_weight(index).unwrap() { *weight } else { unreachable!() } } } impl< PW: GetWidth + TryInto + TryInto + TryInto + Retag + Copy, DW: AccessDotWeight + Into, SW: AccessSegWeight + Into, BW: AccessBendWeight + Into, CW, Cel, PI: GetPetgraphIndex + TryInto + TryInto + TryInto + Copy, DI: GetPetgraphIndex + Into + Copy, SI: GetPetgraphIndex + Into + Copy, BI: GetPetgraphIndex + Into + Copy, > Geometry { pub fn add_dot>(&mut self, weight: W) -> GenericIndex { GenericIndex::::new(self.graph.add_node(GenericNode::Primitive(weight.into()))) } pub(super) fn add_dot_at_index>( &mut self, dot: GenericIndex, weight: W, ) { self.graph .update_node(dot.petgraph_index(), GenericNode::Primitive(weight.into())); } pub fn add_seg>( &mut self, from: DI, to: DI, weight: W, ) -> GenericIndex { let seg = GenericIndex::::new(self.graph.add_node(GenericNode::Primitive(weight.into()))); self.init_seg_joints(seg, from, to); seg } pub(super) fn add_seg_at_index>( &mut self, seg: GenericIndex, from: DI, to: DI, weight: W, ) { self.graph .update_node(seg.petgraph_index(), GenericNode::Primitive(weight.into())); self.init_seg_joints(seg, from, to); } fn init_seg_joints>( &mut self, seg: GenericIndex, from: DI, to: DI, ) { self.graph.update_edge( from.petgraph_index(), seg.petgraph_index(), GeometryLabel::Joined, ); self.graph.update_edge( seg.petgraph_index(), to.petgraph_index(), GeometryLabel::Joined, ); } pub fn is_joined_with(&self, seg: I, node: GenericNode>) -> bool where I: Copy + GetPetgraphIndex, CW: Clone, Cel: Copy, { match node { GenericNode::Primitive(prim) => self .graph .find_edge_undirected(seg.petgraph_index(), prim.petgraph_index()) .map_or(false, |(eidx, _direction)| { matches!(self.graph.edge_weight(eidx).unwrap(), GeometryLabel::Joined) }), GenericNode::Compound(comp) => self .compound_members(comp) .any(|(_cel, i)| self.is_joined_with(seg, GenericNode::Primitive(i))), } } pub fn add_bend>( &mut self, from: DI, to: DI, core: DI, weight: W, ) -> GenericIndex { let bend = GenericIndex::::new(self.graph.add_node(GenericNode::Primitive(weight.into()))); self.init_bend_joints_and_core(bend, from, to, core); bend } pub(super) fn add_bend_at_index>( &mut self, bend: GenericIndex, from: DI, to: DI, core: DI, weight: W, ) { self.graph .update_node(bend.petgraph_index(), GenericNode::Primitive(weight.into())); self.init_bend_joints_and_core(bend, from, to, core); } pub(super) fn add_compound_at_index(&mut self, compound: GenericIndex, weight: CW) { self.graph .update_node(compound.petgraph_index(), GenericNode::Compound(weight)); } fn init_bend_joints_and_core>( &mut self, bend: GenericIndex, from: DI, to: DI, core: DI, ) { self.graph.update_edge( from.petgraph_index(), bend.petgraph_index(), GeometryLabel::Joined, ); self.graph.update_edge( bend.petgraph_index(), to.petgraph_index(), GeometryLabel::Joined, ); self.graph.update_edge( bend.petgraph_index(), core.petgraph_index(), GeometryLabel::Core, ); } pub fn remove_primitive(&mut self, primitive: PI) { debug_assert!(self.graph.remove_node(primitive.petgraph_index()).is_some()); } pub fn move_dot(&mut self, dot: DI, to: Point) { let mut weight = self.dot_weight(dot); weight.set_pos(to); *self.graph.node_weight_mut(dot.petgraph_index()).unwrap() = GenericNode::Primitive(weight.into()); } pub fn shift_bend(&mut self, bend: BI, offset: f64) { let mut weight = self.bend_weight(bend); weight.set_offset(offset); *self.graph.node_weight_mut(bend.petgraph_index()).unwrap() = GenericNode::Primitive(weight.into()); } pub fn flip_bend(&mut self, bend: BI) { let (from, to) = self.bend_joints(bend); let from_edge_weight = self .graph .remove_edge( self.graph .find_edge(from.petgraph_index(), bend.petgraph_index()) .unwrap(), ) .unwrap(); let to_edge_weight = self .graph .remove_edge( self.graph .find_edge(bend.petgraph_index(), to.petgraph_index()) .unwrap(), ) .unwrap(); self.graph .update_edge(from.petgraph_index(), bend.petgraph_index(), to_edge_weight); self.graph .update_edge(bend.petgraph_index(), to.petgraph_index(), from_edge_weight); } pub fn reattach_bend(&mut self, bend: BI, maybe_new_inner: Option) { if let Some(old_inner_edge) = self .graph .edges_directed(bend.petgraph_index(), Incoming) .find(|edge| matches!(edge.weight(), GeometryLabel::Outer)) { debug_assert!(self.graph.remove_edge(old_inner_edge.id()).is_some()); } if let Some(new_inner) = maybe_new_inner { self.graph.update_edge( new_inner.petgraph_index(), bend.petgraph_index(), GeometryLabel::Outer, ); } } pub fn dot_shape(&self, dot: DI) -> PrimitiveShape { let weight = self.dot_weight(dot); PrimitiveShape::Dot(DotShape { circle: Circle { pos: weight.pos(), r: weight.width() / 2.0, }, }) } pub fn seg_shape(&self, seg: SI) -> PrimitiveShape { let (from, to) = self.seg_joints(seg); PrimitiveShape::Seg(SegShape { from: self.dot_weight(from).pos(), to: self.dot_weight(to).pos(), width: self.primitive_weight(seg.petgraph_index()).width(), }) } pub fn bend_shape(&self, bend: BI) -> PrimitiveShape { let (from, to) = self.bend_joints(bend); let core_weight = self.core_weight(bend); PrimitiveShape::Bend(BendShape { from: self.dot_weight(from).pos(), to: self.dot_weight(to).pos(), inner_circle: Circle { pos: core_weight.pos(), r: self.inner_radius(bend), }, width: self.primitive_weight(bend.petgraph_index()).width(), }) } fn inner_radius(&self, bend: BI) -> f64 { let mut r = self.bend_weight(bend).offset(); let mut rail = bend; while let Some(inner) = self.inner(rail) { let weight: BW = self.bend_weight(inner); r += weight.width() + weight.offset(); rail = inner; } self.core_weight(bend).width() / 2.0 + r } pub fn dot_weight(&self, dot: DI) -> DW { self.primitive_weight(dot.petgraph_index()) .try_into() .unwrap_or_else(|_| unreachable!()) } pub fn seg_weight(&self, seg: SI) -> SW { self.primitive_weight(seg.petgraph_index()) .try_into() .unwrap_or_else(|_| unreachable!()) } pub fn bend_weight(&self, bend: BI) -> BW { self.primitive_weight(bend.petgraph_index()) .try_into() .unwrap_or_else(|_| unreachable!()) } pub fn compound_weight(&self, compound: GenericIndex) -> &CW { if let GenericNode::Compound(weight) = self.graph.node_weight(compound.petgraph_index()).unwrap() { weight } else { unreachable!() } } fn core_weight(&self, bend: BI) -> DW { self.graph .edges_directed(bend.petgraph_index(), Outgoing) .find(|edge| matches!(edge.weight(), GeometryLabel::Core)) .map(|edge| { self.primitive_weight(edge.target()) .try_into() .unwrap_or_else(|_| unreachable!()) }) .unwrap() } pub fn joineds(&self, node: PI) -> impl Iterator + '_ { self.graph .neighbors_undirected(node.petgraph_index()) .filter(move |ni| { matches!( self.graph .edge_weight( self.graph .find_edge_undirected(node.petgraph_index(), *ni) .unwrap() .0, ) .unwrap(), GeometryLabel::Joined ) }) .map(|ni| self.primitive_index(ni)) } pub fn joined_segs(&self, dot: DI) -> impl Iterator + '_ { self.joineds(dot.into()).filter_map(|ni| ni.try_into().ok()) } pub fn joined_bends(&self, dot: DI) -> impl Iterator + '_ { self.joineds(dot.into()).filter_map(|ni| ni.try_into().ok()) } fn joints(&self, node: PI) -> (DI, DI) { let lhs = self .graph .edges_directed(node.petgraph_index(), Incoming) .find(|edge| matches!(edge.weight(), GeometryLabel::Joined)) .map(|edge| edge.source()); let rhs = self .graph .edges_directed(node.petgraph_index(), Outgoing) .find(|edge| matches!(edge.weight(), GeometryLabel::Joined)) .map(|edge| edge.target()); ( lhs.map(|ni| { self.primitive_index(ni) .try_into() .unwrap_or_else(|_| unreachable!()) }) .unwrap(), rhs.map(|ni| { self.primitive_index(ni) .try_into() .unwrap_or_else(|_| unreachable!()) }) .unwrap(), ) } pub fn seg_joints(&self, seg: SI) -> (DI, DI) { self.joints(seg.into()) } pub fn bend_joints(&self, bend: BI) -> (DI, DI) { self.joints(bend.into()) } } impl, DW, SW, BW, CW, Cel, PI, DI, SI, BI> Geometry { fn primitive_index(&self, index: NodeIndex) -> PI { self.primitive_weight(index).retag(index) } } pub struct OutwardWalker { frontier: VecDeque, } impl OutwardWalker { pub fn new(initial_frontier: impl Iterator) -> Self { let mut frontier = VecDeque::new(); frontier.extend(initial_frontier); Self { frontier } } } impl< PW: Copy + Retag, DW, SW, BW, CW, Cel, PI: TryInto + TryInto + TryInto, DI, SI, BI: Copy + GetPetgraphIndex, > Walker<&Geometry> for OutwardWalker { type Item = BI; fn walk_next( &mut self, geometry: &Geometry, ) -> Option { let front = self.frontier.pop_front()?; self.frontier.extend(geometry.outers(front)); Some(front) } } impl< PW: Copy + Retag, DW, SW, BW, CW, Cel, PI: TryInto + TryInto + TryInto, DI, SI, BI: GetPetgraphIndex, > Geometry { pub fn all_rails(&self, node: NodeIndex) -> impl Iterator + '_ { self.graph .edges_directed(node, Incoming) .filter(|edge| matches!(edge.weight(), GeometryLabel::Core)) .map(|edge| { self.primitive_index(edge.source()) .try_into() .unwrap_or_else(|_| unreachable!()) }) } pub fn core(&self, bend: BI) -> DI { self.graph .edges_directed(bend.petgraph_index(), Outgoing) .find(|edge| matches!(edge.weight(), GeometryLabel::Core)) .map(|edge| { self.primitive_index(edge.target()) .try_into() .unwrap_or_else(|_| unreachable!()) }) .unwrap() } pub fn inner(&self, bend: BI) -> Option { self.graph .edges_directed(bend.petgraph_index(), Incoming) .find(|edge| matches!(edge.weight(), GeometryLabel::Outer)) .map(|edge| { self.primitive_index(edge.source()) .try_into() .unwrap_or_else(|_| unreachable!()) }) } pub fn outers(&self, bend: BI) -> impl Iterator + '_ { self.graph .edges_directed(bend.petgraph_index(), Outgoing) .filter(|edge| matches!(edge.weight(), GeometryLabel::Outer)) .map(|edge| { self.primitive_index(edge.target()) .try_into() .unwrap_or_else(|_| unreachable!()) }) } pub fn outwards(&self, bend: BI) -> OutwardWalker { OutwardWalker::new(self.outers(bend)) } } impl, DW, SW, BW, CW: Clone, Cel: Copy, PI: Copy, DI, SI, BI> ManageCompounds for Geometry { type GeneralIndex = PI; type EntryLabel = Cel; fn add_compound(&mut self, weight: CW) -> GenericIndex { GenericIndex::::new(self.graph.add_node(GenericNode::Compound(weight))) } fn remove_compound(&mut self, compound: GenericIndex) { debug_assert!(self.graph.remove_node(compound.petgraph_index()).is_some()); } fn add_to_compound(&mut self, primitive: I, entry_label: Cel, compound: GenericIndex) where I: Copy + GetPetgraphIndex, { self.graph.update_edge( primitive.petgraph_index(), compound.petgraph_index(), GeometryLabel::Compound(entry_label), ); } fn compound_weight(&self, compound: GenericIndex) -> &CW { if let GenericNode::Compound(weight) = self.graph.node_weight(compound.petgraph_index()).unwrap() { weight } else { unreachable!() } } fn compound_members( &self, compound: GenericIndex, ) -> impl Iterator + '_ { self.graph .edges_directed(compound.petgraph_index(), Incoming) .filter_map(|edge| { if let GeometryLabel::Compound(entry_label) = *edge.weight() { Some((entry_label, self.primitive_index(edge.source()))) } else { None } }) } fn compounds(&self, node: I) -> impl Iterator)> where I: Copy + GetPetgraphIndex, { self.graph .edges_directed(node.petgraph_index(), Outgoing) .filter_map(|edge| { if let GeometryLabel::Compound(entry_label) = *edge.weight() { Some((entry_label, GenericIndex::new(edge.target()))) } else { None } }) } }