// 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; 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; 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; fn head_around_bend_offset(&self, head: &Head, around: BendIndex, _width: f64) -> f64; fn head_cw(&self, head: &Head) -> Option; fn cane_head(&self, face: LooseDotIndex) -> CaneHead; fn rear_head(&self, face: LooseDotIndex) -> Head; fn head(&self, face: DotIndex) -> Head; } impl Guide for Drawing { fn head_into_dot_segment( &self, head: &Head, into: FixedDotIndex, width: f64, ) -> Result { 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 = 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 { 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 = 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 { 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 { 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>; } impl GuidePrivate for Drawing { 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> { node.primitive(self).conditions() } }