mirror of https://codeberg.org/topola/topola.git
301 lines
8.4 KiB
Rust
301 lines
8.4 KiB
Rust
// SPDX-FileCopyrightText: 2024 Topola contributors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use geo::Line;
|
|
|
|
use crate::{
|
|
geometry::{
|
|
primitive::{AccessPrimitiveShape, PrimitiveShape},
|
|
shape::AccessShape,
|
|
},
|
|
math::{self, Circle, NoTangents},
|
|
};
|
|
|
|
use super::{
|
|
bend::BendIndex,
|
|
dot::{DotIndex, FixedDotIndex, LooseDotIndex},
|
|
graph::{MakePrimitive, PrimitiveIndex},
|
|
head::{BareHead, CaneHead, GetFace, Head},
|
|
primitive::{GetCore, GetJoints, GetOtherJoint, GetWeight, MakePrimitiveShape},
|
|
rules::{AccessRules, Conditions, GetConditions},
|
|
Drawing,
|
|
};
|
|
|
|
pub trait Guide {
|
|
fn head_into_dot_segment(
|
|
&self,
|
|
head: &Head,
|
|
into: FixedDotIndex,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents>;
|
|
|
|
fn head_around_dot_segments(
|
|
&self,
|
|
head: &Head,
|
|
around: DotIndex,
|
|
width: f64,
|
|
) -> Result<(Line, Line), NoTangents>;
|
|
|
|
fn head_around_dot_segment(
|
|
&self,
|
|
head: &Head,
|
|
around: DotIndex,
|
|
cw: bool,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents>;
|
|
|
|
fn head_around_dot_offset(&self, head: &Head, around: DotIndex, _width: f64) -> f64;
|
|
|
|
fn head_around_bend_segments(
|
|
&self,
|
|
head: &Head,
|
|
around: BendIndex,
|
|
width: f64,
|
|
) -> Result<(Line, Line), NoTangents>;
|
|
|
|
fn head_around_bend_segment(
|
|
&self,
|
|
head: &Head,
|
|
around: BendIndex,
|
|
cw: bool,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents>;
|
|
|
|
fn head_around_bend_offset(&self, head: &Head, around: BendIndex, _width: f64) -> f64;
|
|
|
|
fn head_cw(&self, head: &Head) -> Option<bool>;
|
|
|
|
fn cane_head(&self, face: LooseDotIndex) -> CaneHead;
|
|
|
|
fn rear_head(&self, face: LooseDotIndex) -> Head;
|
|
|
|
fn head(&self, face: DotIndex) -> Head;
|
|
}
|
|
|
|
impl<CW: Copy, R: AccessRules> Guide for Drawing<CW, R> {
|
|
fn head_into_dot_segment(
|
|
&self,
|
|
head: &Head,
|
|
into: FixedDotIndex,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents> {
|
|
let from_circle = self.head_circle(head, width);
|
|
let to_circle = Circle {
|
|
pos: self.primitive(into).weight().0.circle.pos,
|
|
r: 0.0,
|
|
};
|
|
|
|
let from_cw = self.head_cw(head);
|
|
math::tangent_segment(from_circle, from_cw, to_circle, None)
|
|
}
|
|
|
|
fn head_around_dot_segments(
|
|
&self,
|
|
head: &Head,
|
|
around: DotIndex,
|
|
width: f64,
|
|
) -> Result<(Line, Line), NoTangents> {
|
|
let from_circle = self.head_circle(head, width);
|
|
let to_circle =
|
|
self.dot_circle(around, width, self.conditions(head.face().into()).as_ref());
|
|
|
|
let from_cw = self.head_cw(head);
|
|
let tangents: Vec<Line> =
|
|
math::tangent_segments(from_circle, from_cw, to_circle, None)?.collect();
|
|
Ok((tangents[0], tangents[1]))
|
|
}
|
|
|
|
fn head_around_dot_segment(
|
|
&self,
|
|
head: &Head,
|
|
around: DotIndex,
|
|
cw: bool,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents> {
|
|
let from_circle = self.head_circle(head, width);
|
|
let to_circle =
|
|
self.dot_circle(around, width, self.conditions(head.face().into()).as_ref());
|
|
|
|
let from_cw = self.head_cw(head);
|
|
math::tangent_segment(from_circle, from_cw, to_circle, Some(cw))
|
|
}
|
|
|
|
fn head_around_dot_offset(&self, head: &Head, around: DotIndex, _width: f64) -> f64 {
|
|
self.clearance(
|
|
self.conditions(around.into()).as_ref(),
|
|
self.conditions(head.face().into()).as_ref(),
|
|
)
|
|
}
|
|
|
|
fn head_around_bend_segments(
|
|
&self,
|
|
head: &Head,
|
|
around: BendIndex,
|
|
width: f64,
|
|
) -> Result<(Line, Line), NoTangents> {
|
|
let from_circle = self.head_circle(head, width);
|
|
let to_circle =
|
|
self.bend_circle(around, width, self.conditions(head.face().into()).as_ref());
|
|
|
|
let from_cw = self.head_cw(head);
|
|
let tangents: Vec<Line> =
|
|
math::tangent_segments(from_circle, from_cw, to_circle, None)?.collect();
|
|
Ok((tangents[0], tangents[1]))
|
|
}
|
|
|
|
fn head_around_bend_segment(
|
|
&self,
|
|
head: &Head,
|
|
around: BendIndex,
|
|
cw: bool,
|
|
width: f64,
|
|
) -> Result<Line, NoTangents> {
|
|
let from_circle = self.head_circle(head, width);
|
|
let to_circle =
|
|
self.bend_circle(around, width, self.conditions(head.face().into()).as_ref());
|
|
|
|
let from_cw = self.head_cw(head);
|
|
math::tangent_segment(from_circle, from_cw, to_circle, Some(cw))
|
|
}
|
|
|
|
fn head_around_bend_offset(&self, head: &Head, around: BendIndex, _width: f64) -> f64 {
|
|
self.clearance(
|
|
self.conditions(head.face().into()).as_ref(),
|
|
self.conditions(around.into()).as_ref(),
|
|
)
|
|
}
|
|
|
|
fn head_cw(&self, head: &Head) -> Option<bool> {
|
|
if let Head::Cane(head) = head {
|
|
let joints = self.primitive(head.cane.bend).joints();
|
|
|
|
if head.face() == joints.0.into() {
|
|
Some(false)
|
|
} else {
|
|
Some(true)
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn cane_head(&self, face: LooseDotIndex) -> CaneHead {
|
|
CaneHead {
|
|
face,
|
|
cane: self.cane(face),
|
|
}
|
|
}
|
|
|
|
fn rear_head(&self, face: LooseDotIndex) -> Head {
|
|
self.head(self.rear(self.cane_head(face)))
|
|
}
|
|
|
|
fn head(&self, face: DotIndex) -> Head {
|
|
match face {
|
|
DotIndex::Fixed(dot) => BareHead { face: dot }.into(),
|
|
DotIndex::Loose(dot) => self.cane_head(dot).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
trait GuidePrivate {
|
|
fn clearance(&self, lhs: Option<&Conditions<'_>>, rhs: Option<&Conditions<'_>>) -> f64;
|
|
|
|
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<Conditions<'_>>;
|
|
}
|
|
|
|
impl<CW: Copy, R: AccessRules> GuidePrivate for Drawing<CW, R> {
|
|
fn clearance(&self, lhs: Option<&Conditions<'_>>, rhs: Option<&Conditions<'_>>) -> f64 {
|
|
match (lhs, rhs) {
|
|
(None, _) | (_, None) => 0.0,
|
|
(Some(lhs), Some(rhs)) => self.rules().clearance(lhs, rhs),
|
|
}
|
|
}
|
|
|
|
fn head_circle(&self, head: &Head, width: f64) -> Circle {
|
|
match *head {
|
|
Head::Bare(head) => Circle {
|
|
pos: head.face().primitive(self).shape().center(), // TODO.
|
|
r: 0.0,
|
|
},
|
|
Head::Cane(head) => {
|
|
if let Some(inner) = self.primitive(head.cane.bend).inner() {
|
|
self.bend_circle(
|
|
inner.into(),
|
|
width,
|
|
self.conditions(head.face().into()).as_ref(),
|
|
)
|
|
} else {
|
|
self.dot_circle(
|
|
self.primitive(head.cane.bend).core().into(),
|
|
width,
|
|
self.conditions(head.face().into()).as_ref(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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<Conditions<'_>> {
|
|
node.primitive(self).conditions()
|
|
}
|
|
}
|