// SPDX-FileCopyrightText: 2024 Topola contributors
//
// SPDX-License-Identifier: MIT
use std::marker::PhantomData;
use derive_getters::Getters;
use enum_dispatch::enum_dispatch;
use geo::Point;
use petgraph::{
stable_graph::{NodeIndex, StableDiGraph},
visit::EdgeRef,
Direction::{self, Incoming},
};
use serde::{Deserialize, Serialize};
use crate::{
drawing::{
bend::BendWeight,
dot::DotWeight,
graph::{PrimitiveWeight, Retag},
primitive::Primitive,
rules::AccessRules,
seg::SegWeight,
},
geometry::{
compound::ManageCompounds,
primitive::{BendShape, DotShape, PrimitiveShape, SegShape},
},
graph::{GenericIndex, GetPetgraphIndex},
math::Circle,
};
#[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(Debug, Clone, Copy, PartialEq)]
pub enum GeometryLabel {
Joined,
Outer,
Core,
Compound,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum GenericNode
{
Primitive(P),
Compound(C),
}
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, Getters)]
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<
PW: GetWidth + TryInto + TryInto + TryInto + Retag + Copy,
DW: AccessDotWeight + Into,
SW: AccessSegWeight + Into,
BW: AccessBendWeight + Into,
CW: Copy,
PI: GetPetgraphIndex + TryInto + TryInto + TryInto + Copy,
DI: GetPetgraphIndex + Into + Copy,
SI: GetPetgraphIndex + Into + Copy,
BI: GetPetgraphIndex + Into + Copy,
> 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,
}
}
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 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) {
self.graph.remove_node(primitive.petgraph_index());
}
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| *edge.weight() == GeometryLabel::Outer)
{
self.graph.remove_edge(old_inner_edge.id());
}
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
}
fn primitive_weight(&self, index: NodeIndex) -> PW {
if let GenericNode::Primitive(weight) = *self.graph.node_weight(index).unwrap() {
weight
} else {
unreachable!()
}
}
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
.neighbors(bend.petgraph_index())
.filter(|ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(bend.petgraph_index(), *ni).unwrap())
.unwrap(),
GeometryLabel::Core
)
})
.map(|ni| {
self.primitive_weight(ni)
.try_into()
.unwrap_or_else(|_| unreachable!())
})
.next()
.unwrap()
}
pub fn first_rail(&self, node: NodeIndex) -> Option {
self.graph
.neighbors_directed(node, Incoming)
.filter(|ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(*ni, node).unwrap())
.unwrap(),
GeometryLabel::Core
)
})
.map(|ni| {
self.primitive_weight(ni)
.retag(ni)
.try_into()
.unwrap_or_else(|_| unreachable!())
})
.next()
}
pub fn core(&self, bend: BI) -> DI {
self.graph
.neighbors(bend.petgraph_index())
.filter(|ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(bend.petgraph_index(), *ni).unwrap())
.unwrap(),
GeometryLabel::Core
)
})
.map(|ni| {
self.primitive_weight(ni)
.retag(ni)
.try_into()
.unwrap_or_else(|_| unreachable!())
})
.next()
.unwrap()
}
pub fn inner(&self, bend: BI) -> Option {
self.graph
.neighbors_directed(bend.petgraph_index(), Incoming)
.filter(|ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(*ni, bend.petgraph_index()).unwrap())
.unwrap(),
GeometryLabel::Outer
)
})
.map(|ni| {
self.primitive_weight(ni)
.retag(ni)
.try_into()
.unwrap_or_else(|_| unreachable!())
})
.next()
}
pub fn outer(&self, bend: BI) -> Option {
self.graph
.neighbors(bend.petgraph_index())
.filter(|ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(bend.petgraph_index(), *ni).unwrap())
.unwrap(),
GeometryLabel::Outer
)
})
.map(|ni| {
self.primitive_weight(ni)
.retag(ni)
.try_into()
.unwrap_or_else(|_| unreachable!())
})
.next()
}
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_weight(ni).retag(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) {
(
self.graph
.neighbors_directed(node.petgraph_index(), Direction::Incoming)
.find(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_weight(ni).retag(ni))
.map(|pi| pi.try_into().unwrap_or_else(|_| unreachable!()))
.unwrap(),
self.graph
.neighbors_directed(node.petgraph_index(), Direction::Outgoing)
.find(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_weight(ni).retag(ni))
.map(|pi| pi.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())
}
pub fn compound_members(&self, compound: GenericIndex) -> impl Iterator
- + '_ {
self.graph
.neighbors_directed(compound.petgraph_index(), Incoming)
.filter(move |ni| {
matches!(
self.graph
.edge_weight(
self.graph
.find_edge(*ni, compound.petgraph_index())
.unwrap()
)
.unwrap(),
GeometryLabel::Compound
)
})
.map(|ni| self.primitive_weight(ni).retag(ni))
}
}
impl<
PW: GetWidth + TryInto + TryInto + TryInto + Retag + Copy,
DW: AccessDotWeight + Into,
SW: AccessSegWeight + Into,
BW: AccessBendWeight + Into,
CW: Copy,
PI: GetPetgraphIndex + TryInto + TryInto + TryInto + Copy,
DI: GetPetgraphIndex + Into + Copy,
SI: GetPetgraphIndex + Into + Copy,
BI: GetPetgraphIndex + Into + Copy,
> ManageCompounds> for Geometry
{
fn add_compound(&mut self, weight: CW) -> GenericIndex {
GenericIndex::::new(self.graph.add_node(GenericNode::Compound(weight)))
}
fn remove_compound(&mut self, compound: GenericIndex) {
self.graph.remove_node(compound.petgraph_index());
}
fn add_to_compound(&mut self, primitive: GenericIndex, compound: GenericIndex) {
self.graph.update_edge(
primitive.petgraph_index(),
compound.petgraph_index(),
GeometryLabel::Compound,
);
}
fn compound_weight(&self, compound: GenericIndex) -> CW {
if let GenericNode::Compound(weight) =
*self.graph.node_weight(compound.petgraph_index()).unwrap()
{
weight
} else {
unreachable!()
}
}
fn compounds(&self, node: GenericIndex) -> impl Iterator
- > {
self.graph
.neighbors(node.petgraph_index())
.filter(move |ni| {
matches!(
self.graph
.edge_weight(self.graph.find_edge(node.petgraph_index(), *ni).unwrap())
.unwrap(),
GeometryLabel::Compound
)
})
.map(|ni| GenericIndex::new(ni))
}
}